@useatlas/create 0.0.1
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 +231 -0
- package/index.ts +829 -0
- package/package.json +38 -0
- package/templates/docker/.env.example +67 -0
- package/templates/docker/Dockerfile +52 -0
- package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/docker/bin/__tests__/eval.test.ts +434 -0
- package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/docker/bin/__tests__/query.test.ts +240 -0
- package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/docker/bin/atlas.ts +5044 -0
- package/templates/docker/bin/benchmark.ts +695 -0
- package/templates/docker/bin/enrich.ts +559 -0
- package/templates/docker/bin/eval.ts +770 -0
- package/templates/docker/bin/smoke.ts +438 -0
- package/templates/docker/data/.gitkeep +0 -0
- package/templates/docker/data/cybersec.sql +1961 -0
- package/templates/docker/data/demo-semantic/catalog.yml +40 -0
- package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
- package/templates/docker/data/demo-semantic/glossary.yml +22 -0
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/docker/data/demo.sql +373 -0
- package/templates/docker/data/ecommerce.sql +1690 -0
- package/templates/docker/data/init-demo-db.sql +8 -0
- package/templates/docker/docker-compose.yml +34 -0
- package/templates/docker/docs/deploy.md +390 -0
- package/templates/docker/eslint.config.mjs +18 -0
- package/templates/docker/gitignore +5 -0
- package/templates/docker/next.config.ts +9 -0
- package/templates/docker/package.json +59 -0
- package/templates/docker/postcss.config.mjs +8 -0
- package/templates/docker/public/.gitkeep +0 -0
- package/templates/docker/public/favicon.svg +4 -0
- package/templates/docker/railway.json +13 -0
- package/templates/docker/render.yaml +34 -0
- package/templates/docker/semantic/catalog.yml +5 -0
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/glossary.yml +6 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/sidecar/Dockerfile +28 -0
- package/templates/docker/sidecar/railway.json +14 -0
- package/templates/docker/sidecar/server.ts +188 -0
- package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
- package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
- package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
- package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
- package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/docker/src/api/__tests__/health.test.ts +261 -0
- package/templates/docker/src/api/__tests__/query.test.ts +891 -0
- package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
- package/templates/docker/src/api/index.ts +117 -0
- package/templates/docker/src/api/routes/actions.ts +274 -0
- package/templates/docker/src/api/routes/admin.ts +757 -0
- package/templates/docker/src/api/routes/auth.ts +48 -0
- package/templates/docker/src/api/routes/chat.ts +465 -0
- package/templates/docker/src/api/routes/conversations.ts +266 -0
- package/templates/docker/src/api/routes/health.ts +287 -0
- package/templates/docker/src/api/routes/openapi.ts +390 -0
- package/templates/docker/src/api/routes/query.ts +318 -0
- package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/docker/src/api/routes/slack.ts +611 -0
- package/templates/docker/src/api/server.ts +226 -0
- package/templates/docker/src/app/api/[...route]/route.ts +33 -0
- package/templates/docker/src/app/error.tsx +24 -0
- package/templates/docker/src/app/globals.css +126 -0
- package/templates/docker/src/app/layout.tsx +19 -0
- package/templates/docker/src/app/page.tsx +14 -0
- package/templates/docker/src/global.d.ts +1 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/docker/src/lib/action-types.ts +95 -0
- package/templates/docker/src/lib/agent-query.ts +178 -0
- package/templates/docker/src/lib/agent.ts +505 -0
- package/templates/docker/src/lib/api-url.ts +2 -0
- package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/docker/src/lib/auth/audit.ts +89 -0
- package/templates/docker/src/lib/auth/byot.ts +158 -0
- package/templates/docker/src/lib/auth/client.ts +35 -0
- package/templates/docker/src/lib/auth/detect.ts +83 -0
- package/templates/docker/src/lib/auth/managed.ts +73 -0
- package/templates/docker/src/lib/auth/middleware.ts +208 -0
- package/templates/docker/src/lib/auth/migrate.ts +111 -0
- package/templates/docker/src/lib/auth/permissions.ts +156 -0
- package/templates/docker/src/lib/auth/server.ts +142 -0
- package/templates/docker/src/lib/auth/simple-key.ts +92 -0
- package/templates/docker/src/lib/auth/types.ts +49 -0
- package/templates/docker/src/lib/config.ts +704 -0
- package/templates/docker/src/lib/conversation-types.ts +29 -0
- package/templates/docker/src/lib/conversations.ts +270 -0
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/docker/src/lib/db/connection.ts +753 -0
- package/templates/docker/src/lib/db/duckdb.ts +122 -0
- package/templates/docker/src/lib/db/internal.ts +273 -0
- package/templates/docker/src/lib/db/salesforce.ts +342 -0
- package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/docker/src/lib/errors.ts +154 -0
- package/templates/docker/src/lib/logger.ts +98 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/docker/src/lib/plugins/hooks.ts +162 -0
- package/templates/docker/src/lib/plugins/index.ts +9 -0
- package/templates/docker/src/lib/plugins/migrate.ts +309 -0
- package/templates/docker/src/lib/plugins/registry.ts +231 -0
- package/templates/docker/src/lib/plugins/tools.ts +39 -0
- package/templates/docker/src/lib/plugins/wiring.ts +291 -0
- package/templates/docker/src/lib/providers.ts +102 -0
- package/templates/docker/src/lib/rls.ts +321 -0
- package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
- package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
- package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
- package/templates/docker/src/lib/scheduler/engine.ts +317 -0
- package/templates/docker/src/lib/scheduler/executor.ts +73 -0
- package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
- package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/docker/src/lib/scheduler/index.ts +7 -0
- package/templates/docker/src/lib/security.ts +11 -0
- package/templates/docker/src/lib/semantic-index.ts +503 -0
- package/templates/docker/src/lib/semantic.ts +387 -0
- package/templates/docker/src/lib/sidecar-types.ts +16 -0
- package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/docker/src/lib/slack/api.ts +102 -0
- package/templates/docker/src/lib/slack/format.ts +209 -0
- package/templates/docker/src/lib/slack/store.ts +107 -0
- package/templates/docker/src/lib/slack/threads.ts +64 -0
- package/templates/docker/src/lib/slack/verify.ts +71 -0
- package/templates/docker/src/lib/startup.ts +730 -0
- package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
- package/templates/docker/src/lib/tools/actions/email.ts +191 -0
- package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
- package/templates/docker/src/lib/tools/actions/index.ts +23 -0
- package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
- package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/docker/src/lib/tools/explore.ts +379 -0
- package/templates/docker/src/lib/tools/registry.ts +221 -0
- package/templates/docker/src/lib/tools/salesforce.ts +138 -0
- package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
- package/templates/docker/src/lib/tools/sql.ts +680 -0
- package/templates/docker/src/lib/tracing.ts +40 -0
- package/templates/docker/src/lib/utils.ts +6 -0
- package/templates/docker/src/test-setup.ts +38 -0
- package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
- package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/docker/src/ui/context.tsx +78 -0
- package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/docker/src/ui/lib/action-types.ts +63 -0
- package/templates/docker/src/ui/lib/helpers.ts +104 -0
- package/templates/docker/src/ui/lib/types.ts +145 -0
- package/templates/docker/tsconfig.json +41 -0
- package/templates/docker/vercel.json +3 -0
- package/templates/nextjs-standalone/.env.example +68 -0
- package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
- package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
- package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
- package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
- package/templates/nextjs-standalone/bin/enrich.ts +559 -0
- package/templates/nextjs-standalone/bin/eval.ts +770 -0
- package/templates/nextjs-standalone/bin/smoke.ts +438 -0
- package/templates/nextjs-standalone/data/.gitkeep +0 -0
- package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/nextjs-standalone/data/demo.sql +373 -0
- package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
- package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
- package/templates/nextjs-standalone/docs/deploy.md +390 -0
- package/templates/nextjs-standalone/eslint.config.mjs +18 -0
- package/templates/nextjs-standalone/gitignore +5 -0
- package/templates/nextjs-standalone/next.config.ts +10 -0
- package/templates/nextjs-standalone/package.json +63 -0
- package/templates/nextjs-standalone/postcss.config.mjs +8 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
- package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
- package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
- package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
- package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
- package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
- package/templates/nextjs-standalone/src/api/index.ts +117 -0
- package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
- package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
- package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
- package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
- package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
- package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
- package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
- package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
- package/templates/nextjs-standalone/src/api/server.ts +226 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
- package/templates/nextjs-standalone/src/app/error.tsx +24 -0
- package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
- package/templates/nextjs-standalone/src/app/globals.css +126 -0
- package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
- package/templates/nextjs-standalone/src/app/page.tsx +14 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
- package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
- package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
- package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
- package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
- package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
- package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
- package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
- package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
- package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
- package/templates/nextjs-standalone/src/lib/config.ts +704 -0
- package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
- package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
- package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
- package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
- package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
- package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
- package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
- package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
- package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
- package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
- package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
- package/templates/nextjs-standalone/src/lib/security.ts +11 -0
- package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
- package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
- package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
- package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
- package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
- package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
- package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
- package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
- package/templates/nextjs-standalone/src/test-setup.ts +38 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
- package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
- package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
- package/templates/nextjs-standalone/tsconfig.json +32 -0
- package/templates/nextjs-standalone/vercel.json +4 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery dispatcher — routes scheduled task results to the configured channel.
|
|
3
|
+
*
|
|
4
|
+
* Switches on task.deliveryChannel and dispatches to the appropriate formatter
|
|
5
|
+
* and transport (email, Slack, webhook). Returns a delivery summary.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createLogger } from "@atlas/api/lib/logger";
|
|
9
|
+
import type { ScheduledTask } from "@atlas/api/lib/scheduled-tasks";
|
|
10
|
+
import type { AgentQueryResult } from "@atlas/api/lib/agent-query";
|
|
11
|
+
import type { EmailRecipient, SlackRecipient, WebhookRecipient } from "@atlas/api/lib/scheduled-task-types";
|
|
12
|
+
import { formatEmailReport } from "./format-email";
|
|
13
|
+
import { formatSlackReport } from "./format-slack";
|
|
14
|
+
import { formatWebhookPayload } from "./format-webhook";
|
|
15
|
+
|
|
16
|
+
const log = createLogger("scheduler-delivery");
|
|
17
|
+
|
|
18
|
+
export interface DeliverySummary {
|
|
19
|
+
attempted: number;
|
|
20
|
+
succeeded: number;
|
|
21
|
+
failed: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const EMPTY_SUMMARY: DeliverySummary = { attempted: 0, succeeded: 0, failed: 0 };
|
|
25
|
+
|
|
26
|
+
/** RFC 5735 / RFC 4193 private and reserved IP ranges. */
|
|
27
|
+
const BLOCKED_HOST_PATTERNS = [
|
|
28
|
+
/^localhost$/i,
|
|
29
|
+
/^127\./,
|
|
30
|
+
/^10\./,
|
|
31
|
+
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
32
|
+
/^192\.168\./,
|
|
33
|
+
/^169\.254\./,
|
|
34
|
+
/^0\./,
|
|
35
|
+
/^\[::1\]$/,
|
|
36
|
+
/^\[fd/i,
|
|
37
|
+
/^\[fe80:/i,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const BLOCKED_HEADER_NAMES = new Set([
|
|
41
|
+
"authorization",
|
|
42
|
+
"cookie",
|
|
43
|
+
"host",
|
|
44
|
+
"x-forwarded-for",
|
|
45
|
+
"x-real-ip",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
/** Returns true if the URL targets a private/internal address. */
|
|
49
|
+
function isBlockedUrl(urlString: string): boolean {
|
|
50
|
+
try {
|
|
51
|
+
const parsed = new URL(urlString);
|
|
52
|
+
// Block non-HTTPS in production
|
|
53
|
+
if (process.env.NODE_ENV === "production" && parsed.protocol !== "https:") {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const hostname = parsed.hostname;
|
|
57
|
+
return BLOCKED_HOST_PATTERNS.some((pattern) => pattern.test(hostname));
|
|
58
|
+
} catch {
|
|
59
|
+
return true; // Unparseable URLs are blocked
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Filter out sensitive header names from user-supplied headers. */
|
|
64
|
+
function sanitizeHeaders(headers: Record<string, string>): Record<string, string> {
|
|
65
|
+
const safe: Record<string, string> = {};
|
|
66
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
67
|
+
if (!BLOCKED_HEADER_NAMES.has(key.toLowerCase())) {
|
|
68
|
+
safe[key] = value;
|
|
69
|
+
} else {
|
|
70
|
+
log.warn({ header: key }, "Blocked sensitive header in webhook recipient");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return safe;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deliver agent results to the task's configured channel and recipients.
|
|
78
|
+
* Returns a delivery summary with attempted/succeeded/failed counts.
|
|
79
|
+
*/
|
|
80
|
+
export async function deliverResult(
|
|
81
|
+
task: ScheduledTask,
|
|
82
|
+
result: AgentQueryResult,
|
|
83
|
+
): Promise<DeliverySummary> {
|
|
84
|
+
if (task.recipients.length === 0) {
|
|
85
|
+
log.debug({ taskId: task.id }, "No recipients configured — skipping delivery");
|
|
86
|
+
return EMPTY_SUMMARY;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
switch (task.deliveryChannel) {
|
|
90
|
+
case "email":
|
|
91
|
+
return deliverEmail(task, result);
|
|
92
|
+
case "slack":
|
|
93
|
+
return deliverSlack(task, result);
|
|
94
|
+
case "webhook":
|
|
95
|
+
return deliverWebhook(task, result);
|
|
96
|
+
default:
|
|
97
|
+
log.warn({ taskId: task.id, channel: task.deliveryChannel }, "Unknown delivery channel");
|
|
98
|
+
return EMPTY_SUMMARY;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function deliverEmail(task: ScheduledTask, result: AgentQueryResult): Promise<DeliverySummary> {
|
|
103
|
+
const emailRecipients = task.recipients.filter(
|
|
104
|
+
(r): r is EmailRecipient => r.type === "email",
|
|
105
|
+
);
|
|
106
|
+
if (emailRecipients.length === 0) return EMPTY_SUMMARY;
|
|
107
|
+
|
|
108
|
+
const { subject, body } = formatEmailReport(task, result);
|
|
109
|
+
|
|
110
|
+
const resendKey = process.env.RESEND_API_KEY;
|
|
111
|
+
if (!resendKey) {
|
|
112
|
+
log.warn({ taskId: task.id, count: emailRecipients.length }, "RESEND_API_KEY not set — email delivery skipped");
|
|
113
|
+
return { attempted: emailRecipients.length, succeeded: 0, failed: emailRecipients.length };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const fromAddress = process.env.ATLAS_EMAIL_FROM ?? "Atlas <noreply@useatlas.dev>";
|
|
117
|
+
let succeeded = 0;
|
|
118
|
+
let failed = 0;
|
|
119
|
+
|
|
120
|
+
for (const recipient of emailRecipients) {
|
|
121
|
+
try {
|
|
122
|
+
const resp = await fetch("https://api.resend.com/emails", {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
Authorization: `Bearer ${resendKey}`,
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
from: fromAddress,
|
|
130
|
+
to: [recipient.address],
|
|
131
|
+
subject,
|
|
132
|
+
html: body,
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!resp.ok) {
|
|
137
|
+
const text = await resp.text().catch(() => "");
|
|
138
|
+
log.error({ taskId: task.id, recipient: recipient.address, status: resp.status, body: text.slice(0, 200) }, "Email delivery failed");
|
|
139
|
+
failed++;
|
|
140
|
+
} else {
|
|
141
|
+
log.info({ taskId: task.id, recipient: recipient.address }, "Email delivered");
|
|
142
|
+
succeeded++;
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
log.error({ taskId: task.id, recipient: recipient.address, err: err instanceof Error ? err.message : String(err) }, "Email delivery error");
|
|
146
|
+
failed++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { attempted: emailRecipients.length, succeeded, failed };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function deliverSlack(task: ScheduledTask, result: AgentQueryResult): Promise<DeliverySummary> {
|
|
154
|
+
const slackRecipients = task.recipients.filter(
|
|
155
|
+
(r): r is SlackRecipient => r.type === "slack",
|
|
156
|
+
);
|
|
157
|
+
if (slackRecipients.length === 0) return EMPTY_SUMMARY;
|
|
158
|
+
|
|
159
|
+
const { text, blocks } = formatSlackReport(task, result);
|
|
160
|
+
let succeeded = 0;
|
|
161
|
+
let failed = 0;
|
|
162
|
+
|
|
163
|
+
for (const recipient of slackRecipients) {
|
|
164
|
+
try {
|
|
165
|
+
let token: string | null = null;
|
|
166
|
+
|
|
167
|
+
if (recipient.teamId) {
|
|
168
|
+
const { getBotToken } = await import("@atlas/api/lib/slack/store");
|
|
169
|
+
token = await getBotToken(recipient.teamId);
|
|
170
|
+
}
|
|
171
|
+
if (!token) {
|
|
172
|
+
token = process.env.SLACK_BOT_TOKEN ?? null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!token) {
|
|
176
|
+
log.warn({ taskId: task.id, channel: recipient.channel }, "No Slack bot token available — delivery skipped");
|
|
177
|
+
failed++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { postMessage } = await import("@atlas/api/lib/slack/api");
|
|
182
|
+
const resp = await postMessage(token, {
|
|
183
|
+
channel: recipient.channel,
|
|
184
|
+
text,
|
|
185
|
+
blocks,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (!resp.ok) {
|
|
189
|
+
log.error({ taskId: task.id, channel: recipient.channel, error: resp.error }, "Slack delivery failed");
|
|
190
|
+
failed++;
|
|
191
|
+
} else {
|
|
192
|
+
log.info({ taskId: task.id, channel: recipient.channel }, "Slack message delivered");
|
|
193
|
+
succeeded++;
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
log.error({ taskId: task.id, channel: recipient.channel, err: err instanceof Error ? err.message : String(err) }, "Slack delivery error");
|
|
197
|
+
failed++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { attempted: slackRecipients.length, succeeded, failed };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function deliverWebhook(task: ScheduledTask, result: AgentQueryResult): Promise<DeliverySummary> {
|
|
205
|
+
const webhookRecipients = task.recipients.filter(
|
|
206
|
+
(r): r is WebhookRecipient => r.type === "webhook",
|
|
207
|
+
);
|
|
208
|
+
if (webhookRecipients.length === 0) return EMPTY_SUMMARY;
|
|
209
|
+
|
|
210
|
+
const payload = formatWebhookPayload(task, result);
|
|
211
|
+
let succeeded = 0;
|
|
212
|
+
let failed = 0;
|
|
213
|
+
|
|
214
|
+
for (const recipient of webhookRecipients) {
|
|
215
|
+
if (isBlockedUrl(recipient.url)) {
|
|
216
|
+
log.error({ taskId: task.id, url: recipient.url }, "Webhook URL blocked — targets private/internal address");
|
|
217
|
+
failed++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const safeHeaders = sanitizeHeaders(recipient.headers ?? {});
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const resp = await fetch(recipient.url, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: {
|
|
227
|
+
...safeHeaders,
|
|
228
|
+
"Content-Type": "application/json",
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify(payload),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (!resp.ok) {
|
|
234
|
+
const text = await resp.text().catch(() => "");
|
|
235
|
+
log.error({ taskId: task.id, url: recipient.url, status: resp.status, body: text.slice(0, 200) }, "Webhook delivery failed");
|
|
236
|
+
failed++;
|
|
237
|
+
} else {
|
|
238
|
+
log.info({ taskId: task.id, url: recipient.url }, "Webhook delivered");
|
|
239
|
+
succeeded++;
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
log.error({ taskId: task.id, url: recipient.url, err: err instanceof Error ? err.message : String(err) }, "Webhook delivery error");
|
|
243
|
+
failed++;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { attempted: webhookRecipients.length, succeeded, failed };
|
|
248
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler engine — tick-based loop that finds due tasks and executes them.
|
|
3
|
+
*
|
|
4
|
+
* Factory function returns a Scheduler object with start/stop lifecycle.
|
|
5
|
+
* Singleton via getScheduler() / _resetScheduler().
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createLogger } from "@atlas/api/lib/logger";
|
|
9
|
+
import {
|
|
10
|
+
getTasksDueForExecution,
|
|
11
|
+
lockTaskForExecution,
|
|
12
|
+
createTaskRun,
|
|
13
|
+
completeTaskRun,
|
|
14
|
+
computeNextRun,
|
|
15
|
+
getScheduledTask,
|
|
16
|
+
} from "@atlas/api/lib/scheduled-tasks";
|
|
17
|
+
import { internalExecute } from "@atlas/api/lib/db/internal";
|
|
18
|
+
import { executeScheduledTask } from "./executor";
|
|
19
|
+
import { getConfig } from "@atlas/api/lib/config";
|
|
20
|
+
|
|
21
|
+
const log = createLogger("scheduler");
|
|
22
|
+
|
|
23
|
+
export interface Scheduler {
|
|
24
|
+
start(): void;
|
|
25
|
+
stop(): void;
|
|
26
|
+
isRunning(): boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Shared helpers (used by both createScheduler and runTick)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
async function rescheduleTask(taskId: string) {
|
|
34
|
+
try {
|
|
35
|
+
const taskResult = await getScheduledTask(taskId);
|
|
36
|
+
if (taskResult.ok) {
|
|
37
|
+
const nextRun = computeNextRun(taskResult.data.cronExpression);
|
|
38
|
+
if (nextRun) {
|
|
39
|
+
internalExecute(
|
|
40
|
+
`UPDATE scheduled_tasks SET next_run_at = $1 WHERE id = $2`,
|
|
41
|
+
[nextRun.toISOString(), taskId],
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
log.error({ taskId, err: err instanceof Error ? err.message : String(err) }, "Failed to reschedule task");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createScheduler(): Scheduler {
|
|
51
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
52
|
+
let running = false;
|
|
53
|
+
let activeTasks = 0;
|
|
54
|
+
|
|
55
|
+
function getSchedulerConfig() {
|
|
56
|
+
const config = getConfig();
|
|
57
|
+
return {
|
|
58
|
+
maxConcurrentTasks: config?.scheduler?.maxConcurrentTasks ?? 5,
|
|
59
|
+
taskTimeout: config?.scheduler?.taskTimeout ?? 60_000,
|
|
60
|
+
tickIntervalSeconds: config?.scheduler?.tickIntervalSeconds ?? 60,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function tick() {
|
|
65
|
+
const cfg = getSchedulerConfig();
|
|
66
|
+
|
|
67
|
+
if (activeTasks >= cfg.maxConcurrentTasks) {
|
|
68
|
+
log.debug({ activeTasks, max: cfg.maxConcurrentTasks }, "Scheduler tick skipped — at capacity");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let dueTasks;
|
|
73
|
+
try {
|
|
74
|
+
dueTasks = await getTasksDueForExecution();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
log.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to fetch due tasks");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (dueTasks.length === 0) return;
|
|
81
|
+
|
|
82
|
+
log.info({ count: dueTasks.length }, "Scheduler tick — found due tasks");
|
|
83
|
+
|
|
84
|
+
const slotsAvailable = cfg.maxConcurrentTasks - activeTasks;
|
|
85
|
+
const tasksToRun = dueTasks.slice(0, slotsAvailable);
|
|
86
|
+
|
|
87
|
+
for (const task of tasksToRun) {
|
|
88
|
+
try {
|
|
89
|
+
const locked = await lockTaskForExecution(task.id);
|
|
90
|
+
if (!locked) {
|
|
91
|
+
log.debug({ taskId: task.id }, "Task already locked by another process");
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
activeTasks++;
|
|
96
|
+
executeAndDeliver(task.id, cfg.taskTimeout).finally(() => {
|
|
97
|
+
activeTasks--;
|
|
98
|
+
});
|
|
99
|
+
} catch (err) {
|
|
100
|
+
log.error({ taskId: task.id, err: err instanceof Error ? err.message : String(err) }, "Failed to lock/dispatch task");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function executeAndDeliver(taskId: string, timeoutMs: number) {
|
|
106
|
+
const runId = await createTaskRun(taskId);
|
|
107
|
+
if (!runId) {
|
|
108
|
+
log.error({ taskId }, "Failed to create run record — attempting to reschedule");
|
|
109
|
+
await rescheduleTask(taskId);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const result = await executeScheduledTask(taskId, runId, timeoutMs);
|
|
115
|
+
completeTaskRun(runId, "success", { tokensUsed: result.tokensUsed });
|
|
116
|
+
log.info({ taskId, runId, tokensUsed: result.tokensUsed }, "Scheduled task completed successfully");
|
|
117
|
+
} catch (err) {
|
|
118
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
119
|
+
completeTaskRun(runId, "failed", { error: message });
|
|
120
|
+
log.error({ taskId, runId, err: message }, "Scheduled task failed");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
start() {
|
|
126
|
+
if (running) return;
|
|
127
|
+
running = true;
|
|
128
|
+
|
|
129
|
+
const cfg = getSchedulerConfig();
|
|
130
|
+
log.info(
|
|
131
|
+
{ intervalSeconds: cfg.tickIntervalSeconds, maxConcurrent: cfg.maxConcurrentTasks },
|
|
132
|
+
"Scheduler starting",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// First tick fires immediately
|
|
136
|
+
void tick().catch((err) => {
|
|
137
|
+
log.error({ err: err instanceof Error ? err.message : String(err) }, "Scheduler tick crashed");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
timer = setInterval(() => {
|
|
141
|
+
void tick().catch((err) => {
|
|
142
|
+
log.error({ err: err instanceof Error ? err.message : String(err) }, "Scheduler tick crashed");
|
|
143
|
+
});
|
|
144
|
+
}, cfg.tickIntervalSeconds * 1000);
|
|
145
|
+
timer.unref();
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
stop() {
|
|
149
|
+
if (!running) return;
|
|
150
|
+
running = false;
|
|
151
|
+
if (timer) {
|
|
152
|
+
clearInterval(timer);
|
|
153
|
+
timer = null;
|
|
154
|
+
}
|
|
155
|
+
log.info("Scheduler stopped");
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
isRunning() {
|
|
159
|
+
return running;
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Singleton
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
let _scheduler: Scheduler | null = null;
|
|
169
|
+
|
|
170
|
+
export function getScheduler(): Scheduler {
|
|
171
|
+
if (!_scheduler) {
|
|
172
|
+
_scheduler = createScheduler();
|
|
173
|
+
}
|
|
174
|
+
return _scheduler;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Trigger a single task immediately (used by POST /:id/run route). */
|
|
178
|
+
export async function triggerTask(taskId: string): Promise<void> {
|
|
179
|
+
const config = getConfig();
|
|
180
|
+
const timeoutMs = config?.scheduler?.taskTimeout ?? 60_000;
|
|
181
|
+
|
|
182
|
+
const locked = await lockTaskForExecution(taskId);
|
|
183
|
+
if (!locked) {
|
|
184
|
+
throw new Error("Failed to lock task for execution — task may be disabled or already running");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const runId = await createTaskRun(taskId);
|
|
188
|
+
if (!runId) {
|
|
189
|
+
throw new Error("Failed to create run record");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const result = await executeScheduledTask(taskId, runId, timeoutMs);
|
|
194
|
+
completeTaskRun(runId, "success", { tokensUsed: result.tokensUsed });
|
|
195
|
+
} catch (err) {
|
|
196
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
197
|
+
completeTaskRun(runId, "failed", { error: message });
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// runTick() — awaitable tick for serverless (Vercel Cron)
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Summary of a single scheduler tick.
|
|
208
|
+
*
|
|
209
|
+
* Invariants:
|
|
210
|
+
* - All fields are non-negative integers.
|
|
211
|
+
* - tasksDispatched <= tasksFound (capped by maxConcurrentTasks, reduced by lock contention).
|
|
212
|
+
* - tasksCompleted + tasksFailed === tasksDispatched (every dispatched task settles).
|
|
213
|
+
*/
|
|
214
|
+
export interface TickResult {
|
|
215
|
+
tasksFound: number;
|
|
216
|
+
tasksDispatched: number;
|
|
217
|
+
tasksCompleted: number;
|
|
218
|
+
tasksFailed: number;
|
|
219
|
+
/** Non-null when the tick itself failed (e.g. DB unreachable). */
|
|
220
|
+
error?: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Run a single scheduler tick that **awaits** all task executions.
|
|
225
|
+
*
|
|
226
|
+
* Unlike the in-process `tick()` (fire-and-forget inside `setInterval`),
|
|
227
|
+
* this function returns only after every dispatched task has settled —
|
|
228
|
+
* required for serverless environments where the function cannot exit early.
|
|
229
|
+
*
|
|
230
|
+
* When the due-task query fails, the error is surfaced in `result.error`
|
|
231
|
+
* so the caller can return an appropriate HTTP status (not a silent 200).
|
|
232
|
+
*/
|
|
233
|
+
export async function runTick(): Promise<TickResult> {
|
|
234
|
+
const config = getConfig();
|
|
235
|
+
const maxConcurrent = config?.scheduler?.maxConcurrentTasks ?? 5;
|
|
236
|
+
const taskTimeout = config?.scheduler?.taskTimeout ?? 60_000;
|
|
237
|
+
|
|
238
|
+
let dueTasks;
|
|
239
|
+
try {
|
|
240
|
+
dueTasks = await getTasksDueForExecution();
|
|
241
|
+
} catch (err) {
|
|
242
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
243
|
+
log.error({ err: message }, "runTick: failed to fetch due tasks");
|
|
244
|
+
return { tasksFound: 0, tasksDispatched: 0, tasksCompleted: 0, tasksFailed: 0, error: message };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (dueTasks.length === 0) {
|
|
248
|
+
return { tasksFound: 0, tasksDispatched: 0, tasksCompleted: 0, tasksFailed: 0 };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const tasksToRun = dueTasks.slice(0, maxConcurrent);
|
|
252
|
+
type TaskOutcome = "completed" | "failed";
|
|
253
|
+
const promises: Promise<TaskOutcome>[] = [];
|
|
254
|
+
|
|
255
|
+
let tasksDispatched = 0;
|
|
256
|
+
|
|
257
|
+
for (const task of tasksToRun) {
|
|
258
|
+
let locked: boolean;
|
|
259
|
+
try {
|
|
260
|
+
locked = await lockTaskForExecution(task.id);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
log.error({ taskId: task.id, err: err instanceof Error ? err.message : String(err) }, "runTick: lock error");
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (!locked) {
|
|
266
|
+
log.debug({ taskId: task.id }, "runTick: task already locked");
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
tasksDispatched++;
|
|
271
|
+
|
|
272
|
+
promises.push(
|
|
273
|
+
(async (): Promise<TaskOutcome> => {
|
|
274
|
+
const runId = await createTaskRun(task.id);
|
|
275
|
+
if (!runId) {
|
|
276
|
+
log.error({ taskId: task.id }, "runTick: failed to create run record — rescheduling");
|
|
277
|
+
await rescheduleTask(task.id);
|
|
278
|
+
return "failed";
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const execResult = await executeScheduledTask(task.id, runId, taskTimeout);
|
|
282
|
+
completeTaskRun(runId, "success", { tokensUsed: execResult.tokensUsed });
|
|
283
|
+
log.info({ taskId: task.id, runId }, "runTick: task completed");
|
|
284
|
+
return "completed";
|
|
285
|
+
} catch (err) {
|
|
286
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
287
|
+
completeTaskRun(runId, "failed", { error: message });
|
|
288
|
+
log.error({ taskId: task.id, runId, err: message }, "runTick: task failed");
|
|
289
|
+
return "failed";
|
|
290
|
+
}
|
|
291
|
+
})(),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const outcomes = await Promise.allSettled(promises);
|
|
296
|
+
let tasksCompleted = 0;
|
|
297
|
+
let tasksFailed = 0;
|
|
298
|
+
for (const o of outcomes) {
|
|
299
|
+
if (o.status === "fulfilled" && o.value === "completed") tasksCompleted++;
|
|
300
|
+
else tasksFailed++;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
tasksFound: dueTasks.length,
|
|
305
|
+
tasksDispatched,
|
|
306
|
+
tasksCompleted,
|
|
307
|
+
tasksFailed,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Reset singleton for testing. */
|
|
312
|
+
export function _resetScheduler(): void {
|
|
313
|
+
if (_scheduler) {
|
|
314
|
+
_scheduler.stop();
|
|
315
|
+
_scheduler = null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduled task executor — bridges the scheduler to executeAgentQuery().
|
|
3
|
+
*
|
|
4
|
+
* Fetches the task, runs the agent, delivers results, and returns execution
|
|
5
|
+
* metadata. The executor does NOT update the run record — callers (engine.ts)
|
|
6
|
+
* own run completion to avoid double-writes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createLogger } from "@atlas/api/lib/logger";
|
|
10
|
+
import { getScheduledTask } from "@atlas/api/lib/scheduled-tasks";
|
|
11
|
+
import { executeAgentQuery } from "@atlas/api/lib/agent-query";
|
|
12
|
+
import { deliverResult } from "./delivery";
|
|
13
|
+
|
|
14
|
+
const log = createLogger("scheduler-executor");
|
|
15
|
+
|
|
16
|
+
export interface ExecutionResult {
|
|
17
|
+
tokensUsed: number;
|
|
18
|
+
deliveryAttempted: number;
|
|
19
|
+
deliverySucceeded: number;
|
|
20
|
+
deliveryFailed: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Execute a scheduled task: run the agent query and deliver results.
|
|
25
|
+
* Returns execution metadata on success. Throws on failure.
|
|
26
|
+
* Callers are responsible for updating the run record.
|
|
27
|
+
*/
|
|
28
|
+
export async function executeScheduledTask(
|
|
29
|
+
taskId: string,
|
|
30
|
+
runId: string,
|
|
31
|
+
timeoutMs: number,
|
|
32
|
+
): Promise<ExecutionResult> {
|
|
33
|
+
const taskResult = await getScheduledTask(taskId);
|
|
34
|
+
if (!taskResult.ok) {
|
|
35
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const task = taskResult.data;
|
|
39
|
+
const requestId = `sched-${taskId}-${runId}`;
|
|
40
|
+
|
|
41
|
+
log.info({ taskId, runId, question: task.question.slice(0, 100) }, "Executing scheduled task");
|
|
42
|
+
|
|
43
|
+
// Run agent with timeout — clear timer to avoid resource leak
|
|
44
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
45
|
+
const agentPromise = executeAgentQuery(task.question, requestId);
|
|
46
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
47
|
+
timer = setTimeout(() => reject(new Error(`Task execution timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let agentResult;
|
|
51
|
+
try {
|
|
52
|
+
agentResult = await Promise.race([agentPromise, timeoutPromise]);
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timer!);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Deliver results to configured channels (best-effort)
|
|
58
|
+
const delivery = await deliverResult(task, agentResult);
|
|
59
|
+
|
|
60
|
+
if (delivery.failed > 0) {
|
|
61
|
+
log.warn(
|
|
62
|
+
{ taskId, runId, ...delivery },
|
|
63
|
+
"Partial delivery failure — some recipients did not receive results",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
tokensUsed: agentResult.usage.totalTokens,
|
|
69
|
+
deliveryAttempted: delivery.attempted,
|
|
70
|
+
deliverySucceeded: delivery.succeeded,
|
|
71
|
+
deliveryFailed: delivery.failed,
|
|
72
|
+
};
|
|
73
|
+
}
|