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.
Files changed (385) hide show
  1. package/.framework/README.md +129 -0
  2. package/.framework/cli/bin.cjs +14 -0
  3. package/.framework/cli/commands/agents.ts +153 -0
  4. package/.framework/cli/commands/auth.ts +94 -0
  5. package/.framework/cli/commands/create-app.ts +185 -0
  6. package/.framework/cli/commands/dev.ts +295 -0
  7. package/.framework/cli/commands/doctor.ts +442 -0
  8. package/.framework/cli/commands/generate.ts +332 -0
  9. package/.framework/cli/commands/init.ts +272 -0
  10. package/.framework/cli/commands/install-app.ts +391 -0
  11. package/.framework/cli/commands/items.ts +253 -0
  12. package/.framework/cli/commands/migrations.ts +141 -0
  13. package/.framework/cli/commands/pipelines.ts +166 -0
  14. package/.framework/cli/commands/status.ts +197 -0
  15. package/.framework/cli/commands/system.ts +184 -0
  16. package/.framework/cli/commands/test.ts +227 -0
  17. package/.framework/cli/commands/uninstall-app.ts +166 -0
  18. package/.framework/cli/context.ts +268 -0
  19. package/.framework/cli/env-loader.ts +36 -0
  20. package/.framework/cli/index.ts +106 -0
  21. package/.framework/cli/welcome.cjs +45 -0
  22. package/.framework/docs/API.md +384 -0
  23. package/.framework/docs/STABILITY.md +52 -0
  24. package/.framework/docs/admin-routes.md +76 -0
  25. package/.framework/docs/api-docs-progress.md +38 -0
  26. package/.framework/docs/api-governance.md +146 -0
  27. package/.framework/docs/api-testing-results.md +212 -0
  28. package/.framework/docs/apis/admin-configs.md +567 -0
  29. package/.framework/docs/apis/admin-data.md +272 -0
  30. package/.framework/docs/apis/index.md +231 -0
  31. package/.framework/docs/apis/internal.md +295 -0
  32. package/.framework/docs/apis/runtime.md +537 -0
  33. package/.framework/docs/assembly-launch-guide.md +138 -0
  34. package/.framework/docs/audit-results.md +590 -0
  35. package/.framework/docs/authorization-model.md +170 -0
  36. package/.framework/docs/db-api-inventory.md +95 -0
  37. package/.framework/docs/examples/custom-app/README.md +77 -0
  38. package/.framework/docs/examples/custom-function/README.md +27 -0
  39. package/.framework/docs/examples/custom-function/handler.ts +48 -0
  40. package/.framework/docs/examples/custom-webhook/README.md +68 -0
  41. package/.framework/docs/gap-remediation-backlog.md +103 -0
  42. package/.framework/docs/guides/cli-guide.md +224 -0
  43. package/.framework/docs/guides/getting-started.md +103 -0
  44. package/.framework/docs/guides/import-guide.md +193 -0
  45. package/.framework/docs/guides/testing-guide.md +229 -0
  46. package/.framework/docs/permission-examples.md +326 -0
  47. package/.framework/docs/ui-adoption-verification.md +111 -0
  48. package/.framework/docs/ui-api-coverage.md +84 -0
  49. package/.framework/docs/v2-compatibility-audit.md +228 -0
  50. package/.framework/functions/.gitkeep +1 -0
  51. package/.framework/functions/_shared/agent-runner.ts +1097 -0
  52. package/.framework/functions/_shared/app-manifest.ts +184 -0
  53. package/.framework/functions/_shared/audit.ts +150 -0
  54. package/.framework/functions/_shared/db.ts +174 -0
  55. package/.framework/functions/_shared/index.ts +382 -0
  56. package/.framework/functions/_shared/middleware.ts +490 -0
  57. package/.framework/functions/_shared/permissions.ts +1325 -0
  58. package/.framework/functions/_shared/pipeline-runner.ts +731 -0
  59. package/.framework/functions/_shared/principal.ts +760 -0
  60. package/.framework/functions/_shared/schema-utils.ts +967 -0
  61. package/.framework/functions/_shared/testing.ts +258 -0
  62. package/.framework/functions/_shared/trigger-engine.ts +425 -0
  63. package/.framework/functions/_shared/webhook-registration.ts +168 -0
  64. package/.framework/functions/_shared/webhook-registry.ts +129 -0
  65. package/.framework/functions/account-nodes.ts +111 -0
  66. package/.framework/functions/admin-data.ts +606 -0
  67. package/.framework/functions/ai-agents.ts +323 -0
  68. package/.framework/functions/api-keys.ts +376 -0
  69. package/.framework/functions/apps.ts +483 -0
  70. package/.framework/functions/auth.ts +196 -0
  71. package/.framework/functions/debug-auth.ts +107 -0
  72. package/.framework/functions/embeddings.ts +556 -0
  73. package/.framework/functions/integration-routes.ts +523 -0
  74. package/.framework/functions/integrations.ts +319 -0
  75. package/.framework/functions/item-progress.ts +272 -0
  76. package/.framework/functions/logs.ts +438 -0
  77. package/.framework/functions/observability.ts +275 -0
  78. package/.framework/functions/pipeline-executions.ts +494 -0
  79. package/.framework/functions/pipelines.ts +485 -0
  80. package/.framework/functions/prompt-configs.ts +339 -0
  81. package/.framework/functions/roles.ts +387 -0
  82. package/.framework/functions/system-cron.ts +742 -0
  83. package/.framework/functions/system.ts +323 -0
  84. package/.framework/functions/tests.ts +119 -0
  85. package/.framework/functions/timers.ts +357 -0
  86. package/.framework/functions/triggers.ts +563 -0
  87. package/.framework/functions/types.ts +604 -0
  88. package/.framework/migrations/000_foundation.sql +1256 -0
  89. package/.framework/migrations/001_seed.sql +92 -0
  90. package/.framework/migrations/002_seed_constraints.sql +13 -0
  91. package/.framework/migrations/003_auth_user_trigger.sql +59 -0
  92. package/.framework/src/App.tsx +126 -0
  93. package/.framework/src/apps/admin/index.tsx +173 -0
  94. package/.framework/src/components/AppWrapper.tsx +56 -0
  95. package/.framework/src/components/CustomAppLoader.tsx +116 -0
  96. package/.framework/src/components/admin/AdminListPage.tsx +151 -0
  97. package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
  98. package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
  99. package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
  100. package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
  101. package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
  102. package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
  103. package/.framework/src/components/app-sidebar.tsx +228 -0
  104. package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
  105. package/.framework/src/components/layout/AppShell.tsx +91 -0
  106. package/.framework/src/components/layout/Header.tsx +88 -0
  107. package/.framework/src/components/layout/Layout.tsx +95 -0
  108. package/.framework/src/components/layout/Sidebar.tsx +329 -0
  109. package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
  110. package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
  111. package/.framework/src/components/runtime/DataFilters.tsx +91 -0
  112. package/.framework/src/components/runtime/DataHeader.tsx +68 -0
  113. package/.framework/src/components/runtime/DataListPage.tsx +124 -0
  114. package/.framework/src/components/runtime/DataStats.tsx +70 -0
  115. package/.framework/src/components/runtime/DataTable.tsx +174 -0
  116. package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
  117. package/.framework/src/components/runtime/index.ts +18 -0
  118. package/.framework/src/components/search-form.tsx +29 -0
  119. package/.framework/src/components/shared/AgentView.tsx +213 -0
  120. package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
  121. package/.framework/src/components/shared/SchemaFields.tsx +226 -0
  122. package/.framework/src/components/ui/DataTable.tsx +343 -0
  123. package/.framework/src/components/ui/Form.tsx +281 -0
  124. package/.framework/src/components/ui/ItemCard.tsx +296 -0
  125. package/.framework/src/components/ui/ItemListView.tsx +308 -0
  126. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
  127. package/.framework/src/components/ui/Modal.tsx +61 -0
  128. package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
  129. package/.framework/src/components/ui/accordion.tsx +82 -0
  130. package/.framework/src/components/ui/alert-dialog.tsx +197 -0
  131. package/.framework/src/components/ui/alert.tsx +76 -0
  132. package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
  133. package/.framework/src/components/ui/avatar.tsx +110 -0
  134. package/.framework/src/components/ui/badge.tsx +49 -0
  135. package/.framework/src/components/ui/breadcrumb.tsx +122 -0
  136. package/.framework/src/components/ui/button-group.tsx +83 -0
  137. package/.framework/src/components/ui/button.tsx +65 -0
  138. package/.framework/src/components/ui/calendar.tsx +222 -0
  139. package/.framework/src/components/ui/card.tsx +100 -0
  140. package/.framework/src/components/ui/carousel.tsx +240 -0
  141. package/.framework/src/components/ui/chart.tsx +373 -0
  142. package/.framework/src/components/ui/checkbox.tsx +31 -0
  143. package/.framework/src/components/ui/collapsible.tsx +33 -0
  144. package/.framework/src/components/ui/combobox.tsx +299 -0
  145. package/.framework/src/components/ui/command.tsx +193 -0
  146. package/.framework/src/components/ui/context-menu.tsx +261 -0
  147. package/.framework/src/components/ui/dialog.tsx +165 -0
  148. package/.framework/src/components/ui/direction.tsx +22 -0
  149. package/.framework/src/components/ui/drawer.tsx +132 -0
  150. package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
  151. package/.framework/src/components/ui/empty.tsx +104 -0
  152. package/.framework/src/components/ui/field.tsx +238 -0
  153. package/.framework/src/components/ui/hover-card.tsx +42 -0
  154. package/.framework/src/components/ui/input-group.tsx +153 -0
  155. package/.framework/src/components/ui/input-otp.tsx +87 -0
  156. package/.framework/src/components/ui/input.tsx +19 -0
  157. package/.framework/src/components/ui/item.tsx +196 -0
  158. package/.framework/src/components/ui/kbd.tsx +26 -0
  159. package/.framework/src/components/ui/label.tsx +22 -0
  160. package/.framework/src/components/ui/menubar.tsx +277 -0
  161. package/.framework/src/components/ui/native-select.tsx +61 -0
  162. package/.framework/src/components/ui/navigation-menu.tsx +164 -0
  163. package/.framework/src/components/ui/pagination.tsx +129 -0
  164. package/.framework/src/components/ui/popover.tsx +87 -0
  165. package/.framework/src/components/ui/progress.tsx +31 -0
  166. package/.framework/src/components/ui/radio-group.tsx +42 -0
  167. package/.framework/src/components/ui/resizable.tsx +50 -0
  168. package/.framework/src/components/ui/scroll-area.tsx +53 -0
  169. package/.framework/src/components/ui/select.tsx +195 -0
  170. package/.framework/src/components/ui/separator.tsx +26 -0
  171. package/.framework/src/components/ui/sheet.tsx +145 -0
  172. package/.framework/src/components/ui/sidebar.tsx +706 -0
  173. package/.framework/src/components/ui/skeleton.tsx +13 -0
  174. package/.framework/src/components/ui/slider.tsx +59 -0
  175. package/.framework/src/components/ui/sonner.tsx +47 -0
  176. package/.framework/src/components/ui/spinner.tsx +10 -0
  177. package/.framework/src/components/ui/switch.tsx +33 -0
  178. package/.framework/src/components/ui/table-primitives.tsx +141 -0
  179. package/.framework/src/components/ui/table.tsx +114 -0
  180. package/.framework/src/components/ui/tabs.tsx +90 -0
  181. package/.framework/src/components/ui/textarea.tsx +18 -0
  182. package/.framework/src/components/ui/toggle-group.tsx +89 -0
  183. package/.framework/src/components/ui/toggle.tsx +45 -0
  184. package/.framework/src/components/ui/tooltip.tsx +57 -0
  185. package/.framework/src/contexts/AppContext.tsx +133 -0
  186. package/.framework/src/contexts/AuthContext.tsx +371 -0
  187. package/.framework/src/hooks/use-mobile.ts +19 -0
  188. package/.framework/src/hooks/useApi.ts +526 -0
  189. package/.framework/src/hooks/useApps.ts +114 -0
  190. package/.framework/src/hooks/useEntityList.ts +190 -0
  191. package/.framework/src/hooks/useEntityRecord.ts +308 -0
  192. package/.framework/src/hooks/useForm.ts +307 -0
  193. package/.framework/src/hooks/useListSchema.ts +264 -0
  194. package/.framework/src/hooks/useSchemaRecord.ts +223 -0
  195. package/.framework/src/index.css +128 -0
  196. package/.framework/src/lib/api.ts +156 -0
  197. package/.framework/src/lib/supabase.ts +94 -0
  198. package/.framework/src/lib/utils.ts +317 -0
  199. package/.framework/src/main.tsx +27 -0
  200. package/.framework/src/pages/DashboardPage.tsx +181 -0
  201. package/.framework/src/pages/NotFoundPage.tsx +39 -0
  202. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
  203. package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
  204. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
  205. package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
  206. package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
  207. package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
  208. package/.framework/src/pages/admin/AppsPage.tsx +355 -0
  209. package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
  210. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
  211. package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
  212. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
  213. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
  214. package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
  215. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
  216. package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
  217. package/.framework/src/pages/admin/LogsPage.tsx +283 -0
  218. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
  219. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
  220. package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
  221. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
  222. package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
  223. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
  224. package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
  225. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
  226. package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
  227. package/.framework/src/pages/admin/RolesPage.tsx +292 -0
  228. package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
  229. package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
  230. package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
  231. package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
  232. package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
  233. package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
  234. package/.framework/src/pages/admin/TimersPage.tsx +376 -0
  235. package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
  236. package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
  237. package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
  238. package/.framework/src/pages/admin/TypesPage.tsx +295 -0
  239. package/.framework/src/pages/auth/LoginPage.tsx +188 -0
  240. package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
  241. package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
  242. package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
  243. package/.framework/src/types/auth.ts +125 -0
  244. package/.framework/src/types/types.ts +407 -0
  245. package/STRUCTURE.md +150 -0
  246. package/config/components.json +25 -0
  247. package/config/deno.lock +108 -0
  248. package/config/package-lock.json +17183 -0
  249. package/config/postcss.config.cjs +10 -0
  250. package/config/tailwind.config.cjs +78 -0
  251. package/config/tsconfig.build.json +32 -0
  252. package/config/tsconfig.cli.json +18 -0
  253. package/config/tsconfig.json +41 -0
  254. package/config/tsconfig.node.json +17 -0
  255. package/config/tsconfig.node.tsbuildinfo +1 -0
  256. package/config/tsconfig.tsbuildinfo +1 -0
  257. package/config/typedoc.json +16 -0
  258. package/config/vite.config.d.ts +2 -0
  259. package/config/vite.config.ts +72 -0
  260. package/dist/cli/commands/agents.d.ts +39 -0
  261. package/dist/cli/commands/agents.d.ts.map +1 -0
  262. package/dist/cli/commands/auth.d.ts +36 -0
  263. package/dist/cli/commands/auth.d.ts.map +1 -0
  264. package/dist/cli/commands/create-app.d.ts +23 -0
  265. package/dist/cli/commands/create-app.d.ts.map +1 -0
  266. package/dist/cli/commands/dev.d.ts +39 -0
  267. package/dist/cli/commands/dev.d.ts.map +1 -0
  268. package/dist/cli/commands/doctor.d.ts +42 -0
  269. package/dist/cli/commands/doctor.d.ts.map +1 -0
  270. package/dist/cli/commands/generate.d.ts +36 -0
  271. package/dist/cli/commands/generate.d.ts.map +1 -0
  272. package/dist/cli/commands/init.d.ts +30 -0
  273. package/dist/cli/commands/init.d.ts.map +1 -0
  274. package/dist/cli/commands/install-app.d.ts +30 -0
  275. package/dist/cli/commands/install-app.d.ts.map +1 -0
  276. package/dist/cli/commands/items.d.ts +45 -0
  277. package/dist/cli/commands/items.d.ts.map +1 -0
  278. package/dist/cli/commands/migrations.d.ts +41 -0
  279. package/dist/cli/commands/migrations.d.ts.map +1 -0
  280. package/dist/cli/commands/pipelines.d.ts +40 -0
  281. package/dist/cli/commands/pipelines.d.ts.map +1 -0
  282. package/dist/cli/commands/status.d.ts +23 -0
  283. package/dist/cli/commands/status.d.ts.map +1 -0
  284. package/dist/cli/commands/system.d.ts +29 -0
  285. package/dist/cli/commands/system.d.ts.map +1 -0
  286. package/dist/cli/commands/test.d.ts +46 -0
  287. package/dist/cli/commands/test.d.ts.map +1 -0
  288. package/dist/cli/commands/uninstall-app.d.ts +23 -0
  289. package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
  290. package/dist/cli/context.d.ts +88 -0
  291. package/dist/cli/context.d.ts.map +1 -0
  292. package/dist/cli/env-loader.d.ts +14 -0
  293. package/dist/cli/env-loader.d.ts.map +1 -0
  294. package/dist/cli/index.d.ts +41 -0
  295. package/dist/cli/index.d.ts.map +1 -0
  296. package/dist/functions/_shared/agent-runner.d.ts +156 -0
  297. package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
  298. package/dist/functions/_shared/app-manifest.d.ts +68 -0
  299. package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
  300. package/dist/functions/_shared/audit.d.ts +91 -0
  301. package/dist/functions/_shared/audit.d.ts.map +1 -0
  302. package/dist/functions/_shared/db.d.ts +125 -0
  303. package/dist/functions/_shared/db.d.ts.map +1 -0
  304. package/dist/functions/_shared/index.d.ts +298 -0
  305. package/dist/functions/_shared/index.d.ts.map +1 -0
  306. package/dist/functions/_shared/middleware.d.ts +315 -0
  307. package/dist/functions/_shared/middleware.d.ts.map +1 -0
  308. package/dist/functions/_shared/permissions.d.ts +626 -0
  309. package/dist/functions/_shared/permissions.d.ts.map +1 -0
  310. package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
  311. package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
  312. package/dist/functions/_shared/principal.d.ts +284 -0
  313. package/dist/functions/_shared/principal.d.ts.map +1 -0
  314. package/dist/functions/_shared/schema-utils.d.ts +181 -0
  315. package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
  316. package/dist/functions/_shared/testing.d.ts +172 -0
  317. package/dist/functions/_shared/testing.d.ts.map +1 -0
  318. package/dist/functions/_shared/trigger-engine.d.ts +140 -0
  319. package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
  320. package/dist/functions/_shared/webhook-registration.d.ts +81 -0
  321. package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
  322. package/dist/functions/_shared/webhook-registry.d.ts +57 -0
  323. package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
  324. package/dist/functions/account-nodes.d.ts +48 -0
  325. package/dist/functions/account-nodes.d.ts.map +1 -0
  326. package/dist/functions/admin-data.d.ts +178 -0
  327. package/dist/functions/admin-data.d.ts.map +1 -0
  328. package/dist/functions/ai-agents.d.ts +125 -0
  329. package/dist/functions/ai-agents.d.ts.map +1 -0
  330. package/dist/functions/api-keys.d.ts +140 -0
  331. package/dist/functions/api-keys.d.ts.map +1 -0
  332. package/dist/functions/apps.d.ts +163 -0
  333. package/dist/functions/apps.d.ts.map +1 -0
  334. package/dist/functions/auth.d.ts +74 -0
  335. package/dist/functions/auth.d.ts.map +1 -0
  336. package/dist/functions/debug-auth.d.ts +33 -0
  337. package/dist/functions/debug-auth.d.ts.map +1 -0
  338. package/dist/functions/embeddings.d.ts +205 -0
  339. package/dist/functions/embeddings.d.ts.map +1 -0
  340. package/dist/functions/integration-routes.d.ts +45 -0
  341. package/dist/functions/integration-routes.d.ts.map +1 -0
  342. package/dist/functions/integrations.d.ts +124 -0
  343. package/dist/functions/integrations.d.ts.map +1 -0
  344. package/dist/functions/item-progress.d.ts +41 -0
  345. package/dist/functions/item-progress.d.ts.map +1 -0
  346. package/dist/functions/logs.d.ts +162 -0
  347. package/dist/functions/logs.d.ts.map +1 -0
  348. package/dist/functions/observability.d.ts +123 -0
  349. package/dist/functions/observability.d.ts.map +1 -0
  350. package/dist/functions/pipeline-executions.d.ts +190 -0
  351. package/dist/functions/pipeline-executions.d.ts.map +1 -0
  352. package/dist/functions/pipelines.d.ts +171 -0
  353. package/dist/functions/pipelines.d.ts.map +1 -0
  354. package/dist/functions/prompt-configs.d.ts +125 -0
  355. package/dist/functions/prompt-configs.d.ts.map +1 -0
  356. package/dist/functions/roles.d.ts +118 -0
  357. package/dist/functions/roles.d.ts.map +1 -0
  358. package/dist/functions/system-cron.d.ts +65 -0
  359. package/dist/functions/system-cron.d.ts.map +1 -0
  360. package/dist/functions/system.d.ts +29 -0
  361. package/dist/functions/system.d.ts.map +1 -0
  362. package/dist/functions/tests.d.ts +28 -0
  363. package/dist/functions/tests.d.ts.map +1 -0
  364. package/dist/functions/timers.d.ts +139 -0
  365. package/dist/functions/timers.d.ts.map +1 -0
  366. package/dist/functions/triggers.d.ts +203 -0
  367. package/dist/functions/triggers.d.ts.map +1 -0
  368. package/dist/functions/types.d.ts +151 -0
  369. package/dist/functions/types.d.ts.map +1 -0
  370. package/dist/src/types/types.d.ts +364 -0
  371. package/dist/src/types/types.d.ts.map +1 -0
  372. package/package.json +192 -0
  373. package/scripts/app-install-cli.ts +286 -0
  374. package/scripts/assemble-frontend.sh +79 -0
  375. package/scripts/assemble-functions.sh +62 -0
  376. package/scripts/assemble.sh +35 -0
  377. package/scripts/boundary-check.sh +106 -0
  378. package/scripts/build-manifest.sh +80 -0
  379. package/scripts/check-core-integrity.sh +82 -0
  380. package/scripts/ingest-chunks.cjs +202 -0
  381. package/scripts/kb-chunk-parser.cjs +312 -0
  382. package/scripts/kb-chunk-parser.ts +330 -0
  383. package/scripts/load-test-app-install.ts +484 -0
  384. package/scripts/netlify-dev-wrapper.sh +22 -0
  385. package/scripts/verify-integrity.sh +69 -0
