@hed-hog/core 0.0.387 → 0.0.390
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 +12 -11
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
- package/dist/access-log/access-log.interceptor.js +4 -2
- package/dist/access-log/access-log.interceptor.js.map +1 -1
- package/dist/ai/ai-attachment.fragments.d.ts +23 -0
- package/dist/ai/ai-attachment.fragments.d.ts.map +1 -0
- package/dist/ai/ai-attachment.fragments.js +34 -0
- package/dist/ai/ai-attachment.fragments.js.map +1 -0
- package/dist/ai/ai.controller.d.ts +7 -16
- package/dist/ai/ai.controller.d.ts.map +1 -1
- package/dist/ai/ai.prompts.d.ts +16 -0
- package/dist/ai/ai.prompts.d.ts.map +1 -0
- package/dist/ai/ai.prompts.js +37 -0
- package/dist/ai/ai.prompts.js.map +1 -0
- package/dist/ai/ai.service.d.ts +63 -12
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +215 -88
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai/dto/chat.dto.d.ts +4 -1
- package/dist/ai/dto/chat.dto.d.ts.map +1 -1
- package/dist/ai/dto/chat.dto.js +23 -1
- package/dist/ai/dto/chat.dto.js.map +1 -1
- package/dist/ai/dto/create-agent.dto.d.ts +1 -1
- package/dist/ai/dto/create-agent.dto.d.ts.map +1 -1
- package/dist/ai/dto/create-agent.dto.js +1 -1
- package/dist/ai/dto/create-agent.dto.js.map +1 -1
- package/dist/ai/dto/update-agent.dto.d.ts +1 -1
- package/dist/ai/dto/update-agent.dto.d.ts.map +1 -1
- package/dist/ai/dto/update-agent.dto.js +1 -1
- package/dist/ai/dto/update-agent.dto.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.controller.d.ts +61 -7
- package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.controller.js +63 -20
- package/dist/ai-instruction/ai-instruction.controller.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.module.js +13 -2
- package/dist/ai-instruction/ai-instruction.module.js.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.d.ts +131 -6
- package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-instruction.service.js +533 -60
- package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts +8 -0
- package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-prompt-builder.service.js +10 -1
- package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-registry.d.ts +45 -0
- package/dist/ai-instruction/ai-prompt-registry.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-registry.js +49 -0
- package/dist/ai-instruction/ai-prompt-registry.js.map +1 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +89 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js +222 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -0
- package/dist/ai-instruction/ai-prompt-template.util.d.ts +70 -0
- package/dist/ai-instruction/ai-prompt-template.util.d.ts.map +1 -0
- package/dist/ai-instruction/ai-prompt-template.util.js +154 -0
- package/dist/ai-instruction/ai-prompt-template.util.js.map +1 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +5 -0
- package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -1
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js +27 -1
- package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -1
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts +14 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts.map +1 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.js +42 -0
- package/dist/ai-instruction/dto/preview-ai-instruction.dto.js.map +1 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +11 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -1
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js +34 -0
- package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -1
- package/dist/ai-instruction/types/ai-instruction.types.d.ts +13 -1
- package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -1
- package/dist/ai-instruction/types/ai-instruction.types.js +10 -0
- package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -1
- package/dist/auth/auth.controller.d.ts +24 -9
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +49 -21
- package/dist/auth/auth.controller.js.map +1 -1
- package/dist/auth/auth.service.d.ts +33 -8
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +78 -29
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/captcha/captcha.module.d.ts +3 -0
- package/dist/captcha/captcha.module.d.ts.map +1 -0
- package/dist/captcha/captcha.module.js +23 -0
- package/dist/captcha/captcha.module.js.map +1 -0
- package/dist/captcha/captcha.service.d.ts +38 -0
- package/dist/captcha/captcha.service.d.ts.map +1 -0
- package/dist/captcha/captcha.service.js +144 -0
- package/dist/captcha/captcha.service.js.map +1 -0
- package/dist/challenge/challenge.service.d.ts +16 -7
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +64 -16
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/core.module.d.ts +3 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +29 -2
- package/dist/core.module.js.map +1 -1
- package/dist/dashboard/dashboard/dashboard.controller.d.ts +15 -15
- package/dist/dashboard/dashboard/dashboard.service.d.ts +15 -15
- package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +6 -6
- package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +6 -6
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +20 -20
- package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +20 -20
- package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +1 -1
- package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +1 -1
- package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +13 -13
- package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +13 -13
- package/dist/file/file.service.d.ts +6 -0
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +44 -4
- package/dist/file/file.service.js.map +1 -1
- package/dist/incident/incident-notifier.service.d.ts +36 -0
- package/dist/incident/incident-notifier.service.d.ts.map +1 -0
- package/dist/incident/incident-notifier.service.js +208 -0
- package/dist/incident/incident-notifier.service.js.map +1 -0
- package/dist/incident/incident.controller.d.ts +23 -0
- package/dist/incident/incident.controller.d.ts.map +1 -0
- package/dist/incident/incident.controller.js +75 -0
- package/dist/incident/incident.controller.js.map +1 -0
- package/dist/incident/incident.module.d.ts +7 -0
- package/dist/incident/incident.module.d.ts.map +1 -0
- package/dist/incident/incident.module.js +32 -0
- package/dist/incident/incident.module.js.map +1 -0
- package/dist/incident/incident.service.d.ts +72 -0
- package/dist/incident/incident.service.d.ts.map +1 -0
- package/dist/incident/incident.service.js +184 -0
- package/dist/incident/incident.service.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/dto/test.dto.d.ts +13 -0
- package/dist/integration-profile/dto/test.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/test.dto.js +26 -0
- package/dist/integration-profile/dto/test.dto.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +12 -1
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.controller.js +9 -1
- package/dist/integration-profile/integration-profile.controller.js.map +1 -1
- package/dist/integration-profile/integration-profile.module.d.ts +6 -1
- package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.module.js +26 -3
- package/dist/integration-profile/integration-profile.module.js.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +9 -1
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +26 -7
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +94 -91
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +13 -1
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +20 -5
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/integration-profile/testers/ai.testers.d.ts +3 -0
- package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/ai.testers.js +125 -0
- package/dist/integration-profile/testers/ai.testers.js.map +1 -0
- package/dist/integration-profile/testers/captcha.testers.d.ts +3 -0
- package/dist/integration-profile/testers/captcha.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/captcha.testers.js +121 -0
- package/dist/integration-profile/testers/captcha.testers.js.map +1 -0
- package/dist/integration-profile/testers/email.testers.d.ts +3 -0
- package/dist/integration-profile/testers/email.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/email.testers.js +128 -0
- package/dist/integration-profile/testers/email.testers.js.map +1 -0
- package/dist/integration-profile/testers/http-probe.d.ts +57 -0
- package/dist/integration-profile/testers/http-probe.d.ts.map +1 -0
- package/dist/integration-profile/testers/http-probe.js +140 -0
- package/dist/integration-profile/testers/http-probe.js.map +1 -0
- package/dist/integration-profile/testers/index.d.ts +12 -0
- package/dist/integration-profile/testers/index.d.ts.map +1 -0
- package/dist/integration-profile/testers/index.js +45 -0
- package/dist/integration-profile/testers/index.js.map +1 -0
- package/dist/integration-profile/testers/infrastructure.testers.d.ts +3 -0
- package/dist/integration-profile/testers/infrastructure.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/infrastructure.testers.js +105 -0
- package/dist/integration-profile/testers/infrastructure.testers.js.map +1 -0
- package/dist/integration-profile/testers/integration-tester.registry.d.ts +17 -0
- package/dist/integration-profile/testers/integration-tester.registry.d.ts.map +1 -0
- package/dist/integration-profile/testers/integration-tester.registry.js +52 -0
- package/dist/integration-profile/testers/integration-tester.registry.js.map +1 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts +55 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -0
- package/dist/integration-profile/testers/integration-tester.types.js +16 -0
- package/dist/integration-profile/testers/integration-tester.types.js.map +1 -0
- package/dist/integration-profile/testers/messaging.testers.d.ts +3 -0
- package/dist/integration-profile/testers/messaging.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/messaging.testers.js +70 -0
- package/dist/integration-profile/testers/messaging.testers.js.map +1 -0
- package/dist/integration-profile/testers/oauth.testers.d.ts +3 -0
- package/dist/integration-profile/testers/oauth.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/oauth.testers.js +285 -0
- package/dist/integration-profile/testers/oauth.testers.js.map +1 -0
- package/dist/integration-profile/testers/payment.testers.d.ts +3 -0
- package/dist/integration-profile/testers/payment.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/payment.testers.js +169 -0
- package/dist/integration-profile/testers/payment.testers.js.map +1 -0
- package/dist/integration-profile/testers/storage.testers.d.ts +3 -0
- package/dist/integration-profile/testers/storage.testers.d.ts.map +1 -0
- package/dist/integration-profile/testers/storage.testers.js +208 -0
- package/dist/integration-profile/testers/storage.testers.js.map +1 -0
- package/dist/language/en.json +4 -2
- package/dist/language/pt.json +4 -2
- package/dist/leaked-password/leaked-password.controller.d.ts +24 -0
- package/dist/leaked-password/leaked-password.controller.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.controller.js +78 -0
- package/dist/leaked-password/leaked-password.controller.js.map +1 -0
- package/dist/leaked-password/leaked-password.module.d.ts +8 -0
- package/dist/leaked-password/leaked-password.module.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.module.js +31 -0
- package/dist/leaked-password/leaked-password.module.js.map +1 -0
- package/dist/leaked-password/leaked-password.service.d.ts +64 -0
- package/dist/leaked-password/leaked-password.service.d.ts.map +1 -0
- package/dist/leaked-password/leaked-password.service.js +218 -0
- package/dist/leaked-password/leaked-password.service.js.map +1 -0
- package/dist/locale/locale.controller.d.ts +1 -1
- package/dist/locale/locale.service.d.ts +1 -1
- package/dist/mail/dto/preview.dto.d.ts +18 -0
- package/dist/mail/dto/preview.dto.d.ts.map +1 -0
- package/dist/mail/dto/preview.dto.js +66 -0
- package/dist/mail/dto/preview.dto.js.map +1 -0
- package/dist/mail/dto/send.dto.d.ts +7 -0
- package/dist/mail/dto/send.dto.d.ts.map +1 -1
- package/dist/mail/dto/send.dto.js +10 -0
- package/dist/mail/dto/send.dto.js.map +1 -1
- package/dist/mail/mail.controller.d.ts +4 -0
- package/dist/mail/mail.controller.d.ts.map +1 -1
- package/dist/mail/mail.controller.js +16 -2
- package/dist/mail/mail.controller.js.map +1 -1
- package/dist/mail/mail.service.d.ts +23 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +102 -13
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mail-layout/dto/update.dto.d.ts +17 -0
- package/dist/mail-layout/dto/update.dto.d.ts.map +1 -0
- package/dist/mail-layout/dto/update.dto.js +27 -0
- package/dist/mail-layout/dto/update.dto.js.map +1 -0
- package/dist/mail-layout/mail-layout.controller.d.ts +15 -0
- package/dist/mail-layout/mail-layout.controller.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.controller.js +71 -0
- package/dist/mail-layout/mail-layout.controller.js.map +1 -0
- package/dist/mail-layout/mail-layout.module.d.ts +3 -0
- package/dist/mail-layout/mail-layout.module.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.module.js +35 -0
- package/dist/mail-layout/mail-layout.module.js.map +1 -0
- package/dist/mail-layout/mail-layout.service.d.ts +27 -0
- package/dist/mail-layout/mail-layout.service.d.ts.map +1 -0
- package/dist/mail-layout/mail-layout.service.js +80 -0
- package/dist/mail-layout/mail-layout.service.js.map +1 -0
- package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
- package/dist/mcp-chat/mcp-chat.service.d.ts +24 -2
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +80 -24
- package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
- package/dist/menu/menu.controller.d.ts +3 -3
- package/dist/menu/menu.service.d.ts +3 -3
- package/dist/notification/notification.controller.d.ts +12 -12
- package/dist/notification/notification.service.d.ts +12 -12
- package/dist/oauth/oauth-token-broker.service.d.ts +72 -0
- package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -0
- package/dist/oauth/oauth-token-broker.service.js +258 -0
- package/dist/oauth/oauth-token-broker.service.js.map +1 -0
- package/dist/oauth/oauth.module.d.ts.map +1 -1
- package/dist/oauth/oauth.module.js +3 -1
- package/dist/oauth/oauth.module.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +2 -0
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +6 -3
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +8 -0
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +52 -0
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/presence/presence.controller.d.ts +16 -0
- package/dist/presence/presence.controller.d.ts.map +1 -0
- package/dist/presence/presence.controller.js +60 -0
- package/dist/presence/presence.controller.js.map +1 -0
- package/dist/presence/presence.listener.d.ts +29 -0
- package/dist/presence/presence.listener.d.ts.map +1 -0
- package/dist/presence/presence.listener.js +82 -0
- package/dist/presence/presence.listener.js.map +1 -0
- package/dist/presence/presence.module.d.ts +3 -0
- package/dist/presence/presence.module.d.ts.map +1 -0
- package/dist/presence/presence.module.js +32 -0
- package/dist/presence/presence.module.js.map +1 -0
- package/dist/presence/presence.service.d.ts +42 -0
- package/dist/presence/presence.service.d.ts.map +1 -0
- package/dist/presence/presence.service.js +147 -0
- package/dist/presence/presence.service.js.map +1 -0
- package/dist/profile/dto/delete-account.dto.d.ts +8 -0
- package/dist/profile/dto/delete-account.dto.d.ts.map +1 -0
- package/dist/profile/dto/delete-account.dto.js +22 -0
- package/dist/profile/dto/delete-account.dto.js.map +1 -0
- package/dist/profile/events/profile-account-deleted.event.d.ts +12 -0
- package/dist/profile/events/profile-account-deleted.event.d.ts.map +1 -0
- package/dist/profile/events/profile-account-deleted.event.js +17 -0
- package/dist/profile/events/profile-account-deleted.event.js.map +1 -0
- package/dist/profile/profile.controller.d.ts +21 -15
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.controller.js +75 -46
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.service.d.ts +30 -18
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +113 -37
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/realtime/realtime.gateway.d.ts +48 -0
- package/dist/realtime/realtime.gateway.d.ts.map +1 -0
- package/dist/realtime/realtime.gateway.js +156 -0
- package/dist/realtime/realtime.gateway.js.map +1 -0
- package/dist/realtime/realtime.module.d.ts +8 -0
- package/dist/realtime/realtime.module.d.ts.map +1 -0
- package/dist/realtime/realtime.module.js +29 -0
- package/dist/realtime/realtime.module.js.map +1 -0
- package/dist/realtime/realtime.service.d.ts +51 -0
- package/dist/realtime/realtime.service.d.ts.map +1 -0
- package/dist/realtime/realtime.service.js +177 -0
- package/dist/realtime/realtime.service.js.map +1 -0
- package/dist/redis/redis-pubsub.d.ts +27 -0
- package/dist/redis/redis-pubsub.d.ts.map +1 -0
- package/dist/redis/redis-pubsub.js +140 -0
- package/dist/redis/redis-pubsub.js.map +1 -0
- package/dist/role/role.controller.d.ts +3 -3
- package/dist/role/role.service.d.ts +3 -3
- package/dist/setting/dto/create.dto.d.ts.map +1 -1
- package/dist/setting/dto/create.dto.js +9 -2
- package/dist/setting/dto/create.dto.js.map +1 -1
- package/dist/setting/setting.controller.d.ts +5 -5
- package/dist/setting/setting.service.d.ts +8 -5
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +25 -1
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/user/dto/create-with-email-and-password.dto.d.ts +2 -0
- package/dist/user/dto/create-with-email-and-password.dto.d.ts.map +1 -1
- package/dist/user/dto/create-with-email-and-password.dto.js +5 -0
- package/dist/user/dto/create-with-email-and-password.dto.js.map +1 -1
- package/dist/user/dto/query-user-mail-list.dto.d.ts +5 -0
- package/dist/user/dto/query-user-mail-list.dto.d.ts.map +1 -0
- package/dist/user/dto/query-user-mail-list.dto.js +25 -0
- package/dist/user/dto/query-user-mail-list.dto.js.map +1 -0
- package/dist/user/user-credential.util.d.ts +19 -0
- package/dist/user/user-credential.util.d.ts.map +1 -0
- package/dist/user/user-credential.util.js +32 -0
- package/dist/user/user-credential.util.js.map +1 -0
- package/dist/user/user.controller.d.ts +17 -5
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +18 -3
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +28 -6
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +204 -13
- package/dist/user/user.service.js.map +1 -1
- package/dist/validators/is-strong-password-with-settings.validator.d.ts.map +1 -1
- package/dist/validators/is-strong-password-with-settings.validator.js +11 -0
- package/dist/validators/is-strong-password-with-settings.validator.js.map +1 -1
- package/dist/validators/service-locator.d.ts +9 -0
- package/dist/validators/service-locator.d.ts.map +1 -1
- package/dist/validators/service-locator.js +12 -0
- package/dist/validators/service-locator.js.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.module.d.ts +3 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.d.ts.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.js +23 -0
- package/dist/whatsapp-sender/whatsapp-sender.module.js.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +50 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.js +224 -0
- package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -0
- package/hedhog/data/integration_provider.yaml +16 -0
- package/hedhog/data/mail.yaml +88 -61
- package/hedhog/data/mail_layout.yaml +188 -0
- package/hedhog/data/menu.yaml +67 -3
- package/hedhog/data/route.yaml +135 -0
- package/hedhog/data/setting_group.yaml +139 -1
- package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +36 -0
- package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +2 -0
- package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +10 -7
- package/hedhog/frontend/app/ai_agent/page.tsx.ejs +7 -4
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +580 -0
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-preview.tsx.ejs +150 -0
- package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +199 -0
- package/hedhog/frontend/app/ai_instruction/_components/prompt-diff.tsx.ejs +63 -0
- package/hedhog/frontend/app/ai_instruction/_lib/line-diff.spec.ts.ejs +59 -0
- package/hedhog/frontend/app/ai_instruction/_lib/line-diff.ts.ejs +72 -0
- package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.spec.ts.ejs +170 -0
- package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.ts.ejs +167 -0
- package/hedhog/frontend/app/ai_instruction/_lib/types.ts.ejs +99 -0
- package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +422 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +51 -4
- package/hedhog/frontend/app/incidents/page.tsx.ejs +400 -0
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +153 -84
- package/hedhog/frontend/app/mail/layout/page.tsx.ejs +344 -0
- package/hedhog/frontend/app/mail/template/page.tsx.ejs +155 -121
- package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +129 -17
- package/hedhog/frontend/app/pages/_components/page-delete-dialog.tsx.ejs +343 -0
- package/hedhog/frontend/app/pages/_components/page-detail-sheet.tsx.ejs +438 -0
- package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1009 -0
- package/hedhog/frontend/app/pages/_components/system-page-types.ts.ejs +56 -0
- package/hedhog/frontend/app/pages/_components/use-system-pages.ts.ejs +171 -0
- package/hedhog/frontend/app/pages/page.tsx.ejs +514 -0
- package/hedhog/frontend/app/users/page.tsx.ejs +119 -13
- package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +231 -0
- package/hedhog/frontend/app/users/user-mails.tsx.ejs +399 -0
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +60 -75
- package/hedhog/frontend/messages/en.json +499 -3
- package/hedhog/frontend/messages/pt.json +499 -3
- package/hedhog/table/ai_agent.yaml +1 -0
- package/hedhog/table/ai_instruction.yaml +15 -0
- package/hedhog/table/leaked_password.yaml +18 -0
- package/hedhog/table/mail_layout.yaml +35 -0
- package/hedhog/table/mail_var.yaml +4 -0
- package/hedhog/table/setting.yaml +1 -0
- package/hedhog/table/system_incident.yaml +59 -0
- package/hedhog/table/user_presence.yaml +36 -0
- package/package.json +8 -4
- package/src/access-log/access-log.interceptor.ts +4 -2
- package/src/ai/ai-attachment.fragments.spec.ts +319 -0
- package/src/ai/ai-attachment.fragments.ts +35 -0
- package/src/ai/ai.prompts.ts +38 -0
- package/src/ai/ai.service.spec.ts +280 -0
- package/src/ai/ai.service.ts +338 -82
- package/src/ai/dto/chat.dto.ts +33 -3
- package/src/ai/dto/create-agent.dto.ts +2 -2
- package/src/ai/dto/update-agent.dto.ts +2 -2
- package/src/ai-instruction/ai-instruction.controller.ts +40 -9
- package/src/ai-instruction/ai-instruction.module.ts +13 -2
- package/src/ai-instruction/ai-instruction.service.spec.ts +658 -0
- package/src/ai-instruction/ai-instruction.service.ts +661 -62
- package/src/ai-instruction/ai-prompt-builder.service.ts +10 -1
- package/src/ai-instruction/ai-prompt-registry.ts +87 -0
- package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +375 -0
- package/src/ai-instruction/ai-prompt-resolver.service.ts +295 -0
- package/src/ai-instruction/ai-prompt-template.util.spec.ts +162 -0
- package/src/ai-instruction/ai-prompt-template.util.ts +199 -0
- package/src/ai-instruction/dto/create-ai-instruction.dto.ts +39 -3
- package/src/ai-instruction/dto/preview-ai-instruction.dto.ts +26 -0
- package/src/ai-instruction/dto/update-ai-instruction.dto.ts +42 -1
- package/src/ai-instruction/types/ai-instruction.types.ts +24 -1
- package/src/auth/auth.controller.ts +31 -5
- package/src/auth/auth.service.spec.ts +3 -1
- package/src/auth/auth.service.ts +101 -36
- package/src/captcha/captcha.module.ts +10 -0
- package/src/captcha/captcha.service.spec.ts +210 -0
- package/src/captcha/captcha.service.ts +191 -0
- package/src/challenge/challenge.service.spec.ts +39 -4
- package/src/challenge/challenge.service.ts +72 -17
- package/src/core.module.ts +27 -1
- package/src/file/file.service.spec.ts +105 -0
- package/src/file/file.service.ts +60 -3
- package/src/incident/incident-notifier.service.spec.ts +205 -0
- package/src/incident/incident-notifier.service.ts +241 -0
- package/src/incident/incident.controller.ts +38 -0
- package/src/incident/incident.module.ts +19 -0
- package/src/incident/incident.service.spec.ts +150 -0
- package/src/incident/incident.service.ts +209 -0
- package/src/index.ts +37 -0
- package/src/integration-profile/dto/test.dto.ts +19 -0
- package/src/integration-profile/integration-profile.controller.ts +10 -1
- package/src/integration-profile/integration-profile.module.spec.ts +51 -0
- package/src/integration-profile/integration-profile.module.ts +23 -4
- package/src/integration-profile/integration-profile.secrets.ts +9 -1
- package/src/integration-profile/integration-profile.service.spec.ts +279 -0
- package/src/integration-profile/integration-profile.service.ts +120 -124
- package/src/integration-profile/integration-profile.utils.spec.ts +22 -0
- package/src/integration-profile/integration-profile.utils.ts +40 -3
- package/src/integration-profile/testers/ai.testers.ts +165 -0
- package/src/integration-profile/testers/captcha.testers.spec.ts +92 -0
- package/src/integration-profile/testers/captcha.testers.ts +139 -0
- package/src/integration-profile/testers/email.testers.ts +161 -0
- package/src/integration-profile/testers/http-probe.spec.ts +153 -0
- package/src/integration-profile/testers/http-probe.ts +171 -0
- package/src/integration-profile/testers/index.ts +30 -0
- package/src/integration-profile/testers/infrastructure.testers.ts +122 -0
- package/src/integration-profile/testers/integration-tester.registry.spec.ts +75 -0
- package/src/integration-profile/testers/integration-tester.registry.ts +43 -0
- package/src/integration-profile/testers/integration-tester.types.ts +61 -0
- package/src/integration-profile/testers/messaging.testers.ts +102 -0
- package/src/integration-profile/testers/oauth.testers.spec.ts +220 -0
- package/src/integration-profile/testers/oauth.testers.ts +305 -0
- package/src/integration-profile/testers/payment.testers.ts +185 -0
- package/src/integration-profile/testers/remaining.testers.spec.ts +401 -0
- package/src/integration-profile/testers/storage.testers.spec.ts +185 -0
- package/src/integration-profile/testers/storage.testers.ts +226 -0
- package/src/integration-profile/testers/tester-coverage.spec.ts +58 -0
- package/src/language/en.json +4 -2
- package/src/language/pt.json +4 -2
- package/src/leaked-password/README.md +83 -0
- package/src/leaked-password/leaked-password.controller.ts +52 -0
- package/src/leaked-password/leaked-password.module.ts +18 -0
- package/src/leaked-password/leaked-password.service.ts +242 -0
- package/src/leaked-password/seed/build-seed-sql.mjs +180 -0
- package/src/leaked-password/seed/common-passwords.txt +335 -0
- package/src/mail/dto/preview.dto.ts +50 -0
- package/src/mail/dto/send.dto.ts +14 -1
- package/src/mail/mail-template.spec.ts +197 -0
- package/src/mail/mail.controller.ts +9 -2
- package/src/mail/mail.service.spec.ts +116 -1
- package/src/mail/mail.service.ts +118 -12
- package/src/mail-layout/dto/update.dto.ts +19 -0
- package/src/mail-layout/mail-layout.controller.ts +47 -0
- package/src/mail-layout/mail-layout.module.ts +22 -0
- package/src/mail-layout/mail-layout.service.ts +94 -0
- package/src/mcp-chat/mcp-chat.service.spec.ts +236 -0
- package/src/mcp-chat/mcp-chat.service.ts +122 -26
- package/src/oauth/oauth-token-broker.service.spec.ts +172 -0
- package/src/oauth/oauth-token-broker.service.ts +363 -0
- package/src/oauth/oauth.module.ts +3 -1
- package/src/oauth/oauth.service.ts +8 -2
- package/src/pdf/pdf.service.ts +75 -0
- package/src/presence/presence.controller.ts +29 -0
- package/src/presence/presence.listener.ts +53 -0
- package/src/presence/presence.module.ts +19 -0
- package/src/presence/presence.service.spec.ts +97 -0
- package/src/presence/presence.service.ts +160 -0
- package/src/profile/dto/delete-account.dto.ts +11 -0
- package/src/profile/events/profile-account-deleted.event.ts +13 -0
- package/src/profile/profile.controller.ts +46 -34
- package/src/profile/profile.service.ts +138 -38
- package/src/realtime/realtime.gateway.ts +138 -0
- package/src/realtime/realtime.module.ts +16 -0
- package/src/realtime/realtime.service.spec.ts +50 -0
- package/src/realtime/realtime.service.ts +187 -0
- package/src/redis/redis-pubsub.ts +179 -0
- package/src/setting/dto/create.dto.ts +13 -2
- package/src/setting/setting.service.spec.ts +56 -0
- package/src/setting/setting.service.ts +29 -1
- package/src/user/dto/create-with-email-and-password.dto.ts +6 -1
- package/src/user/dto/query-user-mail-list.dto.ts +10 -0
- package/src/user/user-credential.util.ts +35 -0
- package/src/user/user.controller.ts +17 -2
- package/src/user/user.service.spec.ts +153 -0
- package/src/user/user.service.ts +255 -12
- package/src/validators/is-strong-password-with-settings.validator.ts +12 -0
- package/src/validators/service-locator.ts +15 -0
- package/src/whatsapp-sender/whatsapp-sender.module.ts +10 -0
- package/src/whatsapp-sender/whatsapp-sender.service.ts +313 -0
|
@@ -13,11 +13,37 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
13
13
|
};
|
|
14
14
|
var AiInstructionService_1;
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.AiInstructionService = void 0;
|
|
16
|
+
exports.AiInstructionService = exports.FALLBACK_SYSTEM_PROMPT = void 0;
|
|
17
17
|
const api_prisma_1 = require("@hed-hog/api-prisma");
|
|
18
18
|
const common_1 = require("@nestjs/common");
|
|
19
|
+
const node_crypto_1 = require("node:crypto");
|
|
20
|
+
const redis_pubsub_1 = require("../redis/redis-pubsub");
|
|
21
|
+
const ai_prompt_registry_1 = require("./ai-prompt-registry");
|
|
22
|
+
const ai_prompt_template_util_1 = require("./ai-prompt-template.util");
|
|
23
|
+
/**
|
|
24
|
+
* Cross-pod invalidation happens through Redis, with the database version check
|
|
25
|
+
* as backup. These TTLs are the last resort for when both are unavailable.
|
|
26
|
+
*/
|
|
19
27
|
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
20
|
-
const
|
|
28
|
+
const TASK_CACHE_TTL_MS = 60 * 1000;
|
|
29
|
+
const NEGATIVE_CACHE_TTL_MS = 60 * 1000;
|
|
30
|
+
/**
|
|
31
|
+
* Reuses the generic runtime-version table owned by the settings service. No
|
|
32
|
+
* column there is setting-specific, and the unique index on `scope` means a new
|
|
33
|
+
* scope needs no migration — the idempotent INSERT below creates its own row.
|
|
34
|
+
*/
|
|
35
|
+
const RUNTIME_STATE_SCOPE = 'ai_instruction';
|
|
36
|
+
/**
|
|
37
|
+
* Columns are cast explicitly because `$queryRaw` returns DECIMAL as
|
|
38
|
+
* `Prisma.Decimal`, which serializes as a string over HTTP and becomes NaN in
|
|
39
|
+
* arithmetic. Keep every SELECT in this file using this list.
|
|
40
|
+
*/
|
|
41
|
+
const INSTRUCTION_COLUMNS = api_prisma_1.Prisma.sql `
|
|
42
|
+
id, slug, layer, name, content, variables_schema, locale, module_slug,
|
|
43
|
+
is_active, version, model, temperature::float8 AS temperature, max_tokens,
|
|
44
|
+
is_locked, created_at, updated_at
|
|
45
|
+
`;
|
|
46
|
+
exports.FALLBACK_SYSTEM_PROMPT = 'You are a helpful assistant with access to tools from the HedHog system. ' +
|
|
21
47
|
"Use the available tools to answer the user's questions accurately. " +
|
|
22
48
|
'Always respond in the same language the user writes in.';
|
|
23
49
|
let AiInstructionService = AiInstructionService_1 = class AiInstructionService {
|
|
@@ -25,78 +51,349 @@ let AiInstructionService = AiInstructionService_1 = class AiInstructionService {
|
|
|
25
51
|
this.prisma = prisma;
|
|
26
52
|
this.logger = new common_1.Logger(AiInstructionService_1.name);
|
|
27
53
|
this.cache = new Map();
|
|
54
|
+
this.instanceId = (0, node_crypto_1.randomUUID)();
|
|
55
|
+
this.redisChannel = 'core:ai-instruction:invalidated';
|
|
56
|
+
this.versionCheckIntervalMs = Math.max(1000, Number(process.env.AI_INSTRUCTION_VERSION_CHECK_INTERVAL_MS || 5000));
|
|
57
|
+
this.localCacheVersion = 0;
|
|
58
|
+
this.lastVersionCheckAt = 0;
|
|
59
|
+
this.versionCheckInFlight = null;
|
|
60
|
+
this.versionStateEnsured = false;
|
|
61
|
+
this.lastVersionCheckErrorLogAt = 0;
|
|
62
|
+
this.pubsub = null;
|
|
28
63
|
}
|
|
29
64
|
async onModuleInit() {
|
|
30
65
|
// Warm up global and product layers on startup
|
|
31
66
|
try {
|
|
67
|
+
// Before the warm-up: starting at version 0 would make the first read
|
|
68
|
+
// think it is behind and throw the freshly warmed cache away.
|
|
69
|
+
await this.syncCacheVersionFromDatabase();
|
|
32
70
|
await this.getLayer('system');
|
|
33
71
|
await this.getLayer('product');
|
|
72
|
+
await this.warnAboutUnseededPrompts();
|
|
34
73
|
}
|
|
35
74
|
catch (_a) {
|
|
36
|
-
// DB may not be ready yet; cache
|
|
75
|
+
// DB may not be ready yet; cache and version fill in on first request
|
|
76
|
+
}
|
|
77
|
+
// Outside the try: createRedisPubSub already handles its own failures.
|
|
78
|
+
this.pubsub = await (0, redis_pubsub_1.createRedisPubSub)({
|
|
79
|
+
channel: this.redisChannel,
|
|
80
|
+
logger: this.logger,
|
|
81
|
+
label: 'ai instruction',
|
|
82
|
+
onMessage: (raw) => this.handleInvalidationMessage(raw),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async onModuleDestroy() {
|
|
86
|
+
var _a;
|
|
87
|
+
await ((_a = this.pubsub) === null || _a === void 0 ? void 0 : _a.close());
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Reports prompts declared in code that have no row yet. Without this, a
|
|
91
|
+
* forgotten seed only surfaces the first time that AI flow runs — which for
|
|
92
|
+
* essay grading means the first graded exam.
|
|
93
|
+
*/
|
|
94
|
+
async warnAboutUnseededPrompts() {
|
|
95
|
+
const definitions = (0, ai_prompt_registry_1.listAiPromptDefinitions)();
|
|
96
|
+
if (!definitions.length)
|
|
97
|
+
return;
|
|
98
|
+
const records = await this.getRecordsBySlugs(definitions.map((d) => d.slug));
|
|
99
|
+
const missing = definitions.filter((d) => !records.get(d.slug)).map((d) => d.slug);
|
|
100
|
+
if (missing.length) {
|
|
101
|
+
this.logger.warn(`${missing.length} AI prompt(s) declared in code have no ai_instruction row ` +
|
|
102
|
+
`and will use the code fallback: ${missing.join(', ')}`);
|
|
37
103
|
}
|
|
38
104
|
}
|
|
39
105
|
// ── Public layer accessors (cached) ──────────────────────────────────────
|
|
40
106
|
async getLayer(layer, moduleSlug) {
|
|
107
|
+
var _a;
|
|
41
108
|
const cacheKey = moduleSlug ? `${layer}:${moduleSlug}` : layer;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
109
|
+
await this.ensureCacheVersionFresh();
|
|
110
|
+
const cached = this.readCache(cacheKey);
|
|
111
|
+
if (cached !== undefined) {
|
|
112
|
+
return (_a = cached === null || cached === void 0 ? void 0 : cached.content) !== null && _a !== void 0 ? _a : (layer === 'system' ? exports.FALLBACK_SYSTEM_PROMPT : '');
|
|
45
113
|
}
|
|
46
114
|
const instruction = await this.findActiveByLayer(layer, moduleSlug);
|
|
115
|
+
this.writeCache(cacheKey, instruction);
|
|
47
116
|
if (!instruction) {
|
|
48
|
-
|
|
49
|
-
return FALLBACK_SYSTEM_PROMPT;
|
|
50
|
-
}
|
|
51
|
-
return '';
|
|
117
|
+
return layer === 'system' ? exports.FALLBACK_SYSTEM_PROMPT : '';
|
|
52
118
|
}
|
|
53
|
-
this.cache.set(cacheKey, { content: instruction.content, fetchedAt: Date.now() });
|
|
54
119
|
return instruction.content;
|
|
55
120
|
}
|
|
56
121
|
async getAgentInstruction(agentSlug) {
|
|
122
|
+
var _a, _b;
|
|
57
123
|
const cacheKey = `agent:${agentSlug}`;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
124
|
+
await this.ensureCacheVersionFresh();
|
|
125
|
+
const cached = this.readCache(cacheKey);
|
|
126
|
+
if (cached !== undefined)
|
|
127
|
+
return (_a = cached === null || cached === void 0 ? void 0 : cached.content) !== null && _a !== void 0 ? _a : '';
|
|
62
128
|
const instruction = await this.findActiveBySlug(`agent.${agentSlug}`);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.cache.set(cacheKey, { content: instruction.content, fetchedAt: Date.now() });
|
|
66
|
-
return instruction.content;
|
|
129
|
+
this.writeCache(cacheKey, instruction);
|
|
130
|
+
return (_b = instruction === null || instruction === void 0 ? void 0 : instruction.content) !== null && _b !== void 0 ? _b : '';
|
|
67
131
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Full record by exact slug, cached. This is what `AiPromptResolverService`
|
|
134
|
+
* uses for task prompts — it needs the model params and `is_locked`, not just
|
|
135
|
+
* the content.
|
|
136
|
+
*/
|
|
137
|
+
async getRecordBySlug(slug) {
|
|
138
|
+
const cacheKey = `slug:${slug}`;
|
|
139
|
+
await this.ensureCacheVersionFresh();
|
|
140
|
+
const cached = this.readCache(cacheKey);
|
|
141
|
+
if (cached !== undefined)
|
|
142
|
+
return cached;
|
|
143
|
+
const record = await this.findBySlug(slug);
|
|
144
|
+
this.writeCache(cacheKey, record);
|
|
145
|
+
return record;
|
|
146
|
+
}
|
|
147
|
+
/** One query for pipelines that resolve several prompts per call. */
|
|
148
|
+
async getRecordsBySlugs(slugs) {
|
|
149
|
+
const result = new Map();
|
|
150
|
+
const missing = [];
|
|
151
|
+
await this.ensureCacheVersionFresh();
|
|
152
|
+
for (const slug of slugs) {
|
|
153
|
+
const cached = this.readCache(`slug:${slug}`);
|
|
154
|
+
if (cached !== undefined) {
|
|
155
|
+
result.set(slug, cached);
|
|
75
156
|
}
|
|
76
157
|
else {
|
|
77
|
-
|
|
78
|
-
const identifier = slug.slice(dotIndex + 1);
|
|
79
|
-
this.cache.delete(layer);
|
|
80
|
-
this.cache.delete(`${layer}:${identifier}`);
|
|
158
|
+
missing.push(slug);
|
|
81
159
|
}
|
|
82
160
|
}
|
|
83
|
-
|
|
161
|
+
if (missing.length) {
|
|
162
|
+
const rows = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
163
|
+
SELECT ${INSTRUCTION_COLUMNS}
|
|
164
|
+
FROM ai_instruction WHERE slug IN (${api_prisma_1.Prisma.join(missing)})
|
|
165
|
+
`);
|
|
166
|
+
const found = new Map(rows.map((row) => [row.slug, row]));
|
|
167
|
+
for (const slug of missing) {
|
|
168
|
+
const record = found.get(slug) || null;
|
|
169
|
+
this.writeCache(`slug:${slug}`, record);
|
|
170
|
+
result.set(slug, record);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
invalidateCache(slug) {
|
|
176
|
+
if (!slug) {
|
|
84
177
|
this.cache.clear();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Exact-slug key used by getRecordBySlug/getRecordsBySlugs.
|
|
181
|
+
this.cache.delete(`slug:${slug}`);
|
|
182
|
+
// Layer keys use format "layer" or "layer:identifier", while slugs use
|
|
183
|
+
// "layer.identifier" (e.g. "module.lms", "agent.admin-support").
|
|
184
|
+
const dotIndex = slug.indexOf('.');
|
|
185
|
+
if (dotIndex === -1) {
|
|
186
|
+
this.cache.delete(slug);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const layer = slug.slice(0, dotIndex);
|
|
190
|
+
const identifier = slug.slice(dotIndex + 1);
|
|
191
|
+
this.cache.delete(layer);
|
|
192
|
+
this.cache.delete(`${layer}:${identifier}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** `undefined` = cache miss/expired. `null` = cached absence. */
|
|
196
|
+
readCache(cacheKey) {
|
|
197
|
+
const cached = this.cache.get(cacheKey);
|
|
198
|
+
if (!cached)
|
|
199
|
+
return undefined;
|
|
200
|
+
if (Date.now() - cached.fetchedAt >= cached.ttl) {
|
|
201
|
+
this.cache.delete(cacheKey);
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return cached.record;
|
|
205
|
+
}
|
|
206
|
+
writeCache(cacheKey, record) {
|
|
207
|
+
const ttl = !record
|
|
208
|
+
? NEGATIVE_CACHE_TTL_MS
|
|
209
|
+
: record.layer === 'task'
|
|
210
|
+
? TASK_CACHE_TTL_MS
|
|
211
|
+
: CACHE_TTL_MS;
|
|
212
|
+
this.cache.set(cacheKey, { record, fetchedAt: Date.now(), ttl });
|
|
213
|
+
}
|
|
214
|
+
// ── Cross-pod invalidation ───────────────────────────────────────────────
|
|
215
|
+
handleInvalidationMessage(raw) {
|
|
216
|
+
let payload;
|
|
217
|
+
try {
|
|
218
|
+
payload = JSON.parse(raw);
|
|
219
|
+
}
|
|
220
|
+
catch (_a) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!payload || payload.originInstanceId === this.instanceId) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// The version is only a watermark, so the lazy check does not re-clear a
|
|
227
|
+
// cache this message already fixed selectively. The invalidation itself is
|
|
228
|
+
// applied unconditionally: dropping an out-of-order message — this pod may
|
|
229
|
+
// have bumped past it on a write of its own — would leave the slug stale
|
|
230
|
+
// here until its TTL, which is exactly the bug this whole path fixes.
|
|
231
|
+
if (Number.isFinite(payload.version)) {
|
|
232
|
+
this.localCacheVersion = Math.max(this.localCacheVersion, payload.version);
|
|
233
|
+
this.lastVersionCheckAt = Date.now();
|
|
234
|
+
}
|
|
235
|
+
const slugs = Array.isArray(payload.slugs) ? payload.slugs.filter(Boolean) : [];
|
|
236
|
+
if (!slugs.length) {
|
|
237
|
+
this.invalidateCache();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
for (const slug of slugs) {
|
|
241
|
+
this.invalidateCache(slug);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async ensureVersionState() {
|
|
245
|
+
// The row is created once and never removed; repeating the INSERT on every
|
|
246
|
+
// version check would double the queries of a path that runs every 5s.
|
|
247
|
+
if (this.versionStateEnsured) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
await this.prisma.$executeRawUnsafe(`
|
|
251
|
+
INSERT INTO "setting_runtime_state" ("scope", "version", "updated_at")
|
|
252
|
+
VALUES ('${RUNTIME_STATE_SCOPE}', 0, NOW())
|
|
253
|
+
ON CONFLICT ("scope") DO NOTHING
|
|
254
|
+
`);
|
|
255
|
+
this.versionStateEnsured = true;
|
|
256
|
+
}
|
|
257
|
+
/** BIGINT comes back from `$queryRawUnsafe` as a `bigint`, not a number. */
|
|
258
|
+
parseVersionValue(value) {
|
|
259
|
+
if (typeof value === 'number') {
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
if (typeof value === 'bigint') {
|
|
263
|
+
return Number(value);
|
|
264
|
+
}
|
|
265
|
+
if (typeof value === 'string') {
|
|
266
|
+
return Number(value);
|
|
267
|
+
}
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
async getCacheVersionFromDatabase() {
|
|
271
|
+
var _a;
|
|
272
|
+
await this.ensureVersionState();
|
|
273
|
+
const rows = await this.prisma.$queryRawUnsafe(`
|
|
274
|
+
SELECT "version"
|
|
275
|
+
FROM "setting_runtime_state"
|
|
276
|
+
WHERE "scope" = '${RUNTIME_STATE_SCOPE}'
|
|
277
|
+
LIMIT 1
|
|
278
|
+
`);
|
|
279
|
+
return this.parseVersionValue((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.version);
|
|
280
|
+
}
|
|
281
|
+
async syncCacheVersionFromDatabase() {
|
|
282
|
+
this.localCacheVersion = await this.getCacheVersionFromDatabase();
|
|
283
|
+
this.lastVersionCheckAt = Date.now();
|
|
284
|
+
}
|
|
285
|
+
async bumpCacheVersion() {
|
|
286
|
+
var _a;
|
|
287
|
+
await this.ensureVersionState();
|
|
288
|
+
const rows = await this.prisma.$queryRawUnsafe(`
|
|
289
|
+
UPDATE "setting_runtime_state"
|
|
290
|
+
SET "version" = "version" + 1,
|
|
291
|
+
"updated_at" = NOW()
|
|
292
|
+
WHERE "scope" = '${RUNTIME_STATE_SCOPE}'
|
|
293
|
+
RETURNING "version"
|
|
294
|
+
`);
|
|
295
|
+
const nextVersion = this.parseVersionValue((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.version);
|
|
296
|
+
this.localCacheVersion = nextVersion;
|
|
297
|
+
this.lastVersionCheckAt = Date.now();
|
|
298
|
+
return nextVersion;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Catches up with writes from other pods when Redis is unavailable. Throttled
|
|
302
|
+
* and coalesced, so a burst of prompt resolutions costs one query at most.
|
|
303
|
+
*/
|
|
304
|
+
async ensureCacheVersionFresh() {
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
if (now - this.lastVersionCheckAt < this.versionCheckIntervalMs) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// `lastVersionCheckAt` only advances after the await, so parallel reads
|
|
310
|
+
// would each fire the same query and tie up the connection pool.
|
|
311
|
+
if (this.versionCheckInFlight) {
|
|
312
|
+
return this.versionCheckInFlight;
|
|
313
|
+
}
|
|
314
|
+
this.versionCheckInFlight = this.runVersionCheck(now).finally(() => {
|
|
315
|
+
this.versionCheckInFlight = null;
|
|
316
|
+
});
|
|
317
|
+
return this.versionCheckInFlight;
|
|
318
|
+
}
|
|
319
|
+
async runVersionCheck(startedAt) {
|
|
320
|
+
try {
|
|
321
|
+
const currentVersion = await this.getCacheVersionFromDatabase();
|
|
322
|
+
this.lastVersionCheckAt = startedAt;
|
|
323
|
+
if (currentVersion > this.localCacheVersion) {
|
|
324
|
+
this.localCacheVersion = currentVersion;
|
|
325
|
+
// This path knows a change happened but not which slugs it touched.
|
|
326
|
+
this.cache.clear();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
// Bookkeeping must never break prompt resolution: the resolver handles a
|
|
331
|
+
// throw by falling back to the code prompt, so a slow database would
|
|
332
|
+
// silently downgrade prompt quality. Serving the current cache is better.
|
|
333
|
+
this.lastVersionCheckAt = startedAt; // and do not hammer a sick database
|
|
334
|
+
this.logVersionCheckError(error);
|
|
85
335
|
}
|
|
86
336
|
}
|
|
337
|
+
logVersionCheckError(error) {
|
|
338
|
+
const now = Date.now();
|
|
339
|
+
if (now - this.lastVersionCheckErrorLogAt < 30000) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
this.lastVersionCheckErrorLogAt = now;
|
|
343
|
+
this.logger.warn(`Failed to check ai instruction cache version: ${error instanceof Error ? error.message : String(error)}`);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Tells the other pods to drop these slugs. Bumps the database version first
|
|
347
|
+
* so pods without Redis still catch up on their next version check.
|
|
348
|
+
*/
|
|
349
|
+
async publishInvalidation(slugs) {
|
|
350
|
+
var _a;
|
|
351
|
+
const filteredSlugs = Array.from(new Set(slugs.filter(Boolean)));
|
|
352
|
+
if (filteredSlugs.length === 0) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
let version = this.localCacheVersion;
|
|
356
|
+
try {
|
|
357
|
+
version = await this.bumpCacheVersion();
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
// The prompt write already committed; failing here must not turn a
|
|
361
|
+
// successful edit into a 500.
|
|
362
|
+
this.logger.warn(`Failed to bump ai instruction cache version: ${error instanceof Error ? error.message : String(error)}`);
|
|
363
|
+
}
|
|
364
|
+
// A stale version here is harmless: receivers take the max, and the lazy
|
|
365
|
+
// check compares against the database, not against this number.
|
|
366
|
+
await ((_a = this.pubsub) === null || _a === void 0 ? void 0 : _a.publish({
|
|
367
|
+
originInstanceId: this.instanceId,
|
|
368
|
+
version,
|
|
369
|
+
slugs: filteredSlugs,
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
87
372
|
// ── CRUD ─────────────────────────────────────────────────────────────────
|
|
88
|
-
async list(paginationParams) {
|
|
373
|
+
async list(paginationParams, filters) {
|
|
89
374
|
var _a;
|
|
90
375
|
const page = Math.max(Number((paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.page) || 1), 1);
|
|
91
376
|
const pageSize = Math.min(Math.max(Number((paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.pageSize) || 20), 1), 100);
|
|
92
377
|
const offset = (page - 1) * pageSize;
|
|
93
378
|
const search = String((paginationParams === null || paginationParams === void 0 ? void 0 : paginationParams.search) || '').trim();
|
|
94
|
-
const
|
|
95
|
-
|
|
379
|
+
const conditions = [];
|
|
380
|
+
if (search) {
|
|
381
|
+
conditions.push(api_prisma_1.Prisma.sql `(LOWER(slug) LIKE LOWER(${`%${search}%`}) OR LOWER(name) LIKE LOWER(${`%${search}%`}))`);
|
|
382
|
+
}
|
|
383
|
+
if (filters === null || filters === void 0 ? void 0 : filters.layer) {
|
|
384
|
+
conditions.push(api_prisma_1.Prisma.sql `layer = CAST(${filters.layer} AS ai_instruction_layer_enum)`);
|
|
385
|
+
}
|
|
386
|
+
if (filters === null || filters === void 0 ? void 0 : filters.module_slug) {
|
|
387
|
+
conditions.push(api_prisma_1.Prisma.sql `module_slug = ${filters.module_slug}`);
|
|
388
|
+
}
|
|
389
|
+
if ((filters === null || filters === void 0 ? void 0 : filters.is_active) !== undefined) {
|
|
390
|
+
conditions.push(api_prisma_1.Prisma.sql `is_active = ${filters.is_active}`);
|
|
391
|
+
}
|
|
392
|
+
const whereClause = conditions.length
|
|
393
|
+
? api_prisma_1.Prisma.sql `WHERE ${api_prisma_1.Prisma.join(conditions, ' AND ')}`
|
|
96
394
|
: api_prisma_1.Prisma.empty;
|
|
97
395
|
const data = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
98
|
-
SELECT
|
|
99
|
-
is_active, version, created_at, updated_at
|
|
396
|
+
SELECT ${INSTRUCTION_COLUMNS}
|
|
100
397
|
FROM ai_instruction
|
|
101
398
|
${whereClause}
|
|
102
399
|
ORDER BY layer, slug
|
|
@@ -115,24 +412,105 @@ let AiInstructionService = AiInstructionService_1 = class AiInstructionService {
|
|
|
115
412
|
totalPages: Math.ceil(total / pageSize),
|
|
116
413
|
};
|
|
117
414
|
}
|
|
415
|
+
/** Distinct module slugs, for the module filter in the admin. */
|
|
416
|
+
async listModules() {
|
|
417
|
+
const rows = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
418
|
+
SELECT DISTINCT module_slug FROM ai_instruction
|
|
419
|
+
WHERE module_slug IS NOT NULL
|
|
420
|
+
ORDER BY module_slug
|
|
421
|
+
`);
|
|
422
|
+
return rows.map((row) => row.module_slug);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The prompts declared in code, paired with what the database currently holds.
|
|
426
|
+
*
|
|
427
|
+
* `dbMatchesFallback` is what lets the admin show "default" vs "customized"
|
|
428
|
+
* and offer "restore default"; it is also the fidelity check after a seed —
|
|
429
|
+
* every freshly seeded prompt must report `true`.
|
|
430
|
+
*/
|
|
431
|
+
async listRegistry(moduleSlug) {
|
|
432
|
+
const definitions = (0, ai_prompt_registry_1.listAiPromptDefinitions)(moduleSlug);
|
|
433
|
+
const records = await this.getRecordsBySlugs(definitions.map((d) => d.slug));
|
|
434
|
+
return definitions.map((definition) => {
|
|
435
|
+
const record = records.get(definition.slug) || null;
|
|
436
|
+
return {
|
|
437
|
+
slug: definition.slug,
|
|
438
|
+
name: definition.name,
|
|
439
|
+
module_slug: definition.moduleSlug,
|
|
440
|
+
locale: definition.locale || 'pt',
|
|
441
|
+
fallback: definition.fallback,
|
|
442
|
+
variables: definition.variables,
|
|
443
|
+
security_sensitive: Boolean(definition.securitySensitive),
|
|
444
|
+
required_phrases: definition.requiredPhrases || [],
|
|
445
|
+
defaults: definition.defaults || {},
|
|
446
|
+
exists_in_db: Boolean(record),
|
|
447
|
+
db_matches_fallback: record ? record.content === definition.fallback : false,
|
|
448
|
+
};
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
/** Renders a draft without saving it, so the admin can check interpolation. */
|
|
452
|
+
previewPrompt(input) {
|
|
453
|
+
var _a, _b;
|
|
454
|
+
const definition = input.slug ? (0, ai_prompt_registry_1.getAiPromptDefinition)(input.slug) : undefined;
|
|
455
|
+
const schema = (_b = (_a = definition === null || definition === void 0 ? void 0 : definition.variables) !== null && _a !== void 0 ? _a : input.variables_schema) !== null && _b !== void 0 ? _b : null;
|
|
456
|
+
const validation = (0, ai_prompt_template_util_1.validateTemplate)(input.content, schema);
|
|
457
|
+
let rendered = '';
|
|
458
|
+
let renderError = null;
|
|
459
|
+
try {
|
|
460
|
+
rendered = (0, ai_prompt_template_util_1.renderTemplate)(input.content, input.variables || {});
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
renderError = error instanceof Error ? error.message : String(error);
|
|
464
|
+
}
|
|
465
|
+
const warnings = [];
|
|
466
|
+
if (validation.unknownPlaceholders.length) {
|
|
467
|
+
warnings.push(`Variáveis não declaradas: ${validation.unknownPlaceholders.join(', ')}. ` +
|
|
468
|
+
'Elas serão renderizadas literalmente.');
|
|
469
|
+
}
|
|
470
|
+
warnings.push(...this.missingPhraseWarnings(definition, input.content));
|
|
471
|
+
return {
|
|
472
|
+
rendered,
|
|
473
|
+
render_error: renderError,
|
|
474
|
+
char_count: rendered.length,
|
|
475
|
+
estimated_tokens: (0, ai_prompt_template_util_1.estimateTokens)(rendered),
|
|
476
|
+
missing_required: validation.missingRequired,
|
|
477
|
+
unknown_placeholders: validation.unknownPlaceholders,
|
|
478
|
+
unbalanced_blocks: validation.unbalancedBlocks,
|
|
479
|
+
warnings,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
118
482
|
async getById(id) {
|
|
119
483
|
const record = await this.findById(id);
|
|
120
484
|
if (!record)
|
|
121
485
|
throw new common_1.NotFoundException(`AI instruction ${id} not found.`);
|
|
122
486
|
return record;
|
|
123
487
|
}
|
|
124
|
-
|
|
125
|
-
|
|
488
|
+
/**
|
|
489
|
+
* Inactive records are returned on purpose: the admin needs to open one to
|
|
490
|
+
* reactivate it. Pass `activeOnly` for the resolution path.
|
|
491
|
+
*/
|
|
492
|
+
async getBySlug(slug, activeOnly = false) {
|
|
493
|
+
const record = activeOnly
|
|
494
|
+
? await this.findActiveBySlug(slug)
|
|
495
|
+
: await this.findBySlug(slug);
|
|
126
496
|
if (!record)
|
|
127
497
|
throw new common_1.NotFoundException(`AI instruction "${slug}" not found.`);
|
|
128
498
|
return record;
|
|
129
499
|
}
|
|
130
500
|
async create(dto, createdById) {
|
|
131
|
-
var _a;
|
|
501
|
+
var _a, _b, _c, _d, _e;
|
|
132
502
|
const locale = dto.locale || 'en';
|
|
133
503
|
const isActive = (_a = dto.is_active) !== null && _a !== void 0 ? _a : true;
|
|
504
|
+
const existing = await this.findBySlug(dto.slug);
|
|
505
|
+
if (existing) {
|
|
506
|
+
throw new common_1.ConflictException(`AI instruction "${dto.slug}" already exists.`);
|
|
507
|
+
}
|
|
508
|
+
this.validateContent(dto.slug, dto.content, {
|
|
509
|
+
fallbackSchema: (_b = dto.variables_schema) !== null && _b !== void 0 ? _b : null,
|
|
510
|
+
allowMissingVariables: dto.allow_missing_variables,
|
|
511
|
+
});
|
|
134
512
|
await this.prisma.$executeRaw `
|
|
135
|
-
INSERT INTO ai_instruction (slug, layer, name, content, variables_schema, locale, module_slug, is_active, version)
|
|
513
|
+
INSERT INTO ai_instruction (slug, layer, name, content, variables_schema, locale, module_slug, is_active, version, model, temperature, max_tokens)
|
|
136
514
|
VALUES (
|
|
137
515
|
${dto.slug},
|
|
138
516
|
CAST(${dto.layer} AS ai_instruction_layer_enum),
|
|
@@ -142,23 +520,50 @@ let AiInstructionService = AiInstructionService_1 = class AiInstructionService {
|
|
|
142
520
|
${locale},
|
|
143
521
|
${dto.module_slug || null},
|
|
144
522
|
${isActive},
|
|
145
|
-
1
|
|
523
|
+
1,
|
|
524
|
+
${(_c = dto.model) !== null && _c !== void 0 ? _c : null},
|
|
525
|
+
${(_d = dto.temperature) !== null && _d !== void 0 ? _d : null},
|
|
526
|
+
${(_e = dto.max_tokens) !== null && _e !== void 0 ? _e : null}
|
|
146
527
|
)
|
|
147
528
|
`;
|
|
148
|
-
const created = await this.
|
|
529
|
+
const created = await this.findBySlug(dto.slug);
|
|
149
530
|
// Record version 1
|
|
150
531
|
if (created) {
|
|
151
532
|
await this.saveVersion(created.id, 1, dto.content, 'Initial version', createdById);
|
|
152
533
|
}
|
|
534
|
+
// Negative cache entries for this slug must go, otherwise the prompt stays
|
|
535
|
+
// invisible to the resolver until the TTL expires. Kept separate from the
|
|
536
|
+
// publish below so a Redis outage never leaves this pod itself stale.
|
|
537
|
+
this.invalidateCache(dto.slug);
|
|
538
|
+
await this.publishInvalidation([dto.slug]);
|
|
153
539
|
return created;
|
|
154
540
|
}
|
|
155
541
|
async update(id, dto, updatedById) {
|
|
156
542
|
var _a, _b, _c, _d;
|
|
157
543
|
const current = await this.getById(id);
|
|
544
|
+
// Locked records are security-sensitive prompts (the essay-grading
|
|
545
|
+
// injection defence). Still editable, but never by accident.
|
|
546
|
+
if (current.is_locked && !dto.acknowledge_security_review) {
|
|
547
|
+
throw new common_1.ConflictException(`AI instruction "${current.slug}" is a security-sensitive prompt. ` +
|
|
548
|
+
'Send acknowledge_security_review=true to confirm you reviewed the change.');
|
|
549
|
+
}
|
|
158
550
|
const newContent = (_a = dto.content) !== null && _a !== void 0 ? _a : current.content;
|
|
159
551
|
const newVersion = dto.content && dto.content !== current.content
|
|
160
552
|
? current.version + 1
|
|
161
553
|
: current.version;
|
|
554
|
+
const warnings = dto.content !== undefined
|
|
555
|
+
? this.validateContent(current.slug, dto.content, {
|
|
556
|
+
fallbackSchema: current.variables_schema,
|
|
557
|
+
allowMissingVariables: dto.allow_missing_variables,
|
|
558
|
+
})
|
|
559
|
+
: [];
|
|
560
|
+
// Losing every guard phrase is recorded in the immutable version row, not
|
|
561
|
+
// just in the log — the resolver will also refuse the stored copy.
|
|
562
|
+
const changeNote = warnings.length
|
|
563
|
+
? [dto.change_note, `[warnings: ${warnings.join(' | ')}]`].filter(Boolean).join(' ').slice(0, 500)
|
|
564
|
+
: dto.change_note;
|
|
565
|
+
// `undefined` means "leave alone", `null` means "clear" — `??` collapses the
|
|
566
|
+
// two, so the model params use an explicit undefined check.
|
|
162
567
|
await this.prisma.$executeRaw `
|
|
163
568
|
UPDATE ai_instruction
|
|
164
569
|
SET
|
|
@@ -168,16 +573,20 @@ let AiInstructionService = AiInstructionService_1 = class AiInstructionService {
|
|
|
168
573
|
locale = ${(_c = dto.locale) !== null && _c !== void 0 ? _c : current.locale},
|
|
169
574
|
module_slug = ${dto.module_slug !== undefined ? dto.module_slug : current.module_slug},
|
|
170
575
|
is_active = ${(_d = dto.is_active) !== null && _d !== void 0 ? _d : current.is_active},
|
|
576
|
+
model = ${dto.model !== undefined ? dto.model : current.model},
|
|
577
|
+
temperature = ${dto.temperature !== undefined ? dto.temperature : current.temperature},
|
|
578
|
+
max_tokens = ${dto.max_tokens !== undefined ? dto.max_tokens : current.max_tokens},
|
|
171
579
|
version = ${newVersion},
|
|
172
580
|
updated_at = NOW()
|
|
173
581
|
WHERE id = ${id}
|
|
174
582
|
`;
|
|
175
583
|
// Save new version if content changed
|
|
176
584
|
if (dto.content && dto.content !== current.content) {
|
|
177
|
-
await this.saveVersion(id, newVersion, newContent,
|
|
585
|
+
await this.saveVersion(id, newVersion, newContent, changeNote, updatedById);
|
|
178
586
|
}
|
|
179
587
|
this.invalidateCache(current.slug);
|
|
180
|
-
|
|
588
|
+
await this.publishInvalidation([current.slug]);
|
|
589
|
+
return Object.assign(Object.assign({}, (await this.getById(id))), { warnings });
|
|
181
590
|
}
|
|
182
591
|
async delete({ ids }) {
|
|
183
592
|
const existing = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
@@ -191,6 +600,8 @@ let AiInstructionService = AiInstructionService_1 = class AiInstructionService {
|
|
|
191
600
|
DELETE FROM ai_instruction WHERE id IN (${api_prisma_1.Prisma.join(ids)})
|
|
192
601
|
`;
|
|
193
602
|
existing.forEach((r) => this.invalidateCache(r.slug));
|
|
603
|
+
// One bump and one message for the whole batch.
|
|
604
|
+
await this.publishInvalidation(existing.map((r) => r.slug));
|
|
194
605
|
return { count: ids.length };
|
|
195
606
|
}
|
|
196
607
|
async listVersions(id) {
|
|
@@ -223,48 +634,110 @@ let AiInstructionService = AiInstructionService_1 = class AiInstructionService {
|
|
|
223
634
|
`;
|
|
224
635
|
await this.saveVersion(id, newVersion, target.content, `Restored from version ${version}`, restoredById);
|
|
225
636
|
this.invalidateCache(current.slug);
|
|
637
|
+
await this.publishInvalidation([current.slug]);
|
|
226
638
|
return this.getById(id);
|
|
227
639
|
}
|
|
228
640
|
// ── Private helpers ───────────────────────────────────────────────────────
|
|
641
|
+
/**
|
|
642
|
+
* Rejects content that would break at render time, and returns non-fatal
|
|
643
|
+
* warnings for the caller to surface.
|
|
644
|
+
*
|
|
645
|
+
* An unclosed block is a hard 422: it silently swallows the rest of the
|
|
646
|
+
* prompt. A removed required variable is also a 422, overridable with
|
|
647
|
+
* `allow_missing_variables` — sometimes a rewrite genuinely no longer needs a
|
|
648
|
+
* variable. An unknown placeholder is only a warning: it renders literally,
|
|
649
|
+
* which is ugly but harmless.
|
|
650
|
+
*/
|
|
651
|
+
validateContent(slug, content, options) {
|
|
652
|
+
var _a, _b;
|
|
653
|
+
const definition = (0, ai_prompt_registry_1.getAiPromptDefinition)(slug);
|
|
654
|
+
const schema = (_b = (_a = definition === null || definition === void 0 ? void 0 : definition.variables) !== null && _a !== void 0 ? _a : options.fallbackSchema) !== null && _b !== void 0 ? _b : null;
|
|
655
|
+
const { missingRequired, unknownPlaceholders, unbalancedBlocks } = (0, ai_prompt_template_util_1.validateTemplate)(content, schema);
|
|
656
|
+
if (unbalancedBlocks.length) {
|
|
657
|
+
throw new common_1.UnprocessableEntityException(`Bloco de template não fechado: ${unbalancedBlocks
|
|
658
|
+
.map((name) => `{{#${name}}}`)
|
|
659
|
+
.join(', ')}. Isso descartaria o resto do prompt.`);
|
|
660
|
+
}
|
|
661
|
+
if (missingRequired.length && !options.allowMissingVariables) {
|
|
662
|
+
throw new common_1.UnprocessableEntityException(`Variáveis obrigatórias ausentes do conteúdo: ${missingRequired.join(', ')}. ` +
|
|
663
|
+
'Envie allow_missing_variables=true para salvar mesmo assim.');
|
|
664
|
+
}
|
|
665
|
+
const warnings = [];
|
|
666
|
+
if (missingRequired.length) {
|
|
667
|
+
warnings.push(`missing-variables: ${missingRequired.join(', ')}`);
|
|
668
|
+
}
|
|
669
|
+
if (unknownPlaceholders.length) {
|
|
670
|
+
warnings.push(`unknown-variables: ${unknownPlaceholders.join(', ')}`);
|
|
671
|
+
}
|
|
672
|
+
warnings.push(...this.missingPhraseWarnings(definition, content));
|
|
673
|
+
return warnings;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Guard phrases are the runtime protection for security-sensitive prompts: if
|
|
677
|
+
* the stored content contains none of them, `AiPromptResolverService` discards
|
|
678
|
+
* it. Warn loudly here so the editor learns it before the first graded essay.
|
|
679
|
+
*/
|
|
680
|
+
missingPhraseWarnings(definition, content) {
|
|
681
|
+
const phrases = definition === null || definition === void 0 ? void 0 : definition.requiredPhrases;
|
|
682
|
+
if (!(phrases === null || phrases === void 0 ? void 0 : phrases.length))
|
|
683
|
+
return [];
|
|
684
|
+
const missing = phrases.filter((phrase) => !content.includes(phrase));
|
|
685
|
+
if (!missing.length)
|
|
686
|
+
return [];
|
|
687
|
+
const hasAny = missing.length < phrases.length;
|
|
688
|
+
const warning = `security-phrases-removed: ${missing.join(', ')}`;
|
|
689
|
+
if (hasAny) {
|
|
690
|
+
this.logger.warn(`AI prompt "${definition.slug}" lost safety phrases: ${missing.join(', ')}`);
|
|
691
|
+
return [warning];
|
|
692
|
+
}
|
|
693
|
+
this.logger.error(`AI prompt "${definition.slug}" lost ALL of its safety phrases; the resolver ` +
|
|
694
|
+
'will ignore the stored content and use the code fallback.');
|
|
695
|
+
return [`${warning} (todas — o prompt do banco será ignorado em runtime)`];
|
|
696
|
+
}
|
|
229
697
|
async findById(id) {
|
|
230
|
-
const rows = await this.prisma.$queryRaw `
|
|
231
|
-
SELECT
|
|
232
|
-
is_active, version, created_at, updated_at
|
|
698
|
+
const rows = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
699
|
+
SELECT ${INSTRUCTION_COLUMNS}
|
|
233
700
|
FROM ai_instruction WHERE id = ${id} LIMIT 1
|
|
234
|
-
|
|
701
|
+
`);
|
|
702
|
+
return (rows === null || rows === void 0 ? void 0 : rows[0]) || null;
|
|
703
|
+
}
|
|
704
|
+
async findBySlug(slug) {
|
|
705
|
+
const rows = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
706
|
+
SELECT ${INSTRUCTION_COLUMNS}
|
|
707
|
+
FROM ai_instruction WHERE slug = ${slug} LIMIT 1
|
|
708
|
+
`);
|
|
235
709
|
return (rows === null || rows === void 0 ? void 0 : rows[0]) || null;
|
|
236
710
|
}
|
|
237
711
|
async findActiveBySlug(slug) {
|
|
238
|
-
const rows = await this.prisma.$queryRaw `
|
|
239
|
-
SELECT
|
|
240
|
-
is_active, version, created_at, updated_at
|
|
712
|
+
const rows = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
713
|
+
SELECT ${INSTRUCTION_COLUMNS}
|
|
241
714
|
FROM ai_instruction WHERE slug = ${slug} AND is_active = TRUE LIMIT 1
|
|
242
|
-
|
|
715
|
+
`);
|
|
243
716
|
return (rows === null || rows === void 0 ? void 0 : rows[0]) || null;
|
|
244
717
|
}
|
|
245
718
|
async findActiveByLayer(layer, moduleSlug) {
|
|
246
719
|
if (moduleSlug) {
|
|
247
|
-
const rows = await this.prisma.$queryRaw `
|
|
248
|
-
SELECT
|
|
249
|
-
is_active, version, created_at, updated_at
|
|
720
|
+
const rows = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
721
|
+
SELECT ${INSTRUCTION_COLUMNS}
|
|
250
722
|
FROM ai_instruction
|
|
251
723
|
WHERE layer = CAST(${layer} AS ai_instruction_layer_enum)
|
|
252
724
|
AND module_slug = ${moduleSlug}
|
|
253
725
|
AND is_active = TRUE
|
|
726
|
+
ORDER BY id
|
|
254
727
|
LIMIT 1
|
|
255
|
-
|
|
728
|
+
`);
|
|
256
729
|
if (rows === null || rows === void 0 ? void 0 : rows[0])
|
|
257
730
|
return rows[0];
|
|
258
731
|
}
|
|
259
|
-
const rows = await this.prisma.$queryRaw `
|
|
260
|
-
SELECT
|
|
261
|
-
is_active, version, created_at, updated_at
|
|
732
|
+
const rows = await this.prisma.$queryRaw(api_prisma_1.Prisma.sql `
|
|
733
|
+
SELECT ${INSTRUCTION_COLUMNS}
|
|
262
734
|
FROM ai_instruction
|
|
263
735
|
WHERE layer = CAST(${layer} AS ai_instruction_layer_enum)
|
|
264
736
|
AND module_slug IS NULL
|
|
265
737
|
AND is_active = TRUE
|
|
738
|
+
ORDER BY id
|
|
266
739
|
LIMIT 1
|
|
267
|
-
|
|
740
|
+
`);
|
|
268
741
|
return (rows === null || rows === void 0 ? void 0 : rows[0]) || null;
|
|
269
742
|
}
|
|
270
743
|
async saveVersion(instructionId, version, content, changeNote, createdById) {
|