spine-framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.framework/README.md +129 -0
- package/.framework/cli/bin.cjs +14 -0
- package/.framework/cli/commands/agents.ts +153 -0
- package/.framework/cli/commands/auth.ts +94 -0
- package/.framework/cli/commands/create-app.ts +185 -0
- package/.framework/cli/commands/dev.ts +295 -0
- package/.framework/cli/commands/doctor.ts +442 -0
- package/.framework/cli/commands/generate.ts +332 -0
- package/.framework/cli/commands/init.ts +272 -0
- package/.framework/cli/commands/install-app.ts +391 -0
- package/.framework/cli/commands/items.ts +253 -0
- package/.framework/cli/commands/migrations.ts +141 -0
- package/.framework/cli/commands/pipelines.ts +166 -0
- package/.framework/cli/commands/status.ts +197 -0
- package/.framework/cli/commands/system.ts +184 -0
- package/.framework/cli/commands/test.ts +227 -0
- package/.framework/cli/commands/uninstall-app.ts +166 -0
- package/.framework/cli/context.ts +268 -0
- package/.framework/cli/env-loader.ts +36 -0
- package/.framework/cli/index.ts +106 -0
- package/.framework/cli/welcome.cjs +45 -0
- package/.framework/docs/API.md +384 -0
- package/.framework/docs/STABILITY.md +52 -0
- package/.framework/docs/admin-routes.md +76 -0
- package/.framework/docs/api-docs-progress.md +38 -0
- package/.framework/docs/api-governance.md +146 -0
- package/.framework/docs/api-testing-results.md +212 -0
- package/.framework/docs/apis/admin-configs.md +567 -0
- package/.framework/docs/apis/admin-data.md +272 -0
- package/.framework/docs/apis/index.md +231 -0
- package/.framework/docs/apis/internal.md +295 -0
- package/.framework/docs/apis/runtime.md +537 -0
- package/.framework/docs/assembly-launch-guide.md +138 -0
- package/.framework/docs/audit-results.md +590 -0
- package/.framework/docs/authorization-model.md +170 -0
- package/.framework/docs/db-api-inventory.md +95 -0
- package/.framework/docs/examples/custom-app/README.md +77 -0
- package/.framework/docs/examples/custom-function/README.md +27 -0
- package/.framework/docs/examples/custom-function/handler.ts +48 -0
- package/.framework/docs/examples/custom-webhook/README.md +68 -0
- package/.framework/docs/gap-remediation-backlog.md +103 -0
- package/.framework/docs/guides/cli-guide.md +224 -0
- package/.framework/docs/guides/getting-started.md +103 -0
- package/.framework/docs/guides/import-guide.md +193 -0
- package/.framework/docs/guides/testing-guide.md +229 -0
- package/.framework/docs/permission-examples.md +326 -0
- package/.framework/docs/ui-adoption-verification.md +111 -0
- package/.framework/docs/ui-api-coverage.md +84 -0
- package/.framework/docs/v2-compatibility-audit.md +228 -0
- package/.framework/functions/.gitkeep +1 -0
- package/.framework/functions/_shared/agent-runner.ts +1097 -0
- package/.framework/functions/_shared/app-manifest.ts +184 -0
- package/.framework/functions/_shared/audit.ts +150 -0
- package/.framework/functions/_shared/db.ts +174 -0
- package/.framework/functions/_shared/index.ts +382 -0
- package/.framework/functions/_shared/middleware.ts +490 -0
- package/.framework/functions/_shared/permissions.ts +1325 -0
- package/.framework/functions/_shared/pipeline-runner.ts +731 -0
- package/.framework/functions/_shared/principal.ts +760 -0
- package/.framework/functions/_shared/schema-utils.ts +967 -0
- package/.framework/functions/_shared/testing.ts +258 -0
- package/.framework/functions/_shared/trigger-engine.ts +425 -0
- package/.framework/functions/_shared/webhook-registration.ts +168 -0
- package/.framework/functions/_shared/webhook-registry.ts +129 -0
- package/.framework/functions/account-nodes.ts +111 -0
- package/.framework/functions/admin-data.ts +606 -0
- package/.framework/functions/ai-agents.ts +323 -0
- package/.framework/functions/api-keys.ts +376 -0
- package/.framework/functions/apps.ts +483 -0
- package/.framework/functions/auth.ts +196 -0
- package/.framework/functions/debug-auth.ts +107 -0
- package/.framework/functions/embeddings.ts +556 -0
- package/.framework/functions/integration-routes.ts +523 -0
- package/.framework/functions/integrations.ts +319 -0
- package/.framework/functions/item-progress.ts +272 -0
- package/.framework/functions/logs.ts +438 -0
- package/.framework/functions/observability.ts +275 -0
- package/.framework/functions/pipeline-executions.ts +494 -0
- package/.framework/functions/pipelines.ts +485 -0
- package/.framework/functions/prompt-configs.ts +339 -0
- package/.framework/functions/roles.ts +387 -0
- package/.framework/functions/system-cron.ts +742 -0
- package/.framework/functions/system.ts +323 -0
- package/.framework/functions/tests.ts +119 -0
- package/.framework/functions/timers.ts +357 -0
- package/.framework/functions/triggers.ts +563 -0
- package/.framework/functions/types.ts +604 -0
- package/.framework/migrations/000_foundation.sql +1256 -0
- package/.framework/migrations/001_seed.sql +92 -0
- package/.framework/migrations/002_seed_constraints.sql +13 -0
- package/.framework/migrations/003_auth_user_trigger.sql +59 -0
- package/.framework/src/App.tsx +126 -0
- package/.framework/src/apps/admin/index.tsx +173 -0
- package/.framework/src/components/AppWrapper.tsx +56 -0
- package/.framework/src/components/CustomAppLoader.tsx +116 -0
- package/.framework/src/components/admin/AdminListPage.tsx +151 -0
- package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
- package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
- package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
- package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
- package/.framework/src/components/app-sidebar.tsx +228 -0
- package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
- package/.framework/src/components/layout/AppShell.tsx +91 -0
- package/.framework/src/components/layout/Header.tsx +88 -0
- package/.framework/src/components/layout/Layout.tsx +95 -0
- package/.framework/src/components/layout/Sidebar.tsx +329 -0
- package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
- package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
- package/.framework/src/components/runtime/DataFilters.tsx +91 -0
- package/.framework/src/components/runtime/DataHeader.tsx +68 -0
- package/.framework/src/components/runtime/DataListPage.tsx +124 -0
- package/.framework/src/components/runtime/DataStats.tsx +70 -0
- package/.framework/src/components/runtime/DataTable.tsx +174 -0
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
- package/.framework/src/components/runtime/index.ts +18 -0
- package/.framework/src/components/search-form.tsx +29 -0
- package/.framework/src/components/shared/AgentView.tsx +213 -0
- package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
- package/.framework/src/components/shared/SchemaFields.tsx +226 -0
- package/.framework/src/components/ui/DataTable.tsx +343 -0
- package/.framework/src/components/ui/Form.tsx +281 -0
- package/.framework/src/components/ui/ItemCard.tsx +296 -0
- package/.framework/src/components/ui/ItemListView.tsx +308 -0
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
- package/.framework/src/components/ui/Modal.tsx +61 -0
- package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
- package/.framework/src/components/ui/accordion.tsx +82 -0
- package/.framework/src/components/ui/alert-dialog.tsx +197 -0
- package/.framework/src/components/ui/alert.tsx +76 -0
- package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
- package/.framework/src/components/ui/avatar.tsx +110 -0
- package/.framework/src/components/ui/badge.tsx +49 -0
- package/.framework/src/components/ui/breadcrumb.tsx +122 -0
- package/.framework/src/components/ui/button-group.tsx +83 -0
- package/.framework/src/components/ui/button.tsx +65 -0
- package/.framework/src/components/ui/calendar.tsx +222 -0
- package/.framework/src/components/ui/card.tsx +100 -0
- package/.framework/src/components/ui/carousel.tsx +240 -0
- package/.framework/src/components/ui/chart.tsx +373 -0
- package/.framework/src/components/ui/checkbox.tsx +31 -0
- package/.framework/src/components/ui/collapsible.tsx +33 -0
- package/.framework/src/components/ui/combobox.tsx +299 -0
- package/.framework/src/components/ui/command.tsx +193 -0
- package/.framework/src/components/ui/context-menu.tsx +261 -0
- package/.framework/src/components/ui/dialog.tsx +165 -0
- package/.framework/src/components/ui/direction.tsx +22 -0
- package/.framework/src/components/ui/drawer.tsx +132 -0
- package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
- package/.framework/src/components/ui/empty.tsx +104 -0
- package/.framework/src/components/ui/field.tsx +238 -0
- package/.framework/src/components/ui/hover-card.tsx +42 -0
- package/.framework/src/components/ui/input-group.tsx +153 -0
- package/.framework/src/components/ui/input-otp.tsx +87 -0
- package/.framework/src/components/ui/input.tsx +19 -0
- package/.framework/src/components/ui/item.tsx +196 -0
- package/.framework/src/components/ui/kbd.tsx +26 -0
- package/.framework/src/components/ui/label.tsx +22 -0
- package/.framework/src/components/ui/menubar.tsx +277 -0
- package/.framework/src/components/ui/native-select.tsx +61 -0
- package/.framework/src/components/ui/navigation-menu.tsx +164 -0
- package/.framework/src/components/ui/pagination.tsx +129 -0
- package/.framework/src/components/ui/popover.tsx +87 -0
- package/.framework/src/components/ui/progress.tsx +31 -0
- package/.framework/src/components/ui/radio-group.tsx +42 -0
- package/.framework/src/components/ui/resizable.tsx +50 -0
- package/.framework/src/components/ui/scroll-area.tsx +53 -0
- package/.framework/src/components/ui/select.tsx +195 -0
- package/.framework/src/components/ui/separator.tsx +26 -0
- package/.framework/src/components/ui/sheet.tsx +145 -0
- package/.framework/src/components/ui/sidebar.tsx +706 -0
- package/.framework/src/components/ui/skeleton.tsx +13 -0
- package/.framework/src/components/ui/slider.tsx +59 -0
- package/.framework/src/components/ui/sonner.tsx +47 -0
- package/.framework/src/components/ui/spinner.tsx +10 -0
- package/.framework/src/components/ui/switch.tsx +33 -0
- package/.framework/src/components/ui/table-primitives.tsx +141 -0
- package/.framework/src/components/ui/table.tsx +114 -0
- package/.framework/src/components/ui/tabs.tsx +90 -0
- package/.framework/src/components/ui/textarea.tsx +18 -0
- package/.framework/src/components/ui/toggle-group.tsx +89 -0
- package/.framework/src/components/ui/toggle.tsx +45 -0
- package/.framework/src/components/ui/tooltip.tsx +57 -0
- package/.framework/src/contexts/AppContext.tsx +133 -0
- package/.framework/src/contexts/AuthContext.tsx +371 -0
- package/.framework/src/hooks/use-mobile.ts +19 -0
- package/.framework/src/hooks/useApi.ts +526 -0
- package/.framework/src/hooks/useApps.ts +114 -0
- package/.framework/src/hooks/useEntityList.ts +190 -0
- package/.framework/src/hooks/useEntityRecord.ts +308 -0
- package/.framework/src/hooks/useForm.ts +307 -0
- package/.framework/src/hooks/useListSchema.ts +264 -0
- package/.framework/src/hooks/useSchemaRecord.ts +223 -0
- package/.framework/src/index.css +128 -0
- package/.framework/src/lib/api.ts +156 -0
- package/.framework/src/lib/supabase.ts +94 -0
- package/.framework/src/lib/utils.ts +317 -0
- package/.framework/src/main.tsx +27 -0
- package/.framework/src/pages/DashboardPage.tsx +181 -0
- package/.framework/src/pages/NotFoundPage.tsx +39 -0
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
- package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
- package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
- package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
- package/.framework/src/pages/admin/AppsPage.tsx +355 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
- package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
- package/.framework/src/pages/admin/LogsPage.tsx +283 -0
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
- package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
- package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
- package/.framework/src/pages/admin/RolesPage.tsx +292 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
- package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
- package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
- package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
- package/.framework/src/pages/admin/TimersPage.tsx +376 -0
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
- package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
- package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
- package/.framework/src/pages/admin/TypesPage.tsx +295 -0
- package/.framework/src/pages/auth/LoginPage.tsx +188 -0
- package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
- package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
- package/.framework/src/types/auth.ts +125 -0
- package/.framework/src/types/types.ts +407 -0
- package/STRUCTURE.md +150 -0
- package/config/components.json +25 -0
- package/config/deno.lock +108 -0
- package/config/package-lock.json +17183 -0
- package/config/postcss.config.cjs +10 -0
- package/config/tailwind.config.cjs +78 -0
- package/config/tsconfig.build.json +32 -0
- package/config/tsconfig.cli.json +18 -0
- package/config/tsconfig.json +41 -0
- package/config/tsconfig.node.json +17 -0
- package/config/tsconfig.node.tsbuildinfo +1 -0
- package/config/tsconfig.tsbuildinfo +1 -0
- package/config/typedoc.json +16 -0
- package/config/vite.config.d.ts +2 -0
- package/config/vite.config.ts +72 -0
- package/dist/cli/commands/agents.d.ts +39 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/auth.d.ts +36 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/create-app.d.ts +23 -0
- package/dist/cli/commands/create-app.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +39 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +42 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +36 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +30 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/install-app.d.ts +30 -0
- package/dist/cli/commands/install-app.d.ts.map +1 -0
- package/dist/cli/commands/items.d.ts +45 -0
- package/dist/cli/commands/items.d.ts.map +1 -0
- package/dist/cli/commands/migrations.d.ts +41 -0
- package/dist/cli/commands/migrations.d.ts.map +1 -0
- package/dist/cli/commands/pipelines.d.ts +40 -0
- package/dist/cli/commands/pipelines.d.ts.map +1 -0
- package/dist/cli/commands/status.d.ts +23 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/system.d.ts +29 -0
- package/dist/cli/commands/system.d.ts.map +1 -0
- package/dist/cli/commands/test.d.ts +46 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/uninstall-app.d.ts +23 -0
- package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
- package/dist/cli/context.d.ts +88 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/env-loader.d.ts +14 -0
- package/dist/cli/env-loader.d.ts.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/functions/_shared/agent-runner.d.ts +156 -0
- package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
- package/dist/functions/_shared/app-manifest.d.ts +68 -0
- package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
- package/dist/functions/_shared/audit.d.ts +91 -0
- package/dist/functions/_shared/audit.d.ts.map +1 -0
- package/dist/functions/_shared/db.d.ts +125 -0
- package/dist/functions/_shared/db.d.ts.map +1 -0
- package/dist/functions/_shared/index.d.ts +298 -0
- package/dist/functions/_shared/index.d.ts.map +1 -0
- package/dist/functions/_shared/middleware.d.ts +315 -0
- package/dist/functions/_shared/middleware.d.ts.map +1 -0
- package/dist/functions/_shared/permissions.d.ts +626 -0
- package/dist/functions/_shared/permissions.d.ts.map +1 -0
- package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
- package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
- package/dist/functions/_shared/principal.d.ts +284 -0
- package/dist/functions/_shared/principal.d.ts.map +1 -0
- package/dist/functions/_shared/schema-utils.d.ts +181 -0
- package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
- package/dist/functions/_shared/testing.d.ts +172 -0
- package/dist/functions/_shared/testing.d.ts.map +1 -0
- package/dist/functions/_shared/trigger-engine.d.ts +140 -0
- package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registration.d.ts +81 -0
- package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registry.d.ts +57 -0
- package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
- package/dist/functions/account-nodes.d.ts +48 -0
- package/dist/functions/account-nodes.d.ts.map +1 -0
- package/dist/functions/admin-data.d.ts +178 -0
- package/dist/functions/admin-data.d.ts.map +1 -0
- package/dist/functions/ai-agents.d.ts +125 -0
- package/dist/functions/ai-agents.d.ts.map +1 -0
- package/dist/functions/api-keys.d.ts +140 -0
- package/dist/functions/api-keys.d.ts.map +1 -0
- package/dist/functions/apps.d.ts +163 -0
- package/dist/functions/apps.d.ts.map +1 -0
- package/dist/functions/auth.d.ts +74 -0
- package/dist/functions/auth.d.ts.map +1 -0
- package/dist/functions/debug-auth.d.ts +33 -0
- package/dist/functions/debug-auth.d.ts.map +1 -0
- package/dist/functions/embeddings.d.ts +205 -0
- package/dist/functions/embeddings.d.ts.map +1 -0
- package/dist/functions/integration-routes.d.ts +45 -0
- package/dist/functions/integration-routes.d.ts.map +1 -0
- package/dist/functions/integrations.d.ts +124 -0
- package/dist/functions/integrations.d.ts.map +1 -0
- package/dist/functions/item-progress.d.ts +41 -0
- package/dist/functions/item-progress.d.ts.map +1 -0
- package/dist/functions/logs.d.ts +162 -0
- package/dist/functions/logs.d.ts.map +1 -0
- package/dist/functions/observability.d.ts +123 -0
- package/dist/functions/observability.d.ts.map +1 -0
- package/dist/functions/pipeline-executions.d.ts +190 -0
- package/dist/functions/pipeline-executions.d.ts.map +1 -0
- package/dist/functions/pipelines.d.ts +171 -0
- package/dist/functions/pipelines.d.ts.map +1 -0
- package/dist/functions/prompt-configs.d.ts +125 -0
- package/dist/functions/prompt-configs.d.ts.map +1 -0
- package/dist/functions/roles.d.ts +118 -0
- package/dist/functions/roles.d.ts.map +1 -0
- package/dist/functions/system-cron.d.ts +65 -0
- package/dist/functions/system-cron.d.ts.map +1 -0
- package/dist/functions/system.d.ts +29 -0
- package/dist/functions/system.d.ts.map +1 -0
- package/dist/functions/tests.d.ts +28 -0
- package/dist/functions/tests.d.ts.map +1 -0
- package/dist/functions/timers.d.ts +139 -0
- package/dist/functions/timers.d.ts.map +1 -0
- package/dist/functions/triggers.d.ts +203 -0
- package/dist/functions/triggers.d.ts.map +1 -0
- package/dist/functions/types.d.ts +151 -0
- package/dist/functions/types.d.ts.map +1 -0
- package/dist/src/types/types.d.ts +364 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/package.json +192 -0
- package/scripts/app-install-cli.ts +286 -0
- package/scripts/assemble-frontend.sh +79 -0
- package/scripts/assemble-functions.sh +62 -0
- package/scripts/assemble.sh +35 -0
- package/scripts/boundary-check.sh +106 -0
- package/scripts/build-manifest.sh +80 -0
- package/scripts/check-core-integrity.sh +82 -0
- package/scripts/ingest-chunks.cjs +202 -0
- package/scripts/kb-chunk-parser.cjs +312 -0
- package/scripts/kb-chunk-parser.ts +330 -0
- package/scripts/load-test-app-install.ts +484 -0
- package/scripts/netlify-dev-wrapper.sh +22 -0
- package/scripts/verify-integrity.sh +69 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module pipeline-runner
|
|
3
|
+
* @audience both
|
|
4
|
+
* @layer shared-core
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* Sequential pipeline execution engine. A pipeline is a named list of stages
|
|
8
|
+
* (`PipelineStage[]`) stored in `pipelines.stages`. Each stage references an
|
|
9
|
+
* `action` record by `stage_type` slug. Stages run sequentially; a failed stage
|
|
10
|
+
* halts execution unless `stage.continue_on_error` is set.
|
|
11
|
+
*
|
|
12
|
+
* Every execution creates a `pipeline_executions` row (status: running →
|
|
13
|
+
* completed | failed) and emits an audit log on completion or failure.
|
|
14
|
+
*
|
|
15
|
+
* Built-in stage handlers (no external config required):
|
|
16
|
+
* - `update_item` — update a record by ID
|
|
17
|
+
* - `create_record` — insert a new record
|
|
18
|
+
* - `http_request` — fetch any HTTP endpoint
|
|
19
|
+
* - `send_notification`— insert watchers notification rows
|
|
20
|
+
* - `run_pipeline` — trigger a nested pipeline (self-recursion blocked)
|
|
21
|
+
* - `search_knowledge` — full-text + fallback search on embeddings table
|
|
22
|
+
* - `query_items` — filtered query on any table
|
|
23
|
+
* - `agent_inference` — call an external LLM webhook or return a mock
|
|
24
|
+
*
|
|
25
|
+
* INVARIANT: `adminDb` (service role) is used for all DB writes — stage
|
|
26
|
+
* execution runs as the pipeline's machine principal, not the end user.
|
|
27
|
+
* INVARIANT: `run_pipeline` handler blocks self-referential execution
|
|
28
|
+
* (`config._pipelineId === pipeline_id`) to prevent infinite loops.
|
|
29
|
+
* INVARIANT: `runPipeline` never throws — failures are captured in the
|
|
30
|
+
* returned `ExecutionResult` with `status: 'failed'`.
|
|
31
|
+
*
|
|
32
|
+
* @seeAlso trigger-engine.ts (calls runPipeline when a trigger fires)
|
|
33
|
+
* @seeAlso agent-runner.ts (calls runPipeline for agentic tool calls)
|
|
34
|
+
* @seeAlso audit.ts (emitAudit called on pipeline.completed / pipeline.failed)
|
|
35
|
+
* @seeAlso index.ts (runPipeline re-exported for v2-custom/ and CLI)
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { CoreContext } from './middleware'
|
|
39
|
+
import { adminDb } from './db'
|
|
40
|
+
import { emitAudit } from './audit'
|
|
41
|
+
|
|
42
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
43
|
+
function isUuid(v: string | undefined | null): v is string { return !!v && UUID_RE.test(v) }
|
|
44
|
+
|
|
45
|
+
// ─── TYPES ──────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Result of a single pipeline stage execution.
|
|
49
|
+
*
|
|
50
|
+
* @outputSpec stageIndex: number — 0-based position in the pipeline's stages array
|
|
51
|
+
* @outputSpec stageType: string — action slug (e.g. 'update_item', 'http_request')
|
|
52
|
+
* @outputSpec status: 'success' | 'failed' | 'skipped'
|
|
53
|
+
* @outputSpec output: any | undefined — handler return value on success
|
|
54
|
+
* @outputSpec error: string | undefined — error message on failure
|
|
55
|
+
* @outputSpec durationMs: number — wall-clock time for this stage
|
|
56
|
+
*/
|
|
57
|
+
export interface StageResult {
|
|
58
|
+
stageIndex: number
|
|
59
|
+
stageType: string
|
|
60
|
+
status: 'success' | 'failed' | 'skipped'
|
|
61
|
+
output?: any
|
|
62
|
+
error?: string
|
|
63
|
+
durationMs: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Top-level result of a `runPipeline` call. Stored in `pipeline_executions.result`.
|
|
68
|
+
*
|
|
69
|
+
* @outputSpec executionId: string — UUID of the pipeline_executions row
|
|
70
|
+
* @outputSpec pipelineId: string — UUID of the pipeline that was run
|
|
71
|
+
* @outputSpec status: 'completed' | 'failed' | 'cancelled'
|
|
72
|
+
* @outputSpec stages: StageResult[] — per-stage results in execution order
|
|
73
|
+
* @outputSpec durationMs: number — total wall-clock time
|
|
74
|
+
* @outputSpec error: string | undefined — top-level error message on failure
|
|
75
|
+
*/
|
|
76
|
+
export interface ExecutionResult {
|
|
77
|
+
executionId: string
|
|
78
|
+
pipelineId: string
|
|
79
|
+
status: 'completed' | 'failed' | 'cancelled'
|
|
80
|
+
stages: StageResult[]
|
|
81
|
+
durationMs: number
|
|
82
|
+
error?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Single stage definition as stored in `pipelines.stages` JSONB array.
|
|
87
|
+
*
|
|
88
|
+
* @inputSpec stage_type: string — action.slug to look up the handler
|
|
89
|
+
* @inputSpec config: Record<string, any> | undefined — merged with action defaults
|
|
90
|
+
* @inputSpec continue_on_error: boolean | undefined — if true, failure doesn’t halt
|
|
91
|
+
*/
|
|
92
|
+
interface PipelineStage {
|
|
93
|
+
stage_type: string
|
|
94
|
+
config?: Record<string, any>
|
|
95
|
+
continue_on_error?: boolean
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── PRIMARY EXPORT ────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Executes a pipeline by ID, running its stages sequentially.
|
|
102
|
+
*
|
|
103
|
+
* Execution lifecycle:
|
|
104
|
+
* 1. Load pipeline record (`pipelines` table, `is_active = true`)
|
|
105
|
+
* 2. Insert `pipeline_executions` row with `status: 'running'`
|
|
106
|
+
* 3. For each stage: load action → merge config → call `executeStage`
|
|
107
|
+
* 4. On stage failure: push failed StageResult; stop unless `continue_on_error`
|
|
108
|
+
* 5. Update `pipeline_executions` to `completed` or `failed`
|
|
109
|
+
* 6. Emit `pipeline.completed` or `pipeline.failed` audit log
|
|
110
|
+
* 7. Return `ExecutionResult` (never throws; failures are in result.status)
|
|
111
|
+
*
|
|
112
|
+
* @param pipelineId - UUID of the pipeline to run (must be active)
|
|
113
|
+
* @param triggerData - Arbitrary context passed to each stage as `_triggerData`
|
|
114
|
+
* @param ctx - CoreContext with principal and accountId for audit and stage writes
|
|
115
|
+
* @returns Promise<ExecutionResult> — always resolves; never throws
|
|
116
|
+
* @throws Error('Pipeline not found or inactive') — only if pipeline lookup fails
|
|
117
|
+
* before execution begins (throws before creating the execution row)
|
|
118
|
+
* @throws Error('Failed to create execution') — if execution row insert fails
|
|
119
|
+
* @inputSpec pipelineId: string — valid UUID of a pipeline in pipelines table
|
|
120
|
+
* @inputSpec triggerData: any — JSON-serializable; stored in trigger_data column
|
|
121
|
+
* @inputSpec ctx.accountId: string | null — stamped on execution row
|
|
122
|
+
* @inputSpec ctx.principal.id: string — stamped as created_by on execution row
|
|
123
|
+
* @outputSpec ExecutionResult with status, stages, durationMs
|
|
124
|
+
* @sideEffects DB write: inserts pipeline_executions row; updates it on completion
|
|
125
|
+
* @sideEffects DB write: emitAudit to logs table
|
|
126
|
+
* @calledBy trigger-engine.ts (on trigger fire)
|
|
127
|
+
* @calledBy agent-runner.ts (tool call dispatch)
|
|
128
|
+
* @calledBy stageHandlers.run_pipeline (nested execution)
|
|
129
|
+
* @calledBy v2-custom/ import callers and CLI
|
|
130
|
+
* @calls executeStage, adminDb, emitAudit
|
|
131
|
+
* @testUnit tests/unit/pipeline-runner.test.ts
|
|
132
|
+
* @testIntegration tests/integration/pipeline-runner.test.ts
|
|
133
|
+
*
|
|
134
|
+
* @example API handler trigger
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { runPipeline } from './_shared/index'
|
|
137
|
+
* const result = await runPipeline(body.pipeline_id, body.data, ctx)
|
|
138
|
+
* if (result.status === 'failed') return error(result.error!, 500)
|
|
139
|
+
* return result
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @example Import usage (v2-custom/)
|
|
143
|
+
* ```ts
|
|
144
|
+
* import { runPipeline, SYSTEM_PRINCIPAL, adminDb, CoreContext } from '../_shared/index'
|
|
145
|
+
* const ctx: CoreContext = { principal: SYSTEM_PRINCIPAL, accountId: MY_ACCOUNT, db: adminDb, requestId: crypto.randomUUID() }
|
|
146
|
+
* const result = await runPipeline('pipeline-uuid', { source: 'import' }, ctx)
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export async function runPipeline(
|
|
150
|
+
pipelineId: string,
|
|
151
|
+
triggerData: any,
|
|
152
|
+
ctx: CoreContext
|
|
153
|
+
): Promise<ExecutionResult> {
|
|
154
|
+
const startTime = Date.now()
|
|
155
|
+
|
|
156
|
+
// Load pipeline definition
|
|
157
|
+
const { data: pipeline, error: pipelineError } = await adminDb
|
|
158
|
+
.from('pipelines')
|
|
159
|
+
.select('*')
|
|
160
|
+
.eq('id', pipelineId)
|
|
161
|
+
.eq('is_active', true)
|
|
162
|
+
.single()
|
|
163
|
+
|
|
164
|
+
if (pipelineError || !pipeline) {
|
|
165
|
+
throw new Error(`Pipeline not found or inactive: ${pipelineId}`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create execution record
|
|
169
|
+
const { data: execution, error: execError } = await adminDb
|
|
170
|
+
.from('pipeline_executions')
|
|
171
|
+
.insert({
|
|
172
|
+
pipeline_id: pipelineId,
|
|
173
|
+
status: 'running',
|
|
174
|
+
trigger_data: triggerData || {},
|
|
175
|
+
account_id: ctx.accountId,
|
|
176
|
+
created_by: isUuid(ctx.principal?.id) ? ctx.principal?.id : null,
|
|
177
|
+
started_at: new Date().toISOString()
|
|
178
|
+
})
|
|
179
|
+
.select()
|
|
180
|
+
.single()
|
|
181
|
+
|
|
182
|
+
if (execError || !execution) {
|
|
183
|
+
throw new Error(`Failed to create execution: ${execError?.message}`)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const executionId = execution.id
|
|
187
|
+
const stageResults: StageResult[] = []
|
|
188
|
+
const stages: PipelineStage[] = pipeline.stages || []
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
// Execute each stage sequentially
|
|
192
|
+
for (let i = 0; i < stages.length; i++) {
|
|
193
|
+
const stage = stages[i]
|
|
194
|
+
const stageStartTime = Date.now()
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
// Load action for this stage type
|
|
198
|
+
const { data: action, error: actionError } = await adminDb
|
|
199
|
+
.from('actions')
|
|
200
|
+
.select('*')
|
|
201
|
+
.eq('slug', stage.stage_type)
|
|
202
|
+
.eq('is_active', true)
|
|
203
|
+
.single()
|
|
204
|
+
|
|
205
|
+
if (actionError || !action) {
|
|
206
|
+
throw new Error(`Action not found for stage type: ${stage.stage_type}`)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Merge stage config with action defaults
|
|
210
|
+
const mergedConfig = {
|
|
211
|
+
...action.config,
|
|
212
|
+
...stage.config,
|
|
213
|
+
_pipelineId: pipelineId,
|
|
214
|
+
_executionId: executionId,
|
|
215
|
+
_stageIndex: i,
|
|
216
|
+
_triggerData: triggerData
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Execute the stage
|
|
220
|
+
const output = await executeStage(ctx, action, mergedConfig)
|
|
221
|
+
|
|
222
|
+
stageResults.push({
|
|
223
|
+
stageIndex: i,
|
|
224
|
+
stageType: stage.stage_type,
|
|
225
|
+
status: 'success',
|
|
226
|
+
output,
|
|
227
|
+
durationMs: Date.now() - stageStartTime
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
} catch (stageError: any) {
|
|
231
|
+
const errorMessage = stageError.message || 'Stage execution failed'
|
|
232
|
+
|
|
233
|
+
stageResults.push({
|
|
234
|
+
stageIndex: i,
|
|
235
|
+
stageType: stage.stage_type,
|
|
236
|
+
status: 'failed',
|
|
237
|
+
error: errorMessage,
|
|
238
|
+
durationMs: Date.now() - stageStartTime
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// Stop unless continue_on_error is set
|
|
242
|
+
if (!stage.continue_on_error) {
|
|
243
|
+
throw new Error(`Stage ${i} (${stage.stage_type}) failed: ${errorMessage}`)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Complete successfully
|
|
249
|
+
const durationMs = Date.now() - startTime
|
|
250
|
+
|
|
251
|
+
await adminDb
|
|
252
|
+
.from('pipeline_executions')
|
|
253
|
+
.update({
|
|
254
|
+
status: 'completed',
|
|
255
|
+
result: { stages: stageResults },
|
|
256
|
+
completed_at: new Date().toISOString(),
|
|
257
|
+
duration_ms: durationMs
|
|
258
|
+
})
|
|
259
|
+
.eq('id', executionId)
|
|
260
|
+
|
|
261
|
+
await emitAudit(ctx, 'pipeline.completed', {
|
|
262
|
+
type: 'pipeline_execution',
|
|
263
|
+
id: executionId,
|
|
264
|
+
account_id: ctx.accountId ?? undefined
|
|
265
|
+
}, {
|
|
266
|
+
pipeline_id: pipelineId,
|
|
267
|
+
duration_ms: durationMs,
|
|
268
|
+
stages_completed: stageResults.length
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
executionId,
|
|
273
|
+
pipelineId,
|
|
274
|
+
status: 'completed',
|
|
275
|
+
stages: stageResults,
|
|
276
|
+
durationMs
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
} catch (error: any) {
|
|
280
|
+
// Complete with failure
|
|
281
|
+
const durationMs = Date.now() - startTime
|
|
282
|
+
|
|
283
|
+
await adminDb
|
|
284
|
+
.from('pipeline_executions')
|
|
285
|
+
.update({
|
|
286
|
+
status: 'failed',
|
|
287
|
+
result: { stages: stageResults },
|
|
288
|
+
error_message: error.message,
|
|
289
|
+
completed_at: new Date().toISOString(),
|
|
290
|
+
duration_ms: durationMs
|
|
291
|
+
})
|
|
292
|
+
.eq('id', executionId)
|
|
293
|
+
|
|
294
|
+
await emitAudit(ctx, 'pipeline.failed', {
|
|
295
|
+
type: 'pipeline_execution',
|
|
296
|
+
id: executionId,
|
|
297
|
+
account_id: ctx.accountId ?? undefined
|
|
298
|
+
}, {
|
|
299
|
+
pipeline_id: pipelineId,
|
|
300
|
+
error: error.message,
|
|
301
|
+
stages_completed: stageResults.length
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
executionId,
|
|
306
|
+
pipelineId,
|
|
307
|
+
status: 'failed',
|
|
308
|
+
stages: stageResults,
|
|
309
|
+
durationMs,
|
|
310
|
+
error: error.message
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ─── STAGE DISPATCH ────────────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Dispatches a single stage to the correct handler module.
|
|
319
|
+
*
|
|
320
|
+
* Routes based on `action.handler_module`:
|
|
321
|
+
* - `'functions'` → `executeFunctionHandler` (built-in stageHandlers)
|
|
322
|
+
* - `'integrations'` → `executeIntegrationHandler` (loads integration by slug)
|
|
323
|
+
* - `'webhook'` → `executeWebhookHandler` (calls action.config.url)
|
|
324
|
+
*
|
|
325
|
+
* @param ctx - CoreContext
|
|
326
|
+
* @param action - Action record from the `actions` table
|
|
327
|
+
* @param config - Merged stage + action config with `_pipelineId`, `_executionId`,
|
|
328
|
+
* `_stageIndex`, `_triggerData` injected by runPipeline
|
|
329
|
+
* @returns Promise<any> — handler output
|
|
330
|
+
* @throws Error('Unknown handler module') on unrecognised module string
|
|
331
|
+
* @inputSpec action.handler_module: 'functions' | 'integrations' | 'webhook'
|
|
332
|
+
* @inputSpec action.handler: string — handler name or integration slug
|
|
333
|
+
* @sideEffects depends on handler module (DB writes, HTTP calls)
|
|
334
|
+
* @calledBy runPipeline (per-stage loop)
|
|
335
|
+
* @calls executeFunctionHandler | executeIntegrationHandler | executeWebhookHandler
|
|
336
|
+
*/
|
|
337
|
+
async function executeStage(
|
|
338
|
+
ctx: CoreContext,
|
|
339
|
+
action: any,
|
|
340
|
+
config: any
|
|
341
|
+
): Promise<any> {
|
|
342
|
+
const handlerModule = action.handler_module || 'functions'
|
|
343
|
+
const handlerName = action.handler
|
|
344
|
+
|
|
345
|
+
switch (handlerModule) {
|
|
346
|
+
case 'functions':
|
|
347
|
+
return await executeFunctionHandler(ctx, handlerName, config)
|
|
348
|
+
|
|
349
|
+
case 'integrations':
|
|
350
|
+
return await executeIntegrationHandler(ctx, handlerName, config)
|
|
351
|
+
|
|
352
|
+
case 'webhook':
|
|
353
|
+
return await executeWebhookHandler(ctx, action, config)
|
|
354
|
+
|
|
355
|
+
default:
|
|
356
|
+
throw new Error(`Unknown handler module: ${handlerModule}`)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ─── BUILT-IN STAGE HANDLERS ─────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Map of built-in stage handler functions keyed by action slug.
|
|
364
|
+
*
|
|
365
|
+
* Each handler receives `(ctx: CoreContext, config: any)` and returns a
|
|
366
|
+
* JSON-serializable result or throws on failure. Registered handlers:
|
|
367
|
+
*
|
|
368
|
+
* | Slug | Required config keys | Returns |
|
|
369
|
+
* |---------------------|-----------------------------------|--------------------------------|
|
|
370
|
+
* | `update_item` | `entity`, `record_id`, `data` | `{ success, record }` |
|
|
371
|
+
* | `create_record` | `entity`, `data` | `{ success, record }` |
|
|
372
|
+
* | `http_request` | `url` | `{ success, status, body }` |
|
|
373
|
+
* | `send_notification` | `message`, `recipients[]` | `{ success, notified_count }` |
|
|
374
|
+
* | `run_pipeline` | `pipeline_id` | `{ success, nested_result }` |
|
|
375
|
+
* | `search_knowledge` | `query` | `{ results[], count, method }` |
|
|
376
|
+
* | `query_items` | `entity` | `{ items[], count }` |
|
|
377
|
+
* | `agent_inference` | `context` | `{ content, confidence, ... }` |
|
|
378
|
+
*
|
|
379
|
+
* @sideEffects DB writes for update_item, create_record, send_notification
|
|
380
|
+
* @sideEffects HTTP calls for http_request, agent_inference (webhook_url)
|
|
381
|
+
* @calledBy executeFunctionHandler
|
|
382
|
+
*/
|
|
383
|
+
const stageHandlers: Record<string, Function> = {
|
|
384
|
+
'update_item': async (ctx: CoreContext, config: any) => {
|
|
385
|
+
const { entity, record_id, data } = config
|
|
386
|
+
|
|
387
|
+
if (!entity || !record_id) {
|
|
388
|
+
throw new Error('entity and record_id are required for update_item')
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const { data: result, error } = await adminDb
|
|
392
|
+
.from(entity)
|
|
393
|
+
.update({
|
|
394
|
+
...data,
|
|
395
|
+
updated_at: new Date().toISOString(),
|
|
396
|
+
updated_by: ctx.principal?.id
|
|
397
|
+
})
|
|
398
|
+
.eq('id', record_id)
|
|
399
|
+
.select()
|
|
400
|
+
.single()
|
|
401
|
+
|
|
402
|
+
if (error) throw new Error(`Update failed: ${error.message}`)
|
|
403
|
+
return { success: true, record: result }
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
'create_record': async (ctx: CoreContext, config: any) => {
|
|
407
|
+
const { entity, data } = config
|
|
408
|
+
|
|
409
|
+
if (!entity || !data) {
|
|
410
|
+
throw new Error('entity and data are required for create_record')
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const { data: result, error } = await adminDb
|
|
414
|
+
.from(entity)
|
|
415
|
+
.insert({
|
|
416
|
+
...data,
|
|
417
|
+
account_id: ctx.accountId,
|
|
418
|
+
created_by: isUuid(ctx.principal?.id) ? ctx.principal?.id : null,
|
|
419
|
+
created_at: new Date().toISOString(),
|
|
420
|
+
updated_at: new Date().toISOString()
|
|
421
|
+
})
|
|
422
|
+
.select()
|
|
423
|
+
.single()
|
|
424
|
+
|
|
425
|
+
if (error) throw new Error(`Create failed: ${error.message}`)
|
|
426
|
+
return { success: true, record: result }
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
'http_request': async (ctx: CoreContext, config: any) => {
|
|
430
|
+
const { method = 'GET', url, headers = {}, body } = config
|
|
431
|
+
|
|
432
|
+
if (!url) {
|
|
433
|
+
throw new Error('url is required for http_request')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const fetchOptions: RequestInit = {
|
|
437
|
+
method,
|
|
438
|
+
headers: {
|
|
439
|
+
'Content-Type': 'application/json',
|
|
440
|
+
...headers
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
445
|
+
fetchOptions.body = JSON.stringify(body)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const response = await fetch(url, fetchOptions)
|
|
449
|
+
const responseBody = await response.json().catch(() => null)
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
success: response.ok,
|
|
453
|
+
status: response.status,
|
|
454
|
+
statusText: response.statusText,
|
|
455
|
+
body: responseBody
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
'send_notification': async (ctx: CoreContext, config: any) => {
|
|
460
|
+
const { message, entity_type, entity_id, recipients = [] } = config
|
|
461
|
+
|
|
462
|
+
// Create watcher notifications
|
|
463
|
+
const notifications = recipients.map((recipientId: string) => ({
|
|
464
|
+
account_id: ctx.accountId,
|
|
465
|
+
person_id: recipientId,
|
|
466
|
+
message,
|
|
467
|
+
entity_type: entity_type || 'pipeline_execution',
|
|
468
|
+
entity_id: entity_id || ctx.requestId,
|
|
469
|
+
is_read: false,
|
|
470
|
+
created_at: new Date().toISOString()
|
|
471
|
+
}))
|
|
472
|
+
|
|
473
|
+
if (notifications.length > 0) {
|
|
474
|
+
const { error } = await adminDb
|
|
475
|
+
.from('watchers')
|
|
476
|
+
.insert(notifications)
|
|
477
|
+
|
|
478
|
+
if (error) throw new Error(`Notification failed: ${error.message}`)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return { success: true, notified_count: notifications.length }
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
'run_pipeline': async (ctx: CoreContext, config: any) => {
|
|
485
|
+
const { pipeline_id, trigger_data = {} } = config
|
|
486
|
+
|
|
487
|
+
if (!pipeline_id) {
|
|
488
|
+
throw new Error('pipeline_id is required for run_pipeline')
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Prevent infinite recursion
|
|
492
|
+
if (config._pipelineId === pipeline_id) {
|
|
493
|
+
throw new Error('Recursive pipeline execution prevented')
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const result = await runPipeline(pipeline_id, trigger_data, ctx)
|
|
497
|
+
return {
|
|
498
|
+
success: result.status === 'completed',
|
|
499
|
+
execution_id: result.executionId,
|
|
500
|
+
nested_result: result
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
'search_knowledge': async (ctx: CoreContext, config: any) => {
|
|
505
|
+
const { query, knowledge_sources, model_id, limit = 5, threshold = 0.7 } = config
|
|
506
|
+
|
|
507
|
+
if (!query) {
|
|
508
|
+
throw new Error('query is required for search_knowledge')
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Text search fallback (when no embedding provided)
|
|
512
|
+
let dbQuery = adminDb
|
|
513
|
+
.from('embeddings')
|
|
514
|
+
.select('*')
|
|
515
|
+
.eq('account_id', ctx.accountId)
|
|
516
|
+
.limit(limit)
|
|
517
|
+
|
|
518
|
+
if (knowledge_sources && knowledge_sources.length > 0) {
|
|
519
|
+
dbQuery = dbQuery.in('document_id', knowledge_sources)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (model_id) {
|
|
523
|
+
dbQuery = dbQuery.eq('model_id', model_id)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Try text search first
|
|
527
|
+
const { data: textResults, error: textError } = await dbQuery
|
|
528
|
+
.textSearch('content', query, { type: 'websearch', config: 'english' })
|
|
529
|
+
|
|
530
|
+
if (!textError && textResults && textResults.length > 0) {
|
|
531
|
+
return {
|
|
532
|
+
results: textResults,
|
|
533
|
+
count: textResults.length,
|
|
534
|
+
method: 'text_search',
|
|
535
|
+
query
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Fallback to ilike search
|
|
540
|
+
const { data: fallbackResults, error: fallbackError } = await dbQuery
|
|
541
|
+
.ilike('content', `%${query}%`)
|
|
542
|
+
|
|
543
|
+
if (fallbackError) {
|
|
544
|
+
throw new Error(`Knowledge search failed: ${fallbackError.message}`)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
results: fallbackResults || [],
|
|
549
|
+
count: fallbackResults?.length || 0,
|
|
550
|
+
method: 'fallback_ilike',
|
|
551
|
+
query
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
'query_items': async (ctx: CoreContext, config: any) => {
|
|
556
|
+
const { entity, filters = {}, query, limit = 10 } = config
|
|
557
|
+
|
|
558
|
+
if (!entity) {
|
|
559
|
+
throw new Error('entity is required for query_items')
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
let dbQuery = adminDb
|
|
563
|
+
.from(entity)
|
|
564
|
+
.select('*')
|
|
565
|
+
.eq('account_id', ctx.accountId)
|
|
566
|
+
.limit(parseInt(limit.toString()))
|
|
567
|
+
|
|
568
|
+
// Apply filters
|
|
569
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
570
|
+
dbQuery = dbQuery.eq(key, value)
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
// Add text search if query provided
|
|
574
|
+
if (query) {
|
|
575
|
+
dbQuery = dbQuery.textSearch('data', query, { type: 'websearch', config: 'english' })
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const { data, error } = await dbQuery
|
|
579
|
+
|
|
580
|
+
if (error) throw new Error(`Query failed: ${error.message}`)
|
|
581
|
+
return { items: data || [], count: data?.length || 0 }
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
'agent_inference': async (ctx: CoreContext, config: any) => {
|
|
585
|
+
const { context, model, temperature, max_tokens, webhook_url, headers = {} } = config
|
|
586
|
+
|
|
587
|
+
if (!context) {
|
|
588
|
+
throw new Error('context is required for agent_inference')
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// If webhook_url provided, call external LLM service
|
|
592
|
+
if (webhook_url) {
|
|
593
|
+
const response = await fetch(webhook_url, {
|
|
594
|
+
method: 'POST',
|
|
595
|
+
headers: {
|
|
596
|
+
'Content-Type': 'application/json',
|
|
597
|
+
...headers
|
|
598
|
+
},
|
|
599
|
+
body: JSON.stringify({
|
|
600
|
+
context,
|
|
601
|
+
model: model || 'gpt-4o',
|
|
602
|
+
temperature: temperature ?? 0.7,
|
|
603
|
+
max_tokens: max_tokens ?? 4000
|
|
604
|
+
})
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
if (!response.ok) {
|
|
608
|
+
throw new Error(`Inference failed: ${response.status} ${response.statusText}`)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const result: any = await response.json()
|
|
612
|
+
return {
|
|
613
|
+
content: result.content || result.message || '',
|
|
614
|
+
confidence: result.confidence || 0.8,
|
|
615
|
+
tool_calls: result.tool_calls,
|
|
616
|
+
metadata: result.metadata,
|
|
617
|
+
model: model || 'gpt-4o'
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Fallback: return mock for development/testing
|
|
622
|
+
return {
|
|
623
|
+
content: `[Mock Response] Received ${context.length} chars of context. In production, configure webhook_url in action config.`,
|
|
624
|
+
confidence: 0.9,
|
|
625
|
+
tool_calls: null,
|
|
626
|
+
metadata: { mock: true },
|
|
627
|
+
model: model || 'gpt-4o'
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ─── MODULE DISPATCHERS ────────────────────────────────────────────────────────────
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Looks up a built-in handler by name and calls it.
|
|
636
|
+
*
|
|
637
|
+
* @throws Error('Unknown function handler: <name>') if not in stageHandlers map
|
|
638
|
+
* @calledBy executeStage (functions module)
|
|
639
|
+
* @calls stageHandlers[handlerName]
|
|
640
|
+
*/
|
|
641
|
+
async function executeFunctionHandler(
|
|
642
|
+
ctx: CoreContext,
|
|
643
|
+
handlerName: string,
|
|
644
|
+
config: any
|
|
645
|
+
): Promise<any> {
|
|
646
|
+
const handler = stageHandlers[handlerName]
|
|
647
|
+
|
|
648
|
+
if (!handler) {
|
|
649
|
+
throw new Error(`Unknown function handler: ${handlerName}`)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return await handler(ctx, config)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Loads an integration record by slug and executes it.
|
|
657
|
+
*
|
|
658
|
+
* Currently a stub — logs the call and returns the integration ID.
|
|
659
|
+
* Future: delegate to integration-specific executor based on integration type.
|
|
660
|
+
*
|
|
661
|
+
* @throws Error('Integration not found') if no active integration matches slug
|
|
662
|
+
* @sideEffects DB read: integrations table
|
|
663
|
+
* @calledBy executeStage (integrations module)
|
|
664
|
+
*/
|
|
665
|
+
async function executeIntegrationHandler(
|
|
666
|
+
ctx: CoreContext,
|
|
667
|
+
handlerName: string,
|
|
668
|
+
config: any
|
|
669
|
+
): Promise<any> {
|
|
670
|
+
// Load integration configuration
|
|
671
|
+
const { data: integration, error } = await adminDb
|
|
672
|
+
.from('integrations')
|
|
673
|
+
.select('*')
|
|
674
|
+
.eq('slug', handlerName)
|
|
675
|
+
.eq('is_active', true)
|
|
676
|
+
.single()
|
|
677
|
+
|
|
678
|
+
if (error || !integration) {
|
|
679
|
+
throw new Error(`Integration not found: ${handlerName}`)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Execute based on integration type
|
|
683
|
+
// This is a placeholder for actual integration logic
|
|
684
|
+
console.log(`[${ctx.requestId}] Integration handler: ${handlerName}`, config)
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
success: true,
|
|
688
|
+
handler: handlerName,
|
|
689
|
+
integration_id: integration.id
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* POSTs the merged stage config as JSON to the webhook URL from action.config.url.
|
|
695
|
+
*
|
|
696
|
+
* Returns `{ success, status, body }` based on the HTTP response. Does not
|
|
697
|
+
* throw on non-2xx responses — `success: false` is returned instead.
|
|
698
|
+
*
|
|
699
|
+
* @throws Error('Webhook URL not configured') if action.config.url is missing
|
|
700
|
+
* @sideEffects HTTP call to action.config.url
|
|
701
|
+
* @calledBy executeStage (webhook module)
|
|
702
|
+
*/
|
|
703
|
+
async function executeWebhookHandler(
|
|
704
|
+
ctx: CoreContext,
|
|
705
|
+
action: any,
|
|
706
|
+
config: any
|
|
707
|
+
): Promise<any> {
|
|
708
|
+
const { url, method = 'POST', headers = {} } = action.config || {}
|
|
709
|
+
|
|
710
|
+
if (!url) {
|
|
711
|
+
throw new Error('Webhook URL not configured in action')
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const fetchOptions: RequestInit = {
|
|
715
|
+
method,
|
|
716
|
+
headers: {
|
|
717
|
+
'Content-Type': 'application/json',
|
|
718
|
+
...headers
|
|
719
|
+
},
|
|
720
|
+
body: JSON.stringify(config)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const response = await fetch(url, fetchOptions)
|
|
724
|
+
const body = await response.json().catch(() => null)
|
|
725
|
+
|
|
726
|
+
return {
|
|
727
|
+
success: response.ok,
|
|
728
|
+
status: response.status,
|
|
729
|
+
body
|
|
730
|
+
}
|
|
731
|
+
}
|