@@ -0,0 +1,1097 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * @module agent-runner
4
+ * @audience both
5
+ * @layer shared-core
6
+ * @stability stable
7
+ *
8
+ * AI agent inference orchestrator with RAG, tool dispatch, and confidence-based
9
+ * escalation. All agent behavior is defined via JSONB configuration stored in
10
+ * existing schema tables — no dedicated migrations are needed.
11
+ *
12
+ * Configuration resolution chain (highest to lowest priority):
13
+ * 1. `thread.data.agent_id` → `thread.type.design_schema.default_agent_id`
14
+ * 2. `thread.data.prompt_config_id` → `agent.metadata.default_prompt_config_id`
15
+ *
16
+ * Inference loop (per `runAgent` call):
17
+ * 1. `resolveAgentConfig` — resolve agent + prompt_config from thread
18
+ * 2. Save user message to `messages` table
19
+ * 3. `executeAgentInference` — iterative tool-call loop (max 5 iterations):
20
+ * a. `buildContext` — assemble system prompt + RAG + history + tools
21
+ * b. `callInference` — call OpenAI-compatible API (or return mock)
22
+ * c. `dispatchTools` — execute any tool_calls via actions table
23
+ * d. Rebuild context with tool results and repeat
24
+ * 4. Confidence check — if below threshold, `handleEscalation`
25
+ * 5. Save agent response to `messages` table
26
+ * 6. Emit `agent.inference.completed` audit log
27
+ *
28
+ * Environment variables used by `callInference`:
29
+ * - `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `LLM_API_KEY` — LLM auth
30
+ * - `OPENAI_BASE_URL` / `LLM_BASE_URL` — API base URL (default: OpenAI)
31
+ * - `LLM_DEFAULT_MODEL` — model name fallback (default: 'gpt-4o')
32
+ *
33
+ * INVARIANT: if no API key is set, `callInference` returns a mock response
34
+ * instead of throwing — safe for local development without credentials.
35
+ * INVARIANT: `runAgent` throws on critical failures (config missing, inference
36
+ * error) — callers must handle the rejection.
37
+ * INVARIANT: `resolveAgentConfig` returns null (not throws) on missing config;
38
+ * `runAgent` converts this to a throw.
39
+ *
40
+ * @seeAlso pipeline-runner.ts (tool dispatch calls runPipeline for run_pipeline tool)
41
+ * @seeAlso audit.ts (emitAudit for agent.inference.* events)
42
+ * @seeAlso index.ts (runAgent, resolveAgentConfig re-exported)
43
+ */
44
+
45
+ import { CoreContext } from './middleware'
46
+ import { adminDb } from './db'
47
+ import { emitAudit } from './audit'
48
+
49
+ // ─── TYPES ───────────────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Resolved agent configuration bundle. Output of `resolveAgentConfig`.
53
+ * Passed to `executeAgentInference` and `buildContext`.
54
+ *
55
+ * @outputSpec agent: ai_agents row (system_prompt, model_config, metadata)
56
+ * @outputSpec promptConfig: prompt_configs row (context_template,
57
+ * knowledge_sources, available_tools, confidence_threshold, escalation_*)
58
+ * @outputSpec thread: threads row with joined type record
59
+ * @outputSpec threadType: types row (design_schema.default_agent_id)
60
+ */
61
+ export interface AgentConfig {
62
+ agent: any
63
+ promptConfig: any
64
+ thread: any
65
+ threadType: any
66
+ }
67
+
68
+ /**
69
+ * Structured result from a single LLM inference call.
70
+ *
71
+ * @outputSpec content: string — agent response text
72
+ * @outputSpec confidence: number — 0-1 score; derived from logprobs or 0.85 default
73
+ * @outputSpec tool_calls: ToolCall[] | undefined — tools the model wants to call
74
+ * @outputSpec metadata: { model, usage, finish_reason } | undefined
75
+ */
76
+ export interface InferenceResult {
77
+ content: string
78
+ confidence: number
79
+ tool_calls?: ToolCall[]
80
+ metadata?: Record<string, any>
81
+ }
82
+
83
+ /**
84
+ * A single tool invocation requested by the LLM in an `InferenceResult`.
85
+ *
86
+ * @inputSpec tool: string — action.slug to look up in the actions table
87
+ * @inputSpec params: Record<string, any> — parsed from OpenAI function call arguments
88
+ * @inputSpec id: string — opaque tool_call ID from the LLM response
89
+ */
90
+ export interface ToolCall {
91
+ tool: string
92
+ params: Record<string, any>
93
+ id: string
94
+ }
95
+
96
+ /**
97
+ * Result of executing a single `ToolCall`.
98
+ *
99
+ * @outputSpec tool: string — mirrors ToolCall.tool
100
+ * @outputSpec result: any — handler return value on success; null on error
101
+ * @outputSpec error: string | undefined — error message if execution failed
102
+ * @outputSpec id: string — mirrors ToolCall.id for correlation
103
+ */
104
+ export interface ToolResult {
105
+ tool: string
106
+ result: any
107
+ error?: string
108
+ id: string
109
+ }
110
+
111
+ // ─── PRIMARY EXPORTS ────────────────────────────────────────────────────────────
112
+
113
+ /**
114
+ * Main entry point: run a full agent inference cycle for a user message.
115
+ *
116
+ * Saves the user message, runs the inference loop (with tool calls), checks
117
+ * confidence, saves the agent response, and emits audit logs.
118
+ *
119
+ * @param threadId - UUID of the thread to run inference on
120
+ * @param userMessage - Raw message text from the user
121
+ * @param ctx - CoreContext with accountId, principal, requestId
122
+ * @returns Promise<any> — the saved agent message record from the `messages` table
123
+ * @throws Error — if agent config cannot be resolved, or LLM inference fails
124
+ * @inputSpec threadId: string — valid UUID in threads table
125
+ * @inputSpec userMessage: string — non-empty message text
126
+ * @inputSpec ctx.accountId: string | null — used to scope DB lookups
127
+ * @outputSpec messages row — the inserted agent reply with content and metadata
128
+ * @sideEffects DB write: inserts 2 messages rows (user + agent)
129
+ * @sideEffects DB write: emitAudit (agent.inference.completed or agent.inference.failed)
130
+ * @sideEffects HTTP call: callInference (LLM API)
131
+ * @sideEffects DB write (conditional): handleEscalation if confidence < threshold
132
+ * @calledBy functions/ai-agents.ts handler for POST ?action=run
133
+ * @calledBy v2-custom/ import callers
134
+ * @calls resolveAgentConfig, saveMessage, executeAgentInference,
135
+ * handleEscalation, emitAudit
136
+ * @testUnit tests/unit/agent-runner.test.ts
137
+ * @testIntegration tests/integration/agent-runner.test.ts
138
+ *
139
+ * @example API handler usage
140
+ * ```ts
141
+ * import { runAgent } from './_shared/index'
142
+ * const agentMsg = await runAgent(body.thread_id, body.message, ctx)
143
+ * return agentMsg
144
+ * ```
145
+ */
146
+ export async function runAgent(
147
+ threadId: string,
148
+ userMessage: string,
149
+ ctx: CoreContext
150
+ ): Promise<any> {
151
+ const startTime = Date.now()
152
+
153
+ try {
154
+ // 1. Resolve configurations (thread → agent → prompt config)
155
+ const config = await resolveAgentConfig(threadId, ctx)
156
+ if (!config) {
157
+ throw new Error(`Could not resolve agent configuration for thread ${threadId}`)
158
+ }
159
+
160
+ const { agent, promptConfig, thread, threadType } = config
161
+
162
+ // 2. Save user message to thread
163
+ const userMsg = await saveMessage(threadId, userMessage, 'human', null, ctx)
164
+
165
+ // 3. Execute agent with full context
166
+ const agentResponse = await executeAgentInference(
167
+ config,
168
+ userMessage,
169
+ ctx
170
+ )
171
+
172
+ // 4. Check confidence and escalate if needed
173
+ if (agentResponse.confidence < (promptConfig.confidence_threshold || 0)) {
174
+ await handleEscalation(
175
+ threadId,
176
+ agentResponse.confidence,
177
+ promptConfig,
178
+ ctx
179
+ )
180
+ }
181
+
182
+ // 5. Save agent response
183
+ const agentMsg = await saveMessage(
184
+ threadId,
185
+ agentResponse.content,
186
+ 'agent',
187
+ {
188
+ confidence: agentResponse.confidence,
189
+ tool_calls: agentResponse.tool_calls,
190
+ agent_id: agent.id,
191
+ prompt_config_id: promptConfig.id
192
+ },
193
+ ctx
194
+ )
195
+
196
+ // 6. Emit audit log
197
+ await emitAudit(ctx, 'agent.inference.completed', {
198
+ type: 'agent_message',
199
+ id: agentMsg.id,
200
+ account_id: ctx.accountId ?? undefined
201
+ }, {
202
+ thread_id: threadId,
203
+ agent_id: agent.id,
204
+ confidence: agentResponse.confidence,
205
+ has_tool_calls: !!agentResponse.tool_calls?.length,
206
+ duration_ms: Date.now() - startTime
207
+ })
208
+
209
+ return agentMsg
210
+
211
+ } catch (error: any) {
212
+ // Log failure
213
+ await emitAudit(ctx, 'agent.inference.failed', {
214
+ type: 'agent_message',
215
+ id: 'failed',
216
+ account_id: ctx.accountId ?? undefined
217
+ }, {
218
+ thread_id: threadId,
219
+ error: error.message
220
+ })
221
+
222
+ throw error
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Resolves the agent, prompt config, thread, and thread type for a given thread.
228
+ *
229
+ * Resolution priority:
230
+ * - `agent_id`: `thread.data.agent_id` → `thread.type.design_schema.default_agent_id`
231
+ * - `prompt_config_id`: `thread.data.prompt_config_id` →
232
+ * `agent.metadata.default_prompt_config_id`
233
+ *
234
+ * Returns `null` (does not throw) when any required record is missing. `runAgent`
235
+ * converts a null return to a thrown Error.
236
+ *
237
+ * @param threadId - UUID of the thread
238
+ * @param ctx - CoreContext (requestId used for error logging)
239
+ * @returns Promise<AgentConfig | null> — null if config cannot be resolved
240
+ * @throws never — errors logged to console, returns null
241
+ * @inputSpec threadId: string — valid UUID in threads table
242
+ * @outputSpec AgentConfig | null
243
+ * @sideEffects DB reads: threads (with type join), ai_agents, prompt_configs
244
+ * @calledBy runAgent
245
+ * @testUnit tests/unit/agent-runner.test.ts — 'resolveAgentConfig'
246
+ */
247
+ export async function resolveAgentConfig(
248
+ threadId: string,
249
+ ctx: CoreContext
250
+ ): Promise<AgentConfig | null> {
251
+ // Load thread with its type
252
+ const { data: thread, error: threadError } = await adminDb
253
+ .from('threads')
254
+ .select('*, type:types(*)')
255
+ .eq('id', threadId)
256
+ .single()
257
+
258
+ if (threadError || !thread) {
259
+ console.error(`Thread not found: ${threadId}`, threadError)
260
+ return null
261
+ }
262
+
263
+ const threadType = thread.type
264
+ const threadData = thread.data || {}
265
+
266
+ // Resolve agent_id: thread.data > thread.type.design_schema.default_agent_id
267
+ let agentId = threadData.agent_id
268
+ if (!agentId && threadType?.design_schema?.default_agent_id) {
269
+ agentId = threadType.design_schema.default_agent_id
270
+ }
271
+
272
+ if (!agentId) {
273
+ console.error(`No agent assigned to thread ${threadId}`)
274
+ return null
275
+ }
276
+
277
+ // Load agent
278
+ const { data: agent, error: agentError } = await adminDb
279
+ .from('ai_agents')
280
+ .select('*')
281
+ .eq('id', agentId)
282
+ .eq('is_active', true)
283
+ .single()
284
+
285
+ if (agentError || !agent) {
286
+ console.error(`Agent not found: ${agentId}`, agentError)
287
+ return null
288
+ }
289
+
290
+ const agentMetadata = agent.metadata || {}
291
+
292
+ // Resolve prompt_config_id: thread.data > agent.metadata.default_prompt_config_id
293
+ let promptConfigId = threadData.prompt_config_id
294
+ if (!promptConfigId && agentMetadata.default_prompt_config_id) {
295
+ promptConfigId = agentMetadata.default_prompt_config_id
296
+ }
297
+
298
+ if (!promptConfigId) {
299
+ console.error(`No prompt config for thread ${threadId}`)
300
+ return null
301
+ }
302
+
303
+ // Load prompt config
304
+ const { data: promptConfig, error: configError } = await adminDb
305
+ .from('prompt_configs')
306
+ .select('*')
307
+ .eq('id', promptConfigId)
308
+ .eq('is_active', true)
309
+ .single()
310
+
311
+ if (configError || !promptConfig) {
312
+ console.error(`Prompt config not found: ${promptConfigId}`, configError)
313
+ return null
314
+ }
315
+
316
+ return { agent, promptConfig, thread, threadType }
317
+ }
318
+
319
+ // ─── INFERENCE LOOP ─────────────────────────────────────────────────────────────
320
+
321
+ /**
322
+ * Runs the iterative inference loop: build context → call LLM → dispatch tools
323
+ * → rebuild context → repeat (up to `maxToolIterations`).
324
+ *
325
+ * Returns early when the LLM response has no tool_calls. Stops on last
326
+ * iteration with a note appended if any tool failed.
327
+ *
328
+ * @param config - Resolved AgentConfig
329
+ * @param userMessage - Original user message text
330
+ * @param ctx - CoreContext
331
+ * @param maxToolIterations - Maximum tool-call loops (default: 5)
332
+ * @returns Promise<InferenceResult> — final LLM response with content and confidence
333
+ * @throws Error('Max tool iterations reached') if loop exhausted without convergence
334
+ * @sideEffects HTTP calls: callInference (per iteration)
335
+ * @sideEffects DB reads: messages (conversation history), embeddings (RAG)
336
+ * @calledBy runAgent
337
+ * @calls buildContext, callInference, dispatchTools
338
+ */
339
+ async function executeAgentInference(
340
+ config: AgentConfig,
341
+ userMessage: string,
342
+ ctx: CoreContext,
343
+ maxToolIterations: number = 5
344
+ ): Promise<InferenceResult> {
345
+ const { agent, promptConfig, thread } = config
346
+
347
+ // Build initial context
348
+ let context = await buildContext(config, userMessage, [], ctx)
349
+
350
+ for (let iteration = 0; iteration < maxToolIterations; iteration++) {
351
+ // Call inference
352
+ const inferenceResult = await callInference(context, agent, promptConfig, ctx)
353
+
354
+ // If no tool calls, return immediately
355
+ if (!inferenceResult.tool_calls || inferenceResult.tool_calls.length === 0) {
356
+ return inferenceResult
357
+ }
358
+
359
+ // Execute tools
360
+ const toolResults = await dispatchTools(inferenceResult.tool_calls, ctx)
361
+
362
+ // Add tool results to context and re-inference
363
+ context = await buildContext(config, userMessage, toolResults, ctx)
364
+
365
+ // Check if any tool failed - if so, return with error info
366
+ const hasErrors = toolResults.some(r => r.error)
367
+ if (hasErrors && iteration === maxToolIterations - 1) {
368
+ inferenceResult.content += '\n\n[Note: Some tools failed to execute.]'
369
+ return inferenceResult
370
+ }
371
+ }
372
+
373
+ // Max iterations reached
374
+ throw new Error('Max tool iterations reached')
375
+ }
376
+
377
+ /**
378
+ * Assembles the full prompt context string for a single inference call.
379
+ *
380
+ * Context sections (in order):
381
+ * 1. `agent.system_prompt` (or default 'You are a helpful assistant.')
382
+ * 2. `promptConfig.context_template` (if set)
383
+ * 3. Retrieved knowledge via `retrieveKnowledge` (RAG, if knowledge_sources set)
384
+ * 4. Conversation history via `getConversationHistory`
385
+ * 5. Tool results from previous iteration (if any)
386
+ * 6. Available tools list from `promptConfig.available_tools`
387
+ * 7. Current user message
388
+ *
389
+ * @param config - AgentConfig with agent, promptConfig, thread
390
+ * @param userMessage - Current user message
391
+ * @param toolResults - Results from previous tool dispatch iteration
392
+ * @param ctx - CoreContext
393
+ * @returns Promise<string> — assembled prompt context string
394
+ * @throws never — returns partial context on sub-call failure
395
+ * @sideEffects DB reads: embeddings (retrieveKnowledge), messages (history)
396
+ * @calledBy executeAgentInference (per iteration)
397
+ * @calls retrieveKnowledge, getConversationHistory
398
+ */
399
+ async function buildContext(
400
+ config: AgentConfig,
401
+ userMessage: string,
402
+ toolResults: ToolResult[],
403
+ ctx: CoreContext
404
+ ): Promise<string> {
405
+ const { agent, promptConfig, thread } = config
406
+
407
+ // 1. System prompt (from agent)
408
+ let context = `${agent.system_prompt || 'You are a helpful assistant.'}\n\n`
409
+
410
+ // 2. Context template (from prompt config)
411
+ if (promptConfig.context_template) {
412
+ context += `${promptConfig.context_template}\n\n`
413
+ }
414
+
415
+ // 3. Retrieved knowledge (RAG)
416
+ if (promptConfig.knowledge_sources && promptConfig.knowledge_sources.length > 0) {
417
+ const retrievedDocs = await retrieveKnowledge(
418
+ userMessage,
419
+ promptConfig.knowledge_sources,
420
+ ctx.accountId!
421
+ )
422
+ if (retrievedDocs.length > 0) {
423
+ context += '## Relevant Information\n'
424
+ retrievedDocs.forEach((doc, i) => {
425
+ context += `[${i + 1}] ${doc.content}\n`
426
+ })
427
+ context += '\n'
428
+ }
429
+ }
430
+
431
+ // 4. Conversation history
432
+ const history = await getConversationHistory(thread.id, promptConfig.max_history_messages || 20)
433
+ if (history.length > 0) {
434
+ context += '## Conversation History\n'
435
+ history.forEach(msg => {
436
+ const role = msg.data?.message_type === 'human' ? 'User' : 'Assistant'
437
+ context += `${role}: ${msg.content}\n`
438
+ })
439
+ context += '\n'
440
+ }
441
+
442
+ // 5. Tool results (if any)
443
+ if (toolResults.length > 0) {
444
+ context += '## Tool Results\n'
445
+ toolResults.forEach(result => {
446
+ if (result.error) {
447
+ context += `Tool "${result.tool}" failed: ${result.error}\n`
448
+ } else {
449
+ context += `Tool "${result.tool}" result: ${JSON.stringify(result.result)}\n`
450
+ }
451
+ })
452
+ context += '\n'
453
+ }
454
+
455
+ // 6. Available tools (if configured)
456
+ if (promptConfig.available_tools && promptConfig.available_tools.length > 0) {
457
+ context += '## Available Tools\n'
458
+ promptConfig.available_tools.forEach((tool: string) => {
459
+ context += `- ${tool}\n`
460
+ })
461
+ context += '\n'
462
+ }
463
+
464
+ // 7. Current user message
465
+ context += `## Current Message\nUser: ${userMessage}\n\nAssistant: `
466
+
467
+ return context
468
+ }
469
+
470
+ // ─── KNOWLEDGE & HISTORY ───────────────────────────────────────────────────────────
471
+
472
+ /**
473
+ * Retrieves relevant documents from the `embeddings` table for RAG.
474
+ *
475
+ * Uses Supabase full-text search (`textSearch`) on `content`. Falls back
476
+ * to returning an empty array on error (never throws).
477
+ * Future: replace with vector similarity search when embedding service is wired.
478
+ *
479
+ * @param query - User message text to search against
480
+ * @param knowledgeSources - Array of document UUIDs to filter the search
481
+ * @param accountId - Account scope for the embeddings lookup
482
+ * @returns Promise<any[]> — array of embedding rows (content, metadata)
483
+ * @throws never — returns [] on failure
484
+ * @sideEffects DB read: embeddings table
485
+ * @calledBy buildContext (if promptConfig.knowledge_sources is non-empty)
486
+ */
487
+ async function retrieveKnowledge(
488
+ query: string,
489
+ knowledgeSources: string[],
490
+ accountId: string
491
+ ): Promise<any[]> {
492
+ // For now, we'll need the query embedding - this requires calling an embedding service
493
+ // In production, this would call the same embedding model used to create embeddings
494
+ // For now, we'll do a text search fallback
495
+
496
+ const { data: docs, error } = await adminDb
497
+ .from('embeddings')
498
+ .select('*')
499
+ .eq('account_id', accountId)
500
+ .in('document_id', knowledgeSources)
501
+ .textSearch('content', query, {
502
+ type: 'websearch',
503
+ config: 'english'
504
+ })
505
+ .limit(5)
506
+
507
+ if (error) {
508
+ console.error('Knowledge retrieval failed:', error)
509
+ return []
510
+ }
511
+
512
+ return docs || []
513
+ }
514
+
515
+ /**
516
+ * Loads the most recent N messages from a thread, ordered chronologically.
517
+ *
518
+ * Returns messages in ascending order (oldest first) for context assembly.
519
+ * Returns empty array on error (never throws).
520
+ *
521
+ * @param threadId - UUID of the thread
522
+ * @param limit - Maximum number of messages to return (from promptConfig.max_history_messages)
523
+ * @returns Promise<any[]> — array of messages rows ordered oldest-first
524
+ * @throws never — returns [] on failure
525
+ * @sideEffects DB read: messages table
526
+ * @calledBy buildContext
527
+ */
528
+ async function getConversationHistory(
529
+ threadId: string,
530
+ limit: number
531
+ ): Promise<any[]> {
532
+ const { data: messages, error } = await adminDb
533
+ .from('messages')
534
+ .select('*')
535
+ .eq('thread_id', threadId)
536
+ .order('created_at', { ascending: false })
537
+ .limit(limit)
538
+
539
+ if (error) {
540
+ console.error('Failed to load history:', error)
541
+ return []
542
+ }
543
+
544
+ return messages?.reverse() || []
545
+ }
546
+
547
+ // ─── INFERENCE CALL ────────────────────────────────────────────────────────────
548
+
549
+ /**
550
+ * Calls an OpenAI-compatible chat completions API with the assembled context.
551
+ *
552
+ * Credentials and base URL are loaded from environment variables:
553
+ * - `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `LLM_API_KEY`
554
+ * - `OPENAI_BASE_URL` / `LLM_BASE_URL` (default: 'https://api.openai.com/v1')
555
+ * - `LLM_DEFAULT_MODEL` (default: 'gpt-4o')
556
+ *
557
+ * If no API key is found, returns a mock response instead of throwing.
558
+ * This allows local development without LLM credentials.
559
+ *
560
+ * Tool calls are extracted from `message.tool_calls` and mapped to `ToolCall[]`.
561
+ * Confidence is derived from `logprobs` if available, otherwise defaults to 0.85.
562
+ *
563
+ * @param context - Assembled context string from buildContext
564
+ * @param agent - Agent record with model_config (model, temperature, max_tokens, tools)
565
+ * @param promptConfig - Prompt config record (unused here beyond model overrides)
566
+ * @param ctx - CoreContext (requestId for logging)
567
+ * @returns Promise<InferenceResult> — content, confidence, tool_calls, metadata
568
+ * @throws Error('Inference failed: <status>') on non-2xx HTTP response
569
+ * @sideEffects HTTP call to `${OPENAI_BASE_URL}/chat/completions`
570
+ * @calledBy executeAgentInference (per iteration)
571
+ */
572
+ async function callInference(
573
+ context: string,
574
+ agent: any,
575
+ promptConfig: any,
576
+ ctx: CoreContext
577
+ ): Promise<InferenceResult> {
578
+ const modelConfig = agent.model_config || {}
579
+
580
+ // Get LLM credentials from environment (safer than DB storage)
581
+ const apiKey = process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.LLM_API_KEY
582
+ const baseUrl = process.env.OPENAI_BASE_URL || process.env.LLM_BASE_URL || 'https://api.openai.com/v1'
583
+
584
+ if (!apiKey) {
585
+ // Fallback: return mock response for development
586
+ console.warn('No LLM API key found in environment. Set OPENAI_API_KEY or LLM_API_KEY.')
587
+ return {
588
+ content: `[Mock Response] Received ${context.length} chars of context. Set OPENAI_API_KEY in .env for live inference.`,
589
+ confidence: 0.9,
590
+ tool_calls: undefined,
591
+ metadata: { mock: true }
592
+ }
593
+ }
594
+
595
+ const model = modelConfig.model || process.env.LLM_DEFAULT_MODEL || 'gpt-4o'
596
+ const temperature = modelConfig.temperature ?? 0.7
597
+ const maxTokens = modelConfig.max_tokens ?? 4000
598
+
599
+ // Call OpenAI-compatible API
600
+ const response = await fetch(`${baseUrl}/chat/completions`, {
601
+ method: 'POST',
602
+ headers: {
603
+ 'Content-Type': 'application/json',
604
+ 'Authorization': `Bearer ${apiKey}`
605
+ },
606
+ body: JSON.stringify({
607
+ model,
608
+ messages: [
609
+ { role: 'system', content: context.split('\n\n')[0] }, // First paragraph as system
610
+ { role: 'user', content: context.split('\n\n').slice(1).join('\n\n') } // Rest as user
611
+ ],
612
+ temperature,
613
+ max_tokens: maxTokens,
614
+ ...(modelConfig.tools?.length ? { tools: modelConfig.tools, tool_choice: 'auto' } : {})
615
+ })
616
+ })
617
+
618
+ if (!response.ok) {
619
+ const errorBody = await response.text()
620
+ throw new Error(`Inference failed: ${response.status} ${response.statusText} - ${errorBody}`)
621
+ }
622
+
623
+ const result: any = await response.json()
624
+ const message = result.choices?.[0]?.message
625
+
626
+ // Extract confidence from logprobs if available, otherwise estimate
627
+ const confidence = result.choices?.[0]?.logprobs?.content?.[0]?.logprob
628
+ ? Math.exp(result.choices[0].logprobs.content[0].logprob)
629
+ : 0.85 // Default confidence when not provided
630
+
631
+ return {
632
+ content: message?.content || '',
633
+ confidence,
634
+ tool_calls: message?.tool_calls?.map((tc: any) => ({
635
+ id: tc.id,
636
+ tool: tc.function?.name,
637
+ params: JSON.parse(tc.function?.arguments || '{}')
638
+ })),
639
+ metadata: {
640
+ model: result.model,
641
+ usage: result.usage,
642
+ finish_reason: result.choices?.[0]?.finish_reason
643
+ }
644
+ }
645
+ }
646
+
647
+ /**
648
+ * Legacy webhook-based inference handler. Calls an arbitrary URL with the
649
+ * config as JSON body. Returns a mock if `config.url` is not set.
650
+ *
651
+ * @deprecated Use `callInference` with environment-based credentials instead.
652
+ * @throws Error on non-2xx HTTP response
653
+ * @sideEffects HTTP call to config.url
654
+ * @calledBy unused (kept for backward compatibility)
655
+ */
656
+ async function executeInferenceHandler(
657
+ config: any,
658
+ ctx: CoreContext
659
+ ): Promise<any> {
660
+ const { url, method = 'POST', headers = {} } = config
661
+
662
+ if (!url) {
663
+ // Fallback: return mock response for development
664
+ console.warn('No inference URL configured, returning mock response')
665
+ return {
666
+ content: `[Mock] I received your message. In production, this would call ${config.model} via webhook.`,
667
+ confidence: 0.9
668
+ }
669
+ }
670
+
671
+ const response = await fetch(url, {
672
+ method,
673
+ headers: {
674
+ 'Content-Type': 'application/json',
675
+ ...headers
676
+ },
677
+ body: JSON.stringify(config)
678
+ })
679
+
680
+ if (!response.ok) {
681
+ throw new Error(`Inference failed: ${response.status} ${response.statusText}`)
682
+ }
683
+
684
+ return await response.json()
685
+ }
686
+
687
+ // ─── TOOL DISPATCH ────────────────────────────────────────────────────────────
688
+
689
+ /**
690
+ * Executes a batch of tool calls in sequence, returning results for each.
691
+ *
692
+ * For each tool call:
693
+ * 1. Look up action by `call.tool` (action.slug) in the actions table
694
+ * 2. Call `executeToolAction` to dispatch to the correct handler
695
+ * 3. Push `ToolResult` with success output or error message
696
+ *
697
+ * Individual tool failures are captured in `ToolResult.error` and do NOT
698
+ * halt the batch — all tool calls are attempted.
699
+ *
700
+ * @param toolCalls - Array of ToolCall objects from an InferenceResult
701
+ * @param ctx - CoreContext
702
+ * @returns Promise<ToolResult[]> — one result per input tool call
703
+ * @throws never — per-tool errors captured in result.error
704
+ * @sideEffects DB read: actions table (per tool call)
705
+ * @sideEffects Calls executeToolAction (DB writes, HTTP calls per handler)
706
+ * @calledBy executeAgentInference (after each inference call with tool_calls)
707
+ * @calls executeToolAction
708
+ */
709
+ async function dispatchTools(
710
+ toolCalls: ToolCall[],
711
+ ctx: CoreContext
712
+ ): Promise<ToolResult[]> {
713
+ const results: ToolResult[] = []
714
+
715
+ for (const call of toolCalls) {
716
+ try {
717
+ // Lookup action for this tool
718
+ const { data: action, error } = await adminDb
719
+ .from('actions')
720
+ .select('*')
721
+ .eq('slug', call.tool)
722
+ .eq('is_active', true)
723
+ .single()
724
+
725
+ if (error || !action) {
726
+ results.push({
727
+ tool: call.tool,
728
+ id: call.id,
729
+ result: null,
730
+ error: `Tool "${call.tool}" not found`
731
+ })
732
+ continue
733
+ }
734
+
735
+ // Execute tool via pipeline-runner pattern
736
+ const result = await executeToolAction(action, call.params, ctx)
737
+
738
+ results.push({
739
+ tool: call.tool,
740
+ id: call.id,
741
+ result
742
+ })
743
+
744
+ } catch (error: any) {
745
+ results.push({
746
+ tool: call.tool,
747
+ id: call.id,
748
+ result: null,
749
+ error: error.message
750
+ })
751
+ }
752
+ }
753
+
754
+ return results
755
+ }
756
+
757
+ /**
758
+ * Dispatches a single tool action to its handler based on `action.handler`.
759
+ *
760
+ * Supported handlers (mirrors pipeline-runner stageHandlers):
761
+ * - `search_knowledge` → `executeSearchKnowledge`
762
+ * - `query_items` → `executeQueryItems`
763
+ * - `create_record` → `executeCreateRecord`
764
+ * - `update_item` → `executeUpdateItem`
765
+ * - `run_pipeline` → `runPipeline` (dynamic import to avoid circular deps)
766
+ * - `send_notification` → `executeSendNotification`
767
+ *
768
+ * @param action - Action record from the actions table
769
+ * @param params - Tool call parameters (from LLM function_call.arguments)
770
+ * @param ctx - CoreContext
771
+ * @returns Promise<any> — handler output
772
+ * @throws Error('Unknown tool handler') on unrecognised action.handler
773
+ * @calledBy dispatchTools
774
+ */
775
+ async function executeToolAction(
776
+ action: any,
777
+ params: any,
778
+ ctx: CoreContext
779
+ ): Promise<any> {
780
+ // Import pipeline-runner handlers dynamically to avoid circular deps
781
+ const { runPipeline } = await import('./pipeline-runner')
782
+
783
+ switch (action.handler) {
784
+ case 'search_knowledge':
785
+ return await executeSearchKnowledge(params, ctx)
786
+
787
+ case 'query_items':
788
+ return await executeQueryItems(params, ctx)
789
+
790
+ case 'create_record':
791
+ return await executeCreateRecord(params, ctx)
792
+
793
+ case 'update_item':
794
+ return await executeUpdateItem(params, ctx)
795
+
796
+ case 'run_pipeline':
797
+ const result = await runPipeline(params.pipeline_id, params.trigger_data || {}, ctx)
798
+ return { success: result.status === 'completed', result }
799
+
800
+ case 'send_notification':
801
+ return await executeSendNotification(params, ctx)
802
+
803
+ default:
804
+ throw new Error(`Unknown tool handler: ${action.handler}`)
805
+ }
806
+ }
807
+
808
+ // ─── TOOL HANDLERS ────────────────────────────────────────────────────────────
809
+
810
+ /**
811
+ * Tool: search_knowledge — vector similarity search via `search_similar_embeddings`
812
+ * RPC, falling back to text search on error.
813
+ *
814
+ * @param params.query: string — search query text
815
+ * @param params.embedding: number[] | undefined — pre-computed embedding vector
816
+ * @param params.limit: number (default 5)
817
+ * @param params.threshold: number (default 0.7)
818
+ * @returns { results[], method: 'vector'|'text_fallback' }
819
+ * @throws never — falls back to text search on RPC failure
820
+ * @sideEffects DB: search_similar_embeddings RPC or embeddings text search
821
+ */
822
+ async function executeSearchKnowledge(
823
+ params: any,
824
+ ctx: CoreContext
825
+ ): Promise<any> {
826
+ const { query, knowledge_sources, limit = 5 } = params
827
+
828
+ const { data: results, error } = await adminDb.rpc('search_similar_embeddings', {
829
+ p_account_id: ctx.accountId,
830
+ p_model_id: params.model_id || 'text-embedding-ada-002',
831
+ p_query_embedding: params.embedding, // If pre-computed
832
+ p_limit: limit,
833
+ p_threshold: params.threshold || 0.7
834
+ })
835
+
836
+ if (error) {
837
+ // Fallback to text search
838
+ const { data: fallback } = await adminDb
839
+ .from('embeddings')
840
+ .select('*')
841
+ .eq('account_id', ctx.accountId)
842
+ .textSearch('content', query)
843
+ .limit(limit)
844
+
845
+ return { results: fallback || [], method: 'text_fallback' }
846
+ }
847
+
848
+ return { results: results || [], method: 'vector' }
849
+ }
850
+
851
+ /**
852
+ * Tool: query_items — filtered SELECT on any table scoped to ctx.accountId.
853
+ *
854
+ * @param params.entity: string — table name
855
+ * @param params.filters: Record<string, any> (default {})
856
+ * @param params.limit: number (default 10)
857
+ * @returns { items[] }
858
+ * @throws Error on DB failure
859
+ * @sideEffects DB read: params.entity table
860
+ */
861
+ async function executeQueryItems(
862
+ params: any,
863
+ ctx: CoreContext
864
+ ): Promise<any> {
865
+ const { entity, filters = {}, limit = 10 } = params
866
+
867
+ let query = adminDb
868
+ .from(entity)
869
+ .select('*')
870
+ .eq('account_id', ctx.accountId)
871
+ .limit(limit)
872
+
873
+ // Apply filters
874
+ Object.entries(filters).forEach(([key, value]) => {
875
+ query = query.eq(key, value)
876
+ })
877
+
878
+ const { data, error } = await query
879
+
880
+ if (error) throw new Error(`Query failed: ${error.message}`)
881
+ return { items: data || [] }
882
+ }
883
+
884
+ /**
885
+ * Tool: create_record — inserts a record into any table.
886
+ *
887
+ * @param params.entity: string — table name
888
+ * @param params.data: Record<string, any> — field values
889
+ * @returns { record } — the inserted row
890
+ * @throws Error on DB failure
891
+ * @sideEffects DB write: params.entity table
892
+ */
893
+ async function executeCreateRecord(
894
+ params: any,
895
+ ctx: CoreContext
896
+ ): Promise<any> {
897
+ const { entity, data } = params
898
+
899
+ const { data: result, error } = await adminDb
900
+ .from(entity)
901
+ .insert({
902
+ ...data,
903
+ account_id: ctx.accountId,
904
+ created_by: ctx.principal?.id,
905
+ created_at: new Date().toISOString(),
906
+ updated_at: new Date().toISOString()
907
+ })
908
+ .select()
909
+ .single()
910
+
911
+ if (error) throw new Error(`Create failed: ${error.message}`)
912
+ return { record: result }
913
+ }
914
+
915
+ /**
916
+ * Tool: update_item — updates a record by ID in any table.
917
+ *
918
+ * @param params.entity: string — table name
919
+ * @param params.record_id: string — UUID of the record
920
+ * @param params.data: Record<string, any> — fields to update
921
+ * @returns { record } — the updated row
922
+ * @throws Error on DB failure
923
+ * @sideEffects DB write: params.entity table
924
+ */
925
+ async function executeUpdateItem(
926
+ params: any,
927
+ ctx: CoreContext
928
+ ): Promise<any> {
929
+ const { entity, record_id, data } = params
930
+
931
+ const { data: result, error } = await adminDb
932
+ .from(entity)
933
+ .update({
934
+ ...data,
935
+ updated_at: new Date().toISOString(),
936
+ updated_by: ctx.principal?.id
937
+ })
938
+ .eq('id', record_id)
939
+ .select()
940
+ .single()
941
+
942
+ if (error) throw new Error(`Update failed: ${error.message}`)
943
+ return { record: result }
944
+ }
945
+
946
+ /**
947
+ * Tool: send_notification — inserts rows into the `watchers` table.
948
+ *
949
+ * @param params.message: string — notification text
950
+ * @param params.recipients: string[] — person UUIDs to notify
951
+ * @param params.entity_type: string | undefined
952
+ * @param params.entity_id: string | undefined
953
+ * @returns { notified_count }
954
+ * @throws Error on DB failure
955
+ * @sideEffects DB write: watchers table
956
+ */
957
+ async function executeSendNotification(
958
+ params: any,
959
+ ctx: CoreContext
960
+ ): Promise<any> {
961
+ const { message, recipients = [], entity_type, entity_id } = params
962
+
963
+ const notifications = recipients.map((recipientId: string) => ({
964
+ account_id: ctx.accountId,
965
+ person_id: recipientId,
966
+ message,
967
+ entity_type: entity_type || 'agent_message',
968
+ entity_id: entity_id || ctx.requestId,
969
+ is_read: false,
970
+ created_at: new Date().toISOString()
971
+ }))
972
+
973
+ if (notifications.length > 0) {
974
+ const { error } = await adminDb.from('watchers').insert(notifications)
975
+ if (error) throw new Error(`Notification failed: ${error.message}`)
976
+ }
977
+
978
+ return { notified_count: notifications.length }
979
+ }
980
+
981
+ // ─── ESCALATION ────────────────────────────────────────────────────────────
982
+
983
+ /**
984
+ * Handles confidence-based escalation when inference confidence falls below
985
+ * `promptConfig.confidence_threshold`.
986
+ *
987
+ * Actions taken:
988
+ * 1. Attempt to set `threads.data.escalation_status = 'pending'` (via jsonb_set RPC)
989
+ * 2. Emit `agent.inference.low_confidence` audit log
990
+ * 3. If `promptConfig.escalation_action === 'pipeline'` and
991
+ * `promptConfig.escalation_target` is set, run the escalation pipeline
992
+ * via dynamic import of `runPipeline`
993
+ *
994
+ * @param threadId - UUID of the thread being escalated
995
+ * @param confidence - The confidence score that triggered escalation
996
+ * @param promptConfig - Prompt config with escalation settings
997
+ * @param ctx - CoreContext
998
+ * @returns Promise<void> — always resolves
999
+ * @throws never — DB errors are silently dropped (best-effort)
1000
+ * @sideEffects DB write: threads.data (jsonb_set for escalation_status)
1001
+ * @sideEffects DB write: emitAudit (agent.inference.low_confidence)
1002
+ * @sideEffects Calls runPipeline (if escalation_action === 'pipeline')
1003
+ * @calledBy runAgent (when agentResponse.confidence < threshold)
1004
+ */
1005
+ async function handleEscalation(
1006
+ threadId: string,
1007
+ confidence: number,
1008
+ promptConfig: any,
1009
+ ctx: CoreContext
1010
+ ): Promise<void> {
1011
+ // Update thread escalation status
1012
+ await adminDb
1013
+ .from('threads')
1014
+ .update({
1015
+ data: adminDb.rpc('jsonb_set', {
1016
+ target: adminDb.from('threads').select('data').eq('id', threadId).single(),
1017
+ path: '{escalation_status}',
1018
+ new_value: '"pending"'
1019
+ })
1020
+ })
1021
+ .eq('id', threadId)
1022
+
1023
+ // Fire trigger event
1024
+ await emitAudit(ctx, 'agent.inference.low_confidence', {
1025
+ type: 'thread',
1026
+ id: threadId,
1027
+ account_id: ctx.accountId ?? undefined
1028
+ }, {
1029
+ confidence,
1030
+ threshold: promptConfig.confidence_threshold,
1031
+ escalation_action: promptConfig.escalation_action,
1032
+ escalation_target: promptConfig.escalation_target
1033
+ })
1034
+
1035
+ // If escalation pipeline configured, run it
1036
+ if (promptConfig.escalation_action === 'pipeline' && promptConfig.escalation_target) {
1037
+ const { runPipeline } = await import('./pipeline-runner')
1038
+ await runPipeline(
1039
+ promptConfig.escalation_target,
1040
+ {
1041
+ thread_id: threadId,
1042
+ confidence,
1043
+ threshold: promptConfig.confidence_threshold,
1044
+ reason: 'low_confidence'
1045
+ },
1046
+ ctx
1047
+ )
1048
+ }
1049
+ }
1050
+
1051
+ // ─── MESSAGE PERSISTENCE ───────────────────────────────────────────────────────────
1052
+
1053
+ /**
1054
+ * Inserts a message row into the `messages` table for a thread.
1055
+ *
1056
+ * For human messages: `person_id` is set to `ctx.principal.id`.
1057
+ * For agent/system/tool messages: `person_id` is null.
1058
+ * The `data` JSONB field carries `message_type` and any additional metadata.
1059
+ *
1060
+ * @param threadId - UUID of the parent thread
1061
+ * @param content - Message text content
1062
+ * @param messageType - Role classifier for the message
1063
+ * @param data - Additional JSONB metadata (e.g. confidence, tool_calls, agent_id)
1064
+ * @param ctx - CoreContext with accountId and principal
1065
+ * @returns Promise<any> — the inserted messages row
1066
+ * @throws Error('Failed to save message') on DB insert failure
1067
+ * @inputSpec messageType: 'human'|'agent'|'system'|'tool_call'|'tool_result'
1068
+ * @sideEffects DB write: messages table
1069
+ * @calledBy runAgent (user message + agent response)
1070
+ */
1071
+ async function saveMessage(
1072
+ threadId: string,
1073
+ content: string,
1074
+ messageType: 'human' | 'agent' | 'system' | 'tool_call' | 'tool_result',
1075
+ data: any,
1076
+ ctx: CoreContext
1077
+ ): Promise<any> {
1078
+ const { data: message, error } = await adminDb
1079
+ .from('messages')
1080
+ .insert({
1081
+ thread_id: threadId,
1082
+ account_id: ctx.accountId,
1083
+ person_id: messageType === 'human' ? ctx.principal?.id : null,
1084
+ content,
1085
+ content_format: 'text',
1086
+ data: {
1087
+ message_type: messageType,
1088
+ ...data
1089
+ },
1090
+ created_at: new Date().toISOString()
1091
+ })
1092
+ .select()
1093
+ .single()
1094
+
1095
+ if (error) throw new Error(`Failed to save message: ${error.message}`)
1096
+ return message
1097
+ }