@useatlas/create 0.0.2 → 0.0.4
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 +4 -18
- package/index.ts +191 -31
- package/package.json +1 -1
- package/templates/docker/.env.example +3 -3
- package/templates/docker/Dockerfile.sidecar +28 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +9 -9
- package/templates/docker/bin/atlas.ts +108 -44
- package/templates/docker/data/demo-semantic/catalog.yml +51 -27
- package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
- package/templates/docker/data/demo-semantic/glossary.yml +104 -8
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/docker/docker-compose.yml +1 -1
- package/templates/docker/docs/deploy.md +2 -39
- package/templates/docker/package.json +17 -1
- package/templates/docker/semantic/catalog.yml +62 -3
- package/templates/docker/semantic/entities/accounts.yml +162 -0
- package/templates/docker/semantic/entities/companies.yml +143 -0
- package/templates/docker/semantic/entities/people.yml +132 -0
- package/templates/docker/semantic/glossary.yml +116 -4
- package/templates/docker/semantic/metrics/accounts.yml +77 -0
- package/templates/docker/semantic/metrics/companies.yml +63 -0
- package/templates/docker/sidecar/Dockerfile +5 -6
- package/templates/docker/sidecar/railway.json +1 -2
- package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/docker/src/api/__tests__/health.test.ts +30 -8
- package/templates/docker/src/api/routes/admin.ts +549 -8
- package/templates/docker/src/api/routes/chat.ts +5 -20
- package/templates/docker/src/api/routes/health.ts +39 -27
- package/templates/docker/src/api/routes/openapi.ts +1329 -74
- package/templates/docker/src/api/routes/query.ts +2 -1
- package/templates/docker/src/api/server.ts +27 -0
- package/templates/docker/src/app/api/[...route]/route.ts +2 -2
- package/templates/docker/src/app/globals.css +13 -12
- package/templates/docker/src/app/layout.tsx +9 -2
- package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/docker/src/components/ui/badge.tsx +48 -0
- package/templates/docker/src/components/ui/button.tsx +64 -0
- package/templates/docker/src/components/ui/card.tsx +92 -0
- package/templates/docker/src/components/ui/collapsible.tsx +33 -0
- package/templates/docker/src/components/ui/command.tsx +184 -0
- package/templates/docker/src/components/ui/dialog.tsx +158 -0
- package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/docker/src/components/ui/input.tsx +21 -0
- package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
- package/templates/docker/src/components/ui/select.tsx +190 -0
- package/templates/docker/src/components/ui/separator.tsx +28 -0
- package/templates/docker/src/components/ui/sheet.tsx +143 -0
- package/templates/docker/src/components/ui/sidebar.tsx +726 -0
- package/templates/docker/src/components/ui/skeleton.tsx +13 -0
- package/templates/docker/src/components/ui/table.tsx +116 -0
- package/templates/docker/src/components/ui/tabs.tsx +91 -0
- package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
- package/templates/docker/src/components/ui/toggle.tsx +47 -0
- package/templates/docker/src/components/ui/tooltip.tsx +57 -0
- package/templates/docker/src/hooks/use-mobile.ts +19 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
- package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/docker/src/lib/agent-query.ts +5 -23
- package/templates/docker/src/lib/agent.ts +32 -112
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/docker/src/lib/auth/middleware.ts +30 -4
- package/templates/docker/src/lib/auth/migrate.ts +97 -0
- package/templates/docker/src/lib/auth/server.ts +12 -1
- package/templates/docker/src/lib/config.ts +37 -39
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/docker/src/lib/db/connection.ts +87 -265
- package/templates/docker/src/lib/db/internal.ts +6 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/docker/src/lib/plugins/index.ts +4 -1
- package/templates/docker/src/lib/plugins/migrate.ts +103 -0
- package/templates/docker/src/lib/plugins/registry.ts +12 -6
- package/templates/docker/src/lib/plugins/wiring.ts +113 -4
- package/templates/docker/src/lib/providers.ts +6 -1
- package/templates/docker/src/lib/security.ts +24 -0
- package/templates/docker/src/lib/semantic.ts +2 -0
- package/templates/docker/src/lib/sidecar-types.ts +12 -1
- package/templates/docker/src/lib/startup.ts +71 -101
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/docker/src/lib/tools/explore.ts +28 -32
- package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/docker/src/lib/tools/python.ts +367 -0
- package/templates/docker/src/lib/tools/registry.ts +49 -22
- package/templates/docker/src/lib/tools/sql.ts +88 -88
- package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/docker/src/ui/context.tsx +1 -1
- package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/docker/tsconfig.json +2 -2
- package/templates/nextjs-standalone/.env.example +1 -1
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +9 -9
- package/templates/nextjs-standalone/bin/atlas.ts +108 -44
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/nextjs-standalone/docs/deploy.md +2 -39
- package/templates/nextjs-standalone/package.json +11 -2
- package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
- package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
- package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
- package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
- package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
- package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
- package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
- package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
- package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
- package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
- package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
- package/templates/nextjs-standalone/src/api/server.ts +27 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
- package/templates/nextjs-standalone/src/app/globals.css +13 -12
- package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
- package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
- package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
- package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
- package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
- package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
- package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
- package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
- package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
- package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
- package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
- package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
- package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
- package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
- package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
- package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
- package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
- package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
- package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
- package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
- package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
- package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
- package/templates/nextjs-standalone/src/lib/config.ts +37 -39
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
- package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +103 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +12 -6
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
- package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
- package/templates/nextjs-standalone/src/lib/security.ts +24 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
- package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
- package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/nextjs-standalone/tsconfig.json +0 -1
- package/templates/nextjs-standalone/vercel.json +4 -1
- package/templates/docker/render.yaml +0 -34
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/docker/src/lib/db/duckdb.ts +0 -122
- package/templates/docker/src/lib/db/salesforce.ts +0 -342
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/docker/src/lib/tools/salesforce.ts +0 -138
- package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, mock } from "bun:test";
|
|
2
2
|
import { PluginRegistry } from "../registry";
|
|
3
3
|
import type { PluginLike, PluginContextLike } from "../registry";
|
|
4
|
-
import { wireDatasourcePlugins, wireActionPlugins, wireInteractionPlugins, wireContextPlugins } from "../wiring";
|
|
4
|
+
import { wireDatasourcePlugins, wireActionPlugins, wireInteractionPlugins, wireContextPlugins, wireSandboxPlugins } from "../wiring";
|
|
5
|
+
import type { SandboxExecBackend } from "../wiring";
|
|
5
6
|
|
|
6
7
|
const minimalCtx: PluginContextLike = {
|
|
7
8
|
db: null,
|
|
@@ -15,9 +16,9 @@ const minimalCtx: PluginContextLike = {
|
|
|
15
16
|
|
|
16
17
|
function makeMockConnectionRegistry() {
|
|
17
18
|
return {
|
|
18
|
-
registered: [] as { id: string; conn: unknown; dbType: string; description?: string; validate?: unknown }[],
|
|
19
|
-
|
|
20
|
-
this.registered.push({ id, conn, dbType, description, validate });
|
|
19
|
+
registered: [] as { id: string; conn: unknown; dbType: string; description?: string; validate?: unknown; meta?: unknown }[],
|
|
20
|
+
registerDirect(id: string, conn: unknown, dbType: string, description?: string, validate?: unknown, meta?: unknown) {
|
|
21
|
+
this.registered.push({ id, conn, dbType, description, validate, meta });
|
|
21
22
|
},
|
|
22
23
|
};
|
|
23
24
|
}
|
|
@@ -49,7 +50,7 @@ function makeDatasourcePlugin(
|
|
|
49
50
|
};
|
|
50
51
|
return {
|
|
51
52
|
id,
|
|
52
|
-
|
|
53
|
+
types: ["datasource"],
|
|
53
54
|
version: "1.0.0",
|
|
54
55
|
connection: {
|
|
55
56
|
create: () => conn,
|
|
@@ -66,7 +67,7 @@ function makeDatasourcePlugin(
|
|
|
66
67
|
function makeActionPlugin(id: string, opts?: { unhealthy?: boolean }): PluginLike {
|
|
67
68
|
return {
|
|
68
69
|
id,
|
|
69
|
-
|
|
70
|
+
types: ["action"],
|
|
70
71
|
version: "1.0.0",
|
|
71
72
|
actions: [
|
|
72
73
|
{
|
|
@@ -88,7 +89,7 @@ function makeActionPlugin(id: string, opts?: { unhealthy?: boolean }): PluginLik
|
|
|
88
89
|
function makeInteractionPlugin(id: string, routesFn: (app: unknown) => void, opts?: { unhealthy?: boolean }): PluginLike {
|
|
89
90
|
return {
|
|
90
91
|
id,
|
|
91
|
-
|
|
92
|
+
types: ["interaction"],
|
|
92
93
|
version: "1.0.0",
|
|
93
94
|
routes: routesFn,
|
|
94
95
|
...(opts?.unhealthy
|
|
@@ -127,7 +128,7 @@ describe("wireDatasourcePlugins", () => {
|
|
|
127
128
|
const validator = (q: string) => ({ valid: /^SELECT/i.test(q) });
|
|
128
129
|
const plugin: PluginLike = {
|
|
129
130
|
id: "validated-ds",
|
|
130
|
-
|
|
131
|
+
types: ["datasource"],
|
|
131
132
|
version: "1.0.0",
|
|
132
133
|
connection: {
|
|
133
134
|
create: () => ({ query: async () => ({ columns: [], rows: [] }), close: async () => {} }),
|
|
@@ -147,6 +148,47 @@ describe("wireDatasourcePlugins", () => {
|
|
|
147
148
|
expect(connRegistry.registered[0].validate).toBe(validator);
|
|
148
149
|
});
|
|
149
150
|
|
|
151
|
+
test("passes parserDialect and forbiddenPatterns through meta", async () => {
|
|
152
|
+
const patterns = [/^\s*(KILL)\b/i];
|
|
153
|
+
const plugin: PluginLike = {
|
|
154
|
+
id: "meta-ds",
|
|
155
|
+
types: ["datasource"],
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
connection: {
|
|
158
|
+
create: () => ({ query: async () => ({ columns: [], rows: [] }), close: async () => {} }),
|
|
159
|
+
dbType: "clickhouse",
|
|
160
|
+
parserDialect: "PostgresQL",
|
|
161
|
+
forbiddenPatterns: patterns,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
registry.register(plugin);
|
|
165
|
+
await registry.initializeAll(minimalCtx);
|
|
166
|
+
|
|
167
|
+
await wireDatasourcePlugins(
|
|
168
|
+
registry,
|
|
169
|
+
connRegistry as unknown as import("@atlas/api/lib/db/connection").ConnectionRegistry,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
expect(connRegistry.registered).toHaveLength(1);
|
|
173
|
+
expect(connRegistry.registered[0].meta).toEqual({
|
|
174
|
+
parserDialect: "PostgresQL",
|
|
175
|
+
forbiddenPatterns: patterns,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("passes undefined meta when no parserDialect or forbiddenPatterns", async () => {
|
|
180
|
+
registry.register(makeDatasourcePlugin("plain-meta"));
|
|
181
|
+
await registry.initializeAll(minimalCtx);
|
|
182
|
+
|
|
183
|
+
await wireDatasourcePlugins(
|
|
184
|
+
registry,
|
|
185
|
+
connRegistry as unknown as import("@atlas/api/lib/db/connection").ConnectionRegistry,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(connRegistry.registered).toHaveLength(1);
|
|
189
|
+
expect(connRegistry.registered[0].meta).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
150
192
|
test("passes undefined validate when not provided", async () => {
|
|
151
193
|
registry.register(makeDatasourcePlugin("plain-ds"));
|
|
152
194
|
await registry.initializeAll(minimalCtx);
|
|
@@ -179,7 +221,7 @@ describe("wireDatasourcePlugins", () => {
|
|
|
179
221
|
test("continues when one create() throws and returns failures", async () => {
|
|
180
222
|
const failingPlugin: PluginLike = {
|
|
181
223
|
id: "failing-ds",
|
|
182
|
-
|
|
224
|
+
types: ["datasource"],
|
|
183
225
|
version: "1.0.0",
|
|
184
226
|
connection: {
|
|
185
227
|
create: () => { throw new Error("conn failed"); },
|
|
@@ -389,7 +431,7 @@ describe("wireActionPlugins", () => {
|
|
|
389
431
|
test("registers all actions from a multi-action plugin", async () => {
|
|
390
432
|
const plugin: PluginLike = {
|
|
391
433
|
id: "multi-action",
|
|
392
|
-
|
|
434
|
+
types: ["action"],
|
|
393
435
|
version: "1.0.0",
|
|
394
436
|
actions: [
|
|
395
437
|
{ name: "action-a", description: "A", tool: {}, actionType: "t", reversible: false, defaultApproval: "manual", requiredCredentials: [] },
|
|
@@ -421,7 +463,7 @@ describe("wireActionPlugins", () => {
|
|
|
421
463
|
|
|
422
464
|
const plugin: PluginLike = {
|
|
423
465
|
id: "fail-action",
|
|
424
|
-
|
|
466
|
+
types: ["action"],
|
|
425
467
|
version: "1.0.0",
|
|
426
468
|
actions: [
|
|
427
469
|
{ name: "fail-action", description: "Fails", tool: {}, actionType: "t", reversible: false, defaultApproval: "manual", requiredCredentials: [] },
|
|
@@ -485,7 +527,7 @@ describe("wireInteractionPlugins", () => {
|
|
|
485
527
|
const fakeApp = { route: mock(() => {}) };
|
|
486
528
|
const routelessPlugin: PluginLike = {
|
|
487
529
|
id: "stdio-interaction",
|
|
488
|
-
|
|
530
|
+
types: ["interaction"],
|
|
489
531
|
version: "1.0.0",
|
|
490
532
|
// No routes property — like MCP stdio transport
|
|
491
533
|
};
|
|
@@ -506,7 +548,7 @@ describe("wireInteractionPlugins", () => {
|
|
|
506
548
|
function makeContextPlugin(id: string, loadFn: () => Promise<string>, opts?: { unhealthy?: boolean }): PluginLike {
|
|
507
549
|
return {
|
|
508
550
|
id,
|
|
509
|
-
|
|
551
|
+
types: ["context"],
|
|
510
552
|
version: "1.0.0",
|
|
511
553
|
contextProvider: { load: loadFn },
|
|
512
554
|
...(opts?.unhealthy
|
|
@@ -571,7 +613,7 @@ describe("wireContextPlugins", () => {
|
|
|
571
613
|
test("skips context plugins without contextProvider", async () => {
|
|
572
614
|
const noProvider: PluginLike = {
|
|
573
615
|
id: "no-provider",
|
|
574
|
-
|
|
616
|
+
types: ["context"],
|
|
575
617
|
version: "1.0.0",
|
|
576
618
|
};
|
|
577
619
|
registry.register(noProvider);
|
|
@@ -583,3 +625,175 @@ describe("wireContextPlugins", () => {
|
|
|
583
625
|
expect(result.failed).toEqual([]);
|
|
584
626
|
});
|
|
585
627
|
});
|
|
628
|
+
|
|
629
|
+
// --- wireSandboxPlugins ---
|
|
630
|
+
|
|
631
|
+
function makeMockBackend(tag: string): SandboxExecBackend {
|
|
632
|
+
return {
|
|
633
|
+
exec: async (command: string) => ({
|
|
634
|
+
stdout: `[${tag}] ${command}`,
|
|
635
|
+
stderr: "",
|
|
636
|
+
exitCode: 0,
|
|
637
|
+
}),
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function makeSandboxPlugin(
|
|
642
|
+
id: string,
|
|
643
|
+
opts?: { priority?: number; createFn?: (root: string) => Promise<SandboxExecBackend> | SandboxExecBackend; unhealthy?: boolean },
|
|
644
|
+
): PluginLike {
|
|
645
|
+
return {
|
|
646
|
+
id,
|
|
647
|
+
types: ["sandbox"],
|
|
648
|
+
version: "1.0.0",
|
|
649
|
+
sandbox: {
|
|
650
|
+
create: opts?.createFn ?? (async () => makeMockBackend(id)),
|
|
651
|
+
...(opts?.priority !== undefined ? { priority: opts.priority } : {}),
|
|
652
|
+
},
|
|
653
|
+
...(opts?.unhealthy
|
|
654
|
+
? { initialize: async () => { throw new Error("fail"); } }
|
|
655
|
+
: {}),
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
describe("wireSandboxPlugins", () => {
|
|
660
|
+
let registry: PluginRegistry;
|
|
661
|
+
|
|
662
|
+
beforeEach(() => {
|
|
663
|
+
registry = new PluginRegistry();
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test("returns backend from a single sandbox plugin", async () => {
|
|
667
|
+
registry.register(makeSandboxPlugin("my-sandbox"));
|
|
668
|
+
await registry.initializeAll(minimalCtx);
|
|
669
|
+
|
|
670
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
671
|
+
|
|
672
|
+
expect(result.backend).not.toBeNull();
|
|
673
|
+
expect(result.pluginId).toBe("my-sandbox");
|
|
674
|
+
expect(result.failed).toEqual([]);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("selects highest-priority plugin", async () => {
|
|
678
|
+
registry.register(makeSandboxPlugin("low", { priority: 40 }));
|
|
679
|
+
registry.register(makeSandboxPlugin("high", { priority: 90 }));
|
|
680
|
+
await registry.initializeAll(minimalCtx);
|
|
681
|
+
|
|
682
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
683
|
+
|
|
684
|
+
expect(result.pluginId).toBe("high");
|
|
685
|
+
const output = await result.backend!.exec("ls");
|
|
686
|
+
expect(output.stdout).toContain("[high]");
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test("uses default priority (60) when omitted", async () => {
|
|
690
|
+
registry.register(makeSandboxPlugin("explicit-low", { priority: 50 }));
|
|
691
|
+
registry.register(makeSandboxPlugin("default-priority")); // no priority → 60
|
|
692
|
+
await registry.initializeAll(minimalCtx);
|
|
693
|
+
|
|
694
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
695
|
+
|
|
696
|
+
expect(result.pluginId).toBe("default-priority");
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test("falls through to next plugin when create() throws", async () => {
|
|
700
|
+
registry.register(makeSandboxPlugin("broken", {
|
|
701
|
+
priority: 90,
|
|
702
|
+
createFn: async () => { throw new Error("init failed"); },
|
|
703
|
+
}));
|
|
704
|
+
registry.register(makeSandboxPlugin("working", { priority: 50 }));
|
|
705
|
+
await registry.initializeAll(minimalCtx);
|
|
706
|
+
|
|
707
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
708
|
+
|
|
709
|
+
expect(result.pluginId).toBe("working");
|
|
710
|
+
expect(result.failed).toHaveLength(1);
|
|
711
|
+
expect(result.failed[0].pluginId).toBe("broken");
|
|
712
|
+
expect(result.failed[0].error).toBe("init failed");
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
test("returns null when all plugins fail", async () => {
|
|
716
|
+
registry.register(makeSandboxPlugin("broken-1", {
|
|
717
|
+
createFn: async () => { throw new Error("fail 1"); },
|
|
718
|
+
}));
|
|
719
|
+
registry.register(makeSandboxPlugin("broken-2", {
|
|
720
|
+
createFn: async () => { throw new Error("fail 2"); },
|
|
721
|
+
}));
|
|
722
|
+
await registry.initializeAll(minimalCtx);
|
|
723
|
+
|
|
724
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
725
|
+
|
|
726
|
+
expect(result.backend).toBeNull();
|
|
727
|
+
expect(result.pluginId).toBeNull();
|
|
728
|
+
expect(result.failed).toHaveLength(2);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("returns null when no sandbox plugins registered", async () => {
|
|
732
|
+
await registry.initializeAll(minimalCtx);
|
|
733
|
+
|
|
734
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
735
|
+
|
|
736
|
+
expect(result.backend).toBeNull();
|
|
737
|
+
expect(result.pluginId).toBeNull();
|
|
738
|
+
expect(result.failed).toEqual([]);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test("skips unhealthy sandbox plugins", async () => {
|
|
742
|
+
registry.register(makeSandboxPlugin("healthy"));
|
|
743
|
+
registry.register(makeSandboxPlugin("unhealthy", { unhealthy: true }));
|
|
744
|
+
await registry.initializeAll(minimalCtx);
|
|
745
|
+
|
|
746
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
747
|
+
|
|
748
|
+
expect(result.pluginId).toBe("healthy");
|
|
749
|
+
expect(result.failed).toEqual([]);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test("skips sandbox plugins missing sandbox.create()", async () => {
|
|
753
|
+
const noCreate: PluginLike = {
|
|
754
|
+
id: "no-create",
|
|
755
|
+
types: ["sandbox"],
|
|
756
|
+
version: "1.0.0",
|
|
757
|
+
// No sandbox property
|
|
758
|
+
};
|
|
759
|
+
registry.register(noCreate);
|
|
760
|
+
await registry.initializeAll(minimalCtx);
|
|
761
|
+
|
|
762
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
763
|
+
|
|
764
|
+
expect(result.backend).toBeNull();
|
|
765
|
+
expect(result.pluginId).toBeNull();
|
|
766
|
+
expect(result.failed).toEqual([]);
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test("passes semanticRoot to sandbox.create()", async () => {
|
|
770
|
+
let receivedRoot: string | undefined;
|
|
771
|
+
registry.register(makeSandboxPlugin("root-check", {
|
|
772
|
+
createFn: async (root: string) => {
|
|
773
|
+
receivedRoot = root;
|
|
774
|
+
return makeMockBackend("root-check");
|
|
775
|
+
},
|
|
776
|
+
}));
|
|
777
|
+
await registry.initializeAll(minimalCtx);
|
|
778
|
+
|
|
779
|
+
await wireSandboxPlugins(registry, "/my/custom/semantic");
|
|
780
|
+
|
|
781
|
+
expect(receivedRoot).toBe("/my/custom/semantic");
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
test("rejects backend missing exec method from create()", async () => {
|
|
785
|
+
registry.register(makeSandboxPlugin("bad-backend", {
|
|
786
|
+
createFn: async () => ({} as SandboxExecBackend),
|
|
787
|
+
}));
|
|
788
|
+
registry.register(makeSandboxPlugin("good-backend", { priority: 40 }));
|
|
789
|
+
await registry.initializeAll(minimalCtx);
|
|
790
|
+
|
|
791
|
+
const result = await wireSandboxPlugins(registry, "/semantic");
|
|
792
|
+
|
|
793
|
+
// bad-backend has default priority (60) > good-backend (40), tried first but rejected
|
|
794
|
+
expect(result.pluginId).toBe("good-backend");
|
|
795
|
+
expect(result.failed).toHaveLength(1);
|
|
796
|
+
expect(result.failed[0].pluginId).toBe("bad-backend");
|
|
797
|
+
expect(result.failed[0].error).toContain("missing exec method");
|
|
798
|
+
});
|
|
799
|
+
});
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
export { plugins, PluginRegistry } from "./registry";
|
|
6
6
|
export type { PluginLike, PluginContextLike, PluginHealthResult, PluginType, PluginStatus, PluginDescription } from "./registry";
|
|
7
|
-
export { wireDatasourcePlugins, wireActionPlugins, wireInteractionPlugins, wireContextPlugins } from "./wiring";
|
|
7
|
+
export { wireDatasourcePlugins, wireActionPlugins, wireInteractionPlugins, wireContextPlugins, wireSandboxPlugins } from "./wiring";
|
|
8
|
+
export type { SandboxExecBackend } from "./wiring";
|
|
9
|
+
export { generateMigrationSQL, generateColumnMigrations, applyMigrations, runPluginMigrations, ensureMigrationsTable, getAppliedMigrations, diffSchema, prefixTableName } from "./migrate";
|
|
10
|
+
export type { MigrateDB, MigrationStatement, SchemaDiff } from "./migrate";
|
|
8
11
|
export { dispatchHook } from "./hooks";
|
|
9
12
|
export { getPluginTools, setPluginTools, getContextFragments, setContextFragments } from "./tools";
|
|
@@ -273,6 +273,109 @@ export async function applyMigrations(
|
|
|
273
273
|
return { applied, skipped };
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Column migrations (ALTER TABLE ADD COLUMN)
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Query the actual columns for a given table from information_schema.
|
|
282
|
+
* Returns a Set of lowercase column names.
|
|
283
|
+
*/
|
|
284
|
+
async function getExistingColumns(db: MigrateDB, tableName: string): Promise<Set<string>> {
|
|
285
|
+
const result = await db.query(
|
|
286
|
+
"SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1",
|
|
287
|
+
[tableName],
|
|
288
|
+
);
|
|
289
|
+
return new Set(result.rows.map((r) => String(r.column_name).toLowerCase()));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Generate ALTER TABLE ADD COLUMN IF NOT EXISTS statements for fields
|
|
294
|
+
* that exist in the plugin schema but not in the actual database table.
|
|
295
|
+
*
|
|
296
|
+
* Column detection queries `information_schema.columns` in the `public`
|
|
297
|
+
* schema. Only handles column additions — column removal and type
|
|
298
|
+
* changes are not supported (require manual migration).
|
|
299
|
+
*/
|
|
300
|
+
export async function generateColumnMigrations(
|
|
301
|
+
db: MigrateDB,
|
|
302
|
+
plugins: PluginWithSchema[],
|
|
303
|
+
): Promise<MigrationStatement[]> {
|
|
304
|
+
const statements: MigrationStatement[] = [];
|
|
305
|
+
|
|
306
|
+
for (const plugin of plugins) {
|
|
307
|
+
if (!plugin.schema) continue;
|
|
308
|
+
|
|
309
|
+
for (const [tableName, tableDef] of Object.entries(plugin.schema)) {
|
|
310
|
+
const prefixed = prefixTableName(plugin.id, tableName);
|
|
311
|
+
|
|
312
|
+
// Check if table exists at all — skip if it doesn't (CREATE TABLE will handle it)
|
|
313
|
+
const existing = await getExistingColumns(db, prefixed);
|
|
314
|
+
if (existing.size === 0) {
|
|
315
|
+
log.debug({ table: prefixed, pluginId: plugin.id }, "Table not in information_schema — skipping column migration");
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
for (const [fieldName, fieldDef] of Object.entries(tableDef.fields)) {
|
|
320
|
+
if (existing.has(fieldName.toLowerCase())) continue;
|
|
321
|
+
|
|
322
|
+
let colDef: string;
|
|
323
|
+
try {
|
|
324
|
+
colDef = fieldToSQL(fieldName, fieldDef);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Plugin "${plugin.id}", table "${prefixed}": ${err instanceof Error ? err.message : String(err)}`,
|
|
328
|
+
{ cause: err },
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
const sql = `ALTER TABLE "${prefixed}" ADD COLUMN IF NOT EXISTS ${colDef};`;
|
|
332
|
+
|
|
333
|
+
statements.push({
|
|
334
|
+
pluginId: plugin.id,
|
|
335
|
+
tableName,
|
|
336
|
+
prefixedName: prefixed,
|
|
337
|
+
sql,
|
|
338
|
+
hash: hashSQL(sql),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return statements;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
// High-level orchestrator
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Run all plugin schema migrations: CREATE TABLE for new tables, then
|
|
353
|
+
* ALTER TABLE ADD COLUMN for new fields on existing tables.
|
|
354
|
+
*
|
|
355
|
+
* Idempotent — safe to call on every startup.
|
|
356
|
+
*/
|
|
357
|
+
export async function runPluginMigrations(
|
|
358
|
+
db: MigrateDB,
|
|
359
|
+
plugins: PluginWithSchema[],
|
|
360
|
+
): Promise<{ applied: string[]; skipped: string[] }> {
|
|
361
|
+
// Phase 1: CREATE TABLE
|
|
362
|
+
const createStatements = generateMigrationSQL(plugins);
|
|
363
|
+
const createResult = await applyMigrations(db, createStatements);
|
|
364
|
+
|
|
365
|
+
// Phase 2: ALTER TABLE ADD COLUMN (for tables that already existed or were just created)
|
|
366
|
+
const columnStatements = await generateColumnMigrations(db, plugins);
|
|
367
|
+
if (columnStatements.length === 0) {
|
|
368
|
+
return createResult;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const columnResult = await applyMigrations(db, columnStatements);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
applied: [...createResult.applied, ...columnResult.applied],
|
|
375
|
+
skipped: [...createResult.skipped, ...columnResult.skipped],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
276
379
|
// ---------------------------------------------------------------------------
|
|
277
380
|
// Diff: compare declared schema vs actual tables
|
|
278
381
|
// ---------------------------------------------------------------------------
|
|
@@ -41,7 +41,8 @@ export interface PluginContextLike {
|
|
|
41
41
|
*/
|
|
42
42
|
export interface PluginLike {
|
|
43
43
|
readonly id: string;
|
|
44
|
-
|
|
44
|
+
/** Plugin type(s). A plugin can implement multiple types. */
|
|
45
|
+
readonly types: readonly PluginType[];
|
|
45
46
|
readonly version: string;
|
|
46
47
|
readonly name?: string;
|
|
47
48
|
initialize?(ctx: PluginContextLike): Promise<void>;
|
|
@@ -59,7 +60,7 @@ interface PluginEntry {
|
|
|
59
60
|
|
|
60
61
|
export interface PluginDescription {
|
|
61
62
|
id: string;
|
|
62
|
-
|
|
63
|
+
types: readonly PluginType[];
|
|
63
64
|
version: string;
|
|
64
65
|
name: string;
|
|
65
66
|
status: PluginStatus;
|
|
@@ -83,7 +84,7 @@ export class PluginRegistry {
|
|
|
83
84
|
}
|
|
84
85
|
this.idSet.add(plugin.id);
|
|
85
86
|
this.entries.push({ plugin, status: "registered" });
|
|
86
|
-
log.info({ pluginId: plugin.id,
|
|
87
|
+
log.info({ pluginId: plugin.id, types: plugin.types }, "Plugin registered");
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
/**
|
|
@@ -190,13 +191,18 @@ export class PluginRegistry {
|
|
|
190
191
|
return this.entries.find((e) => e.plugin.id === id)?.status;
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
/** Return plugins
|
|
194
|
+
/** Return plugins whose types array includes the given type and are currently healthy. */
|
|
194
195
|
getByType(type: PluginType): PluginLike[] {
|
|
195
196
|
return this.entries
|
|
196
|
-
.filter((e) => e.plugin.type
|
|
197
|
+
.filter((e) => e.plugin.types.includes(type) && e.status === "healthy")
|
|
197
198
|
.map((e) => e.plugin);
|
|
198
199
|
}
|
|
199
200
|
|
|
201
|
+
/** Return all registered plugins regardless of status (for schema migrations at boot). */
|
|
202
|
+
getAll(): PluginLike[] {
|
|
203
|
+
return this.entries.map((e) => e.plugin);
|
|
204
|
+
}
|
|
205
|
+
|
|
200
206
|
/** Return all healthy plugins regardless of type (for cross-cutting hooks). */
|
|
201
207
|
getAllHealthy(): PluginLike[] {
|
|
202
208
|
return this.entries
|
|
@@ -208,7 +214,7 @@ export class PluginRegistry {
|
|
|
208
214
|
describe(): PluginDescription[] {
|
|
209
215
|
return this.entries.map((e) => ({
|
|
210
216
|
id: e.plugin.id,
|
|
211
|
-
|
|
217
|
+
types: e.plugin.types,
|
|
212
218
|
version: e.plugin.version,
|
|
213
219
|
name: e.plugin.name ?? e.plugin.id,
|
|
214
220
|
status: e.status,
|
|
@@ -28,6 +28,8 @@ interface DatasourceShape {
|
|
|
28
28
|
create(): Promise<{ query(sql: string, timeoutMs?: number): Promise<unknown>; close(): Promise<void> }> | { query(sql: string, timeoutMs?: number): Promise<unknown>; close(): Promise<void> };
|
|
29
29
|
dbType: string;
|
|
30
30
|
validate?(query: string): { valid: boolean; reason?: string };
|
|
31
|
+
parserDialect?: string;
|
|
32
|
+
forbiddenPatterns?: RegExp[];
|
|
31
33
|
};
|
|
32
34
|
entities?: unknown[] | (() => Promise<unknown[]> | unknown[]);
|
|
33
35
|
dialect?: string;
|
|
@@ -51,7 +53,7 @@ interface InteractionShape {
|
|
|
51
53
|
|
|
52
54
|
function hasContextProvider(p: PluginLike): p is PluginLike & ContextShape {
|
|
53
55
|
return (
|
|
54
|
-
p.
|
|
56
|
+
p.types.includes("context") &&
|
|
55
57
|
typeof (p as Record<string, unknown>).contextProvider === "object" &&
|
|
56
58
|
(p as Record<string, unknown>).contextProvider !== null &&
|
|
57
59
|
typeof ((p as Record<string, unknown>).contextProvider as Record<string, unknown>)?.load === "function"
|
|
@@ -60,18 +62,18 @@ function hasContextProvider(p: PluginLike): p is PluginLike & ContextShape {
|
|
|
60
62
|
|
|
61
63
|
function hasDatasource(p: PluginLike): p is PluginLike & DatasourceShape {
|
|
62
64
|
return (
|
|
63
|
-
p.
|
|
65
|
+
p.types.includes("datasource") &&
|
|
64
66
|
typeof (p as Record<string, unknown>).connection === "object" &&
|
|
65
67
|
(p as Record<string, unknown>).connection !== null
|
|
66
68
|
);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
function hasActions(p: PluginLike): p is PluginLike & ActionShape {
|
|
70
|
-
return p.
|
|
72
|
+
return p.types.includes("action") && Array.isArray((p as Record<string, unknown>).actions);
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
function hasRoutes(p: PluginLike): p is PluginLike & InteractionShape {
|
|
74
|
-
return p.
|
|
76
|
+
return p.types.includes("interaction") && typeof (p as Record<string, unknown>).routes === "function";
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
// ---------------------------------------------------------------------------
|
|
@@ -106,12 +108,16 @@ export async function wireDatasourcePlugins(
|
|
|
106
108
|
}
|
|
107
109
|
try {
|
|
108
110
|
const conn = await plugin.connection.create();
|
|
111
|
+
const meta = (plugin.connection.parserDialect || plugin.connection.forbiddenPatterns)
|
|
112
|
+
? { parserDialect: plugin.connection.parserDialect, forbiddenPatterns: plugin.connection.forbiddenPatterns }
|
|
113
|
+
: undefined;
|
|
109
114
|
await connRegistry.registerDirect(
|
|
110
115
|
plugin.id,
|
|
111
116
|
conn as Parameters<ConnectionRegistry["registerDirect"]>[1],
|
|
112
117
|
plugin.connection.dbType as Parameters<ConnectionRegistry["registerDirect"]>[2],
|
|
113
118
|
plugin.name ?? plugin.id,
|
|
114
119
|
plugin.connection.validate,
|
|
120
|
+
meta,
|
|
115
121
|
);
|
|
116
122
|
wired.push(plugin.id);
|
|
117
123
|
log.info({ pluginId: plugin.id, dbType: plugin.connection.dbType }, "Datasource plugin wired");
|
|
@@ -254,6 +260,109 @@ export async function wireInteractionPlugins(
|
|
|
254
260
|
return { wired, failed };
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Sandbox plugins
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Duplicated from @useatlas/plugin-sdk/types to avoid runtime SDK dependency.
|
|
269
|
+
* Keep in sync — explore-sdk-compat.test.ts verifies structural equivalence.
|
|
270
|
+
*/
|
|
271
|
+
const SANDBOX_DEFAULT_PRIORITY = 60;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Minimal interface for a sandbox execution backend.
|
|
275
|
+
* Structurally identical to ExploreBackend in explore.ts — duplicated here
|
|
276
|
+
* to avoid a circular dependency (explore.ts imports from wiring.ts).
|
|
277
|
+
* Changes to either interface should be mirrored.
|
|
278
|
+
*/
|
|
279
|
+
export interface SandboxExecBackend {
|
|
280
|
+
exec(command: string): Promise<{ stdout: string; stderr: string; exitCode: number }>;
|
|
281
|
+
close?(): Promise<void>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
interface SandboxShape {
|
|
285
|
+
sandbox: {
|
|
286
|
+
create(root: string): Promise<SandboxExecBackend> | SandboxExecBackend;
|
|
287
|
+
priority?: number;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function hasSandbox(p: PluginLike): p is PluginLike & SandboxShape {
|
|
292
|
+
return (
|
|
293
|
+
p.types.includes("sandbox") &&
|
|
294
|
+
typeof (p as Record<string, unknown>).sandbox === "object" &&
|
|
295
|
+
(p as Record<string, unknown>).sandbox !== null &&
|
|
296
|
+
typeof ((p as Record<string, unknown>).sandbox as Record<string, unknown>)?.create === "function"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Discover sandbox plugins, sort by priority (highest first), and try to
|
|
302
|
+
* create a backend from each until one succeeds.
|
|
303
|
+
*
|
|
304
|
+
* Unlike other wire functions, sandbox plugins are not registered into a
|
|
305
|
+
* global registry — the caller receives a single backend instance directly.
|
|
306
|
+
*
|
|
307
|
+
* NOTE: In practice, called lazily from getExploreBackend() on the first
|
|
308
|
+
* explore command (not at startup), because the explore backend is cached
|
|
309
|
+
* as a singleton and depends on runtime environment detection.
|
|
310
|
+
*/
|
|
311
|
+
export async function wireSandboxPlugins(
|
|
312
|
+
pluginRegistry: PluginRegistry,
|
|
313
|
+
semanticRoot: string,
|
|
314
|
+
): Promise<{
|
|
315
|
+
backend: SandboxExecBackend | null;
|
|
316
|
+
pluginId: string | null;
|
|
317
|
+
failed: Array<{ pluginId: string; error: string }>;
|
|
318
|
+
}> {
|
|
319
|
+
const sandboxPlugins = pluginRegistry.getByType("sandbox");
|
|
320
|
+
const failed: Array<{ pluginId: string; error: string }> = [];
|
|
321
|
+
|
|
322
|
+
if (sandboxPlugins.length === 0) {
|
|
323
|
+
return { backend: null, pluginId: null, failed };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Filter to valid sandbox plugins, sort by priority descending
|
|
327
|
+
const valid = sandboxPlugins.filter(hasSandbox);
|
|
328
|
+
if (valid.length === 0) {
|
|
329
|
+
for (const sp of sandboxPlugins) {
|
|
330
|
+
log.warn({ pluginId: sp.id }, "Sandbox plugin missing sandbox.create() — skipped");
|
|
331
|
+
}
|
|
332
|
+
return { backend: null, pluginId: null, failed };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const sorted = [...valid].sort((a, b) => {
|
|
336
|
+
const pa = a.sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
|
|
337
|
+
const pb = b.sandbox.priority ?? SANDBOX_DEFAULT_PRIORITY;
|
|
338
|
+
return pb - pa;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
for (const sp of sorted) {
|
|
342
|
+
try {
|
|
343
|
+
const backend = await sp.sandbox.create(semanticRoot);
|
|
344
|
+
if (!backend || typeof backend.exec !== "function") {
|
|
345
|
+
const msg = "create() returned invalid backend (missing exec method)";
|
|
346
|
+
failed.push({ pluginId: sp.id, error: msg });
|
|
347
|
+
log.error({ pluginId: sp.id }, msg);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
log.info({ pluginId: sp.id }, "Using sandbox plugin for explore backend");
|
|
351
|
+
return { backend, pluginId: sp.id, failed };
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
354
|
+
failed.push({ pluginId: sp.id, error: msg });
|
|
355
|
+
log.error(
|
|
356
|
+
{ pluginId: sp.id, err: err instanceof Error ? err : new Error(String(err)) },
|
|
357
|
+
"Sandbox plugin create() failed, trying next",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
log.error({ count: sorted.length }, "All sandbox plugins failed to create a backend");
|
|
363
|
+
return { backend: null, pluginId: null, failed };
|
|
364
|
+
}
|
|
365
|
+
|
|
257
366
|
/**
|
|
258
367
|
* For each healthy context plugin, call `contextProvider.load()` and collect
|
|
259
368
|
* the returned text fragments. Fragments are injected into the agent system
|