slackhive 0.1.37 → 0.1.39
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/.dockerignore +14 -0
- package/.env.example +44 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +65 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +27 -0
- package/.github/dependabot.yml +20 -0
- package/.github/workflows/audit.yml +149 -0
- package/.github/workflows/ci.yml +135 -0
- package/CHANGELOG.md +52 -0
- package/CODE_OF_CONDUCT.md +37 -0
- package/CONTRIBUTING.md +204 -0
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/SECURITY.md +47 -0
- package/apps/runner/Dockerfile +33 -0
- package/apps/runner/dist/__tests__/channel-restrictions.test.d.ts +8 -0
- package/apps/runner/dist/__tests__/channel-restrictions.test.js +63 -0
- package/apps/runner/dist/__tests__/channel-restrictions.test.js.map +1 -0
- package/apps/runner/dist/__tests__/claude-handler-resolve.test.d.ts +20 -0
- package/apps/runner/dist/__tests__/claude-handler-resolve.test.js +178 -0
- package/apps/runner/dist/__tests__/claude-handler-resolve.test.js.map +1 -0
- package/apps/runner/dist/__tests__/compile-claude-md.test.d.ts +13 -0
- package/apps/runner/dist/__tests__/compile-claude-md.test.js +144 -0
- package/apps/runner/dist/__tests__/compile-claude-md.test.js.map +1 -0
- package/apps/runner/dist/__tests__/memory-sync.test.d.ts +11 -0
- package/apps/runner/dist/__tests__/memory-sync.test.js +56 -0
- package/apps/runner/dist/__tests__/memory-sync.test.js.map +1 -0
- package/apps/runner/dist/__tests__/slack-file-support.test.d.ts +9 -0
- package/apps/runner/dist/__tests__/slack-file-support.test.js +271 -0
- package/apps/runner/dist/__tests__/slack-file-support.test.js.map +1 -0
- package/apps/runner/dist/__tests__/slack-formatting.test.d.ts +12 -0
- package/apps/runner/dist/__tests__/slack-formatting.test.js +400 -0
- package/apps/runner/dist/__tests__/slack-formatting.test.js.map +1 -0
- package/apps/runner/dist/__tests__/thread-context.test.d.ts +12 -0
- package/apps/runner/dist/__tests__/thread-context.test.js +182 -0
- package/apps/runner/dist/__tests__/thread-context.test.js.map +1 -0
- package/apps/runner/dist/agent-runner.d.ts +118 -0
- package/apps/runner/dist/agent-runner.js +352 -0
- package/apps/runner/dist/agent-runner.js.map +1 -0
- package/apps/runner/dist/claude-handler.d.ts +122 -0
- package/apps/runner/dist/claude-handler.js +402 -0
- package/apps/runner/dist/claude-handler.js.map +1 -0
- package/apps/runner/dist/compile-claude-md.d.ts +59 -0
- package/apps/runner/dist/compile-claude-md.js +291 -0
- package/apps/runner/dist/compile-claude-md.js.map +1 -0
- package/apps/runner/dist/correction-handler.d.ts +46 -0
- package/apps/runner/dist/correction-handler.js +162 -0
- package/apps/runner/dist/correction-handler.js.map +1 -0
- package/apps/runner/dist/correction-manager.d.ts +53 -0
- package/apps/runner/dist/correction-manager.js +241 -0
- package/apps/runner/dist/correction-manager.js.map +1 -0
- package/apps/runner/dist/db.d.ts +193 -0
- package/apps/runner/dist/db.js +492 -0
- package/apps/runner/dist/db.js.map +1 -0
- package/apps/runner/dist/index.d.ts +9 -0
- package/apps/runner/dist/index.js +43 -0
- package/apps/runner/dist/index.js.map +1 -0
- package/apps/runner/dist/job-scheduler.d.ts +57 -0
- package/apps/runner/dist/job-scheduler.js +150 -0
- package/apps/runner/dist/job-scheduler.js.map +1 -0
- package/apps/runner/dist/logger.d.ts +32 -0
- package/apps/runner/dist/logger.js +52 -0
- package/apps/runner/dist/logger.js.map +1 -0
- package/apps/runner/dist/mcp-process-manager.d.ts +38 -0
- package/apps/runner/dist/mcp-process-manager.js +189 -0
- package/apps/runner/dist/mcp-process-manager.js.map +1 -0
- package/apps/runner/dist/memory-mcp.d.ts +14 -0
- package/apps/runner/dist/memory-mcp.js +88 -0
- package/apps/runner/dist/memory-mcp.js.map +1 -0
- package/apps/runner/dist/memory-watcher.d.ts +78 -0
- package/apps/runner/dist/memory-watcher.js +220 -0
- package/apps/runner/dist/memory-watcher.js.map +1 -0
- package/apps/runner/dist/slack-handler.d.ts +120 -0
- package/apps/runner/dist/slack-handler.js +843 -0
- package/apps/runner/dist/slack-handler.js.map +1 -0
- package/apps/runner/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/apps/runner/package.json +42 -0
- package/apps/runner/src/__tests__/channel-restrictions.test.ts +75 -0
- package/apps/runner/src/__tests__/claude-handler-resolve.test.ts +160 -0
- package/apps/runner/src/__tests__/compile-claude-md.test.ts +139 -0
- package/apps/runner/src/__tests__/memory-sync.test.ts +59 -0
- package/apps/runner/src/__tests__/slack-file-support.test.ts +376 -0
- package/apps/runner/src/__tests__/slack-formatting.test.ts +495 -0
- package/apps/runner/src/__tests__/thread-context.test.ts +215 -0
- package/apps/runner/src/agent-runner.ts +397 -0
- package/apps/runner/src/claude-handler.ts +475 -0
- package/apps/runner/src/compile-claude-md.ts +283 -0
- package/apps/runner/src/correction-handler.ts +191 -0
- package/apps/runner/src/correction-manager.ts +285 -0
- package/apps/runner/src/db.ts +604 -0
- package/apps/runner/src/index.ts +46 -0
- package/apps/runner/src/job-scheduler.ts +165 -0
- package/apps/runner/src/logger.ts +49 -0
- package/apps/runner/src/mcp-process-manager.ts +195 -0
- package/apps/runner/src/memory-mcp.ts +85 -0
- package/apps/runner/src/memory-watcher.ts +215 -0
- package/apps/runner/src/slack-handler.ts +929 -0
- package/apps/runner/tsconfig.json +17 -0
- package/apps/runner/vitest.config.mts +17 -0
- package/apps/web/.eslintrc.json +3 -0
- package/apps/web/.next/app-build-manifest.json +323 -0
- package/apps/web/.next/app-path-routes-manifest.json +46 -0
- package/apps/web/.next/build-manifest.json +33 -0
- package/apps/web/.next/cache/.previewinfo +1 -0
- package/apps/web/.next/cache/.rscinfo +1 -0
- package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/1.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/2.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/3.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/4.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/1.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/1.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/2.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
- package/apps/web/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/apps/web/.next/diagnostics/build-diagnostics.json +6 -0
- package/apps/web/.next/diagnostics/framework.json +1 -0
- package/apps/web/.next/package.json +1 -0
- package/apps/web/.next/react-loadable-manifest.json +1 -0
- package/apps/web/.next/server/app/_not-found/page.js +2 -0
- package/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/agents/[slug]/page.js +4 -0
- package/apps/web/.next/server/app/agents/[slug]/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/agents/[slug]/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/agents/new/page.js +2 -0
- package/apps/web/.next/server/app/agents/new/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/agents/new/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/access/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/access/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/access/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js +6 -0
- package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/claude-md/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/logs/route.js +3 -0
- package/apps/web/.next/server/app/api/agents/[id]/logs/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/logs/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/manifest/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/mcps/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/memories/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/permissions/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/reload/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/reload/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/reload/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/restrictions/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/route.js +33 -0
- package/apps/web/.next/server/app/api/agents/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/skills/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/slack-info/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/snapshots/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/start/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/start/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/start/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/stop/route.js +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/stop/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/[id]/stop/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/agents/route.js +91 -0
- package/apps/web/.next/server/app/api/agents/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/agents/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/login/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/login/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/logout/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/logout/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/me/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/me/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/me/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/[id]/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/users/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/route.js +1 -0
- package/apps/web/.next/server/app/api/auth/users/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/auth/users/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/[key]/route.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/[key]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/env-vars/[key]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/route.js +1 -0
- package/apps/web/.next/server/app/api/env-vars/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/env-vars/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/route.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/jobs/[id]/runs/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/jobs/route.js +1 -0
- package/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/jobs/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/route.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/test/route.js +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/test/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/mcps/[id]/test/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/mcps/route.js +1 -0
- package/apps/web/.next/server/app/api/mcps/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/mcps/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/api/settings/route.js +1 -0
- package/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/icon.svg/route.js +1 -0
- package/apps/web/.next/server/app/icon.svg/route.js.nft.json +1 -0
- package/apps/web/.next/server/app/jobs/page.js +2 -0
- package/apps/web/.next/server/app/jobs/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/jobs/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/login/page.js +2 -0
- package/apps/web/.next/server/app/login/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/login/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/page.js +2 -0
- package/apps/web/.next/server/app/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/settings/env-vars/page.js +2 -0
- package/apps/web/.next/server/app/settings/env-vars/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/settings/env-vars/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/settings/mcps/page.js +2 -0
- package/apps/web/.next/server/app/settings/mcps/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/settings/mcps/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app/settings/page.js +2 -0
- package/apps/web/.next/server/app/settings/page.js.nft.json +1 -0
- package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -0
- package/apps/web/.next/server/app-paths-manifest.json +46 -0
- package/apps/web/.next/server/chunks/1157.js +9 -0
- package/apps/web/.next/server/chunks/2287.js +1 -0
- package/apps/web/.next/server/chunks/3444.js +1 -0
- package/apps/web/.next/server/chunks/383.js +6 -0
- package/apps/web/.next/server/chunks/4012.js +58 -0
- package/apps/web/.next/server/chunks/6791.js +1 -0
- package/apps/web/.next/server/chunks/7171.js +1 -0
- package/apps/web/.next/server/chunks/8819.js +22 -0
- package/apps/web/.next/server/edge-runtime-webpack.js +2 -0
- package/apps/web/.next/server/edge-runtime-webpack.js.map +1 -0
- package/apps/web/.next/server/interception-route-rewrite-manifest.js +1 -0
- package/apps/web/.next/server/middleware-build-manifest.js +1 -0
- package/apps/web/.next/server/middleware-manifest.json +32 -0
- package/apps/web/.next/server/middleware-react-loadable-manifest.js +1 -0
- package/apps/web/.next/server/next-font-manifest.js +1 -0
- package/apps/web/.next/server/next-font-manifest.json +1 -0
- package/apps/web/.next/server/pages/_app.js +1 -0
- package/apps/web/.next/server/pages/_app.js.nft.json +1 -0
- package/apps/web/.next/server/pages/_document.js +1 -0
- package/apps/web/.next/server/pages/_document.js.nft.json +1 -0
- package/apps/web/.next/server/pages/_error.js +19 -0
- package/apps/web/.next/server/pages/_error.js.nft.json +1 -0
- package/apps/web/.next/server/pages-manifest.json +5 -0
- package/apps/web/.next/server/server-reference-manifest.js +1 -0
- package/apps/web/.next/server/server-reference-manifest.json +1 -0
- package/apps/web/.next/server/src/middleware.js +14 -0
- package/apps/web/.next/server/src/middleware.js.map +1 -0
- package/apps/web/.next/server/webpack-runtime.js +1 -0
- package/apps/web/.next/static/chunks/18-90b700ea37b686a2.js +1 -0
- package/apps/web/.next/static/chunks/87c73c54-24122e7b92478d00.js +1 -0
- package/apps/web/.next/static/chunks/9664-af80478aa73ba424.js +1 -0
- package/apps/web/.next/static/chunks/app/_not-found/page-b9cee17ed89ca24a.js +1 -0
- package/apps/web/.next/static/chunks/app/agents/[slug]/page-18369fc3fe1a9a7b.js +1 -0
- package/apps/web/.next/static/chunks/app/agents/new/page-bf11cf8901c7e2cd.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/access/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/claude-md/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/logs/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/manifest/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/mcps/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/[memId]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/permissions/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/reload/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/restrictions/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/[skillId]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/slack-info/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/restore/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/start/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/[id]/stop/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/agents/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/login/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/logout/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/me/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/users/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/auth/users/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/env-vars/[key]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/env-vars/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/jobs/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/jobs/[id]/runs/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/jobs/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/mcps/[id]/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/mcps/[id]/test/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/mcps/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/api/settings/route-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/jobs/page-f5aa89a47c50efd8.js +1 -0
- package/apps/web/.next/static/chunks/app/layout-2079f4964aa7314e.js +1 -0
- package/apps/web/.next/static/chunks/app/login/layout-07f0f73ac9839899.js +1 -0
- package/apps/web/.next/static/chunks/app/login/page-aa259283dc38e8f9.js +1 -0
- package/apps/web/.next/static/chunks/app/page-e83437b608104dff.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/env-vars/page-06479dbdfb78b76b.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/mcps/page-75650686ed6490c7.js +1 -0
- package/apps/web/.next/static/chunks/app/settings/page-e1e62fc41ff6cddd.js +1 -0
- package/apps/web/.next/static/chunks/framework-811407f832a33072.js +1 -0
- package/apps/web/.next/static/chunks/main-3f1cddbdd67b1546.js +1 -0
- package/apps/web/.next/static/chunks/main-app-cebd8a6a5ccbf72d.js +1 -0
- package/apps/web/.next/static/chunks/pages/_app-50fa07b56b2d29ac.js +1 -0
- package/apps/web/.next/static/chunks/pages/_error-fed8688bdd23f211.js +1 -0
- package/apps/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/apps/web/.next/static/chunks/webpack-6c05566dba553c97.js +1 -0
- package/apps/web/.next/static/css/15371687405525e2.css +5 -0
- package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_buildManifest.js +1 -0
- package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_ssgManifest.js +1 -0
- package/apps/web/.next/trace +5 -0
- package/apps/web/.next/types/app/agents/[slug]/page.ts +84 -0
- package/apps/web/.next/types/app/agents/new/page.ts +84 -0
- package/apps/web/.next/types/app/api/agents/[id]/access/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/claude-md/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/logs/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/manifest/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/mcps/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/memories/[memId]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/memories/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/permissions/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/reload/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/restrictions/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/skills/[skillId]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/skills/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/slack-info/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/snapshots/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/start/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/[id]/stop/route.ts +347 -0
- package/apps/web/.next/types/app/api/agents/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/login/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/logout/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/me/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/users/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/auth/users/route.ts +347 -0
- package/apps/web/.next/types/app/api/env-vars/[key]/route.ts +347 -0
- package/apps/web/.next/types/app/api/env-vars/route.ts +347 -0
- package/apps/web/.next/types/app/api/jobs/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/jobs/[id]/runs/route.ts +347 -0
- package/apps/web/.next/types/app/api/jobs/route.ts +347 -0
- package/apps/web/.next/types/app/api/mcps/[id]/route.ts +347 -0
- package/apps/web/.next/types/app/api/mcps/[id]/test/route.ts +347 -0
- package/apps/web/.next/types/app/api/mcps/route.ts +347 -0
- package/apps/web/.next/types/app/api/settings/route.ts +347 -0
- package/apps/web/.next/types/app/jobs/page.ts +84 -0
- package/apps/web/.next/types/app/login/layout.ts +84 -0
- package/apps/web/.next/types/app/login/page.ts +84 -0
- package/apps/web/.next/types/app/page.ts +84 -0
- package/apps/web/.next/types/app/settings/env-vars/page.ts +84 -0
- package/apps/web/.next/types/app/settings/mcps/page.ts +84 -0
- package/apps/web/.next/types/app/settings/page.ts +84 -0
- package/apps/web/.next/types/cache-life.d.ts +141 -0
- package/apps/web/.next/types/package.json +1 -0
- package/apps/web/.next/types/routes.d.ts +114 -0
- package/apps/web/.next/types/validator.ts +448 -0
- package/apps/web/Dockerfile +37 -0
- package/apps/web/next-env.d.ts +6 -0
- package/apps/web/next.config.js +6 -0
- package/apps/web/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/apps/web/package.json +48 -0
- package/apps/web/postcss.config.js +3 -0
- package/apps/web/public/logo.svg +17 -0
- package/apps/web/src/app/agents/[slug]/page.tsx +2235 -0
- package/apps/web/src/app/agents/new/page.tsx +1161 -0
- package/apps/web/src/app/api/agents/[id]/access/route.ts +76 -0
- package/apps/web/src/app/api/agents/[id]/claude-md/route.ts +111 -0
- package/apps/web/src/app/api/agents/[id]/logs/route.ts +84 -0
- package/apps/web/src/app/api/agents/[id]/manifest/route.ts +32 -0
- package/apps/web/src/app/api/agents/[id]/mcps/route.ts +73 -0
- package/apps/web/src/app/api/agents/[id]/memories/[memId]/route.ts +31 -0
- package/apps/web/src/app/api/agents/[id]/memories/route.ts +56 -0
- package/apps/web/src/app/api/agents/[id]/permissions/route.ts +74 -0
- package/apps/web/src/app/api/agents/[id]/reload/route.ts +33 -0
- package/apps/web/src/app/api/agents/[id]/restrictions/route.ts +85 -0
- package/apps/web/src/app/api/agents/[id]/route.ts +81 -0
- package/apps/web/src/app/api/agents/[id]/skills/[skillId]/route.ts +52 -0
- package/apps/web/src/app/api/agents/[id]/skills/route.ts +80 -0
- package/apps/web/src/app/api/agents/[id]/slack-info/route.ts +38 -0
- package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +61 -0
- package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/route.ts +53 -0
- package/apps/web/src/app/api/agents/[id]/snapshots/route.ts +84 -0
- package/apps/web/src/app/api/agents/[id]/start/route.ts +35 -0
- package/apps/web/src/app/api/agents/[id]/stop/route.ts +35 -0
- package/apps/web/src/app/api/agents/route.ts +99 -0
- package/apps/web/src/app/api/auth/login/route.ts +39 -0
- package/apps/web/src/app/api/auth/logout/route.ts +21 -0
- package/apps/web/src/app/api/auth/me/route.ts +24 -0
- package/apps/web/src/app/api/auth/users/[id]/route.ts +48 -0
- package/apps/web/src/app/api/auth/users/route.ts +63 -0
- package/apps/web/src/app/api/env-vars/[key]/route.ts +66 -0
- package/apps/web/src/app/api/env-vars/route.ts +59 -0
- package/apps/web/src/app/api/jobs/[id]/route.ts +51 -0
- package/apps/web/src/app/api/jobs/[id]/runs/route.ts +24 -0
- package/apps/web/src/app/api/jobs/route.ts +42 -0
- package/apps/web/src/app/api/mcps/[id]/route.ts +60 -0
- package/apps/web/src/app/api/mcps/[id]/test/route.ts +195 -0
- package/apps/web/src/app/api/mcps/route.ts +72 -0
- package/apps/web/src/app/api/settings/route.ts +42 -0
- package/apps/web/src/app/globals.css +124 -0
- package/apps/web/src/app/icon.svg +17 -0
- package/apps/web/src/app/jobs/page.tsx +543 -0
- package/apps/web/src/app/layout-shell.tsx +89 -0
- package/apps/web/src/app/layout.tsx +18 -0
- package/apps/web/src/app/login/layout.tsx +9 -0
- package/apps/web/src/app/login/page.tsx +150 -0
- package/apps/web/src/app/page.tsx +573 -0
- package/apps/web/src/app/settings/env-vars/page.tsx +216 -0
- package/apps/web/src/app/settings/mcps/page.tsx +763 -0
- package/apps/web/src/app/settings/page.tsx +528 -0
- package/apps/web/src/app/sidebar.tsx +345 -0
- package/apps/web/src/lib/__tests__/api-guard.test.ts +189 -0
- package/apps/web/src/lib/__tests__/auth.test.ts +262 -0
- package/apps/web/src/lib/__tests__/boss-registry.test.ts +323 -0
- package/apps/web/src/lib/__tests__/compile.test.ts +161 -0
- package/apps/web/src/lib/__tests__/db-agent-hierarchy.test.ts +136 -0
- package/apps/web/src/lib/__tests__/db-env-vars.test.ts +216 -0
- package/apps/web/src/lib/__tests__/db-restrictions.test.ts +117 -0
- package/apps/web/src/lib/__tests__/db.integration.test.ts +271 -0
- package/apps/web/src/lib/__tests__/diff.test.ts +102 -0
- package/apps/web/src/lib/__tests__/mcp-mask.test.ts +274 -0
- package/apps/web/src/lib/__tests__/skill-templates.test.ts +237 -0
- package/apps/web/src/lib/__tests__/slack-manifest.test.ts +105 -0
- package/apps/web/src/lib/api-guard.ts +68 -0
- package/apps/web/src/lib/auth-context.tsx +71 -0
- package/apps/web/src/lib/auth.ts +128 -0
- package/apps/web/src/lib/boss-registry.ts +90 -0
- package/apps/web/src/lib/compile.ts +51 -0
- package/apps/web/src/lib/db.ts +1196 -0
- package/apps/web/src/lib/diff.ts +43 -0
- package/apps/web/src/lib/mcp-mask.ts +91 -0
- package/apps/web/src/lib/portal.tsx +23 -0
- package/apps/web/src/lib/skill-templates.ts +148 -0
- package/apps/web/src/lib/slack-manifest.ts +85 -0
- package/apps/web/src/middleware.ts +68 -0
- package/apps/web/tailwind.config.js +6 -0
- package/apps/web/tsconfig.json +23 -0
- package/apps/web/vitest.config.mts +21 -0
- package/cli/.claude/settings.local.json +6 -0
- package/cli/README.md +281 -0
- package/cli/node_modules/.package-lock.json +427 -0
- package/cli/node_modules/commander/LICENSE +22 -0
- package/cli/node_modules/commander/Readme.md +1157 -0
- package/cli/node_modules/commander/esm.mjs +16 -0
- package/cli/node_modules/commander/index.js +24 -0
- package/cli/node_modules/commander/lib/argument.js +149 -0
- package/cli/node_modules/commander/lib/command.js +2509 -0
- package/cli/node_modules/commander/lib/error.js +39 -0
- package/cli/node_modules/commander/lib/help.js +520 -0
- package/cli/node_modules/commander/lib/option.js +330 -0
- package/cli/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/cli/node_modules/commander/package-support.json +16 -0
- package/cli/node_modules/commander/package.json +84 -0
- package/cli/node_modules/commander/typings/esm.d.mts +3 -0
- package/cli/node_modules/commander/typings/index.d.ts +969 -0
- package/cli/package-lock.json +449 -0
- package/cli/package.json +44 -0
- package/cli/src/commands/init.ts +514 -0
- package/cli/src/commands/manage.ts +115 -0
- package/cli/src/index.ts +63 -0
- package/cli/tsconfig.json +14 -0
- package/docker-compose.yml +122 -0
- package/docs/agents/boss-agents.mdx +108 -0
- package/docs/agents/creating-agents.mdx +132 -0
- package/docs/agents/memory.mdx +113 -0
- package/docs/agents/tools.mdx +103 -0
- package/docs/configuration/env-vars.mdx +166 -0
- package/docs/configuration/mcp-servers.mdx +203 -0
- package/docs/configuration/slack-app.mdx +175 -0
- package/docs/docs.json +79 -0
- package/docs/favicon.svg +17 -0
- package/docs/features/history.mdx +60 -0
- package/docs/features/import-export.mdx +77 -0
- package/docs/features/logs.mdx +131 -0
- package/docs/features/multi-workspace.mdx +90 -0
- package/docs/features/scheduled-jobs.mdx +231 -0
- package/docs/features/users.mdx +92 -0
- package/docs/introduction.mdx +160 -0
- package/docs/logo/dark.svg +17 -0
- package/docs/logo/light.svg +17 -0
- package/docs/logo/wide-dark.svg +12 -0
- package/docs/logo/wide-light.svg +12 -0
- package/docs/quickstart.mdx +270 -0
- package/docs/self-hosting/docker.mdx +151 -0
- package/docs/self-hosting/production.mdx +176 -0
- package/package.json +20 -36
- package/packages/shared/dist/index.d.ts +8 -0
- package/packages/shared/dist/index.d.ts.map +1 -0
- package/packages/shared/dist/index.js +24 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/shared/dist/types.d.ts +584 -0
- package/packages/shared/dist/types.d.ts.map +1 -0
- package/packages/shared/dist/types.js +39 -0
- package/packages/shared/dist/types.js.map +1 -0
- package/packages/shared/package.json +15 -0
- package/packages/shared/src/db/schema.sql +354 -0
- package/packages/shared/src/index.ts +8 -0
- package/packages/shared/src/types.ts +683 -0
- package/packages/shared/tsconfig.json +17 -0
- package/scripts/dev.sh +45 -0
- /package/{dist → cli/dist}/commands/init.d.ts +0 -0
- /package/{dist → cli/dist}/commands/init.js +0 -0
- /package/{dist → cli/dist}/commands/manage.d.ts +0 -0
- /package/{dist → cli/dist}/commands/manage.js +0 -0
- /package/{dist → cli/dist}/index.d.ts +0 -0
- /package/{dist → cli/dist}/index.js +0 -0
|
@@ -0,0 +1,1196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview PostgreSQL database client for the Next.js web application.
|
|
3
|
+
*
|
|
4
|
+
* Provides a singleton connection pool and typed query functions for
|
|
5
|
+
* all tables managed by the web UI: agents, mcp_servers, agent_mcps,
|
|
6
|
+
* skills, permissions, and memories.
|
|
7
|
+
*
|
|
8
|
+
* Also provides a Redis client for publishing agent lifecycle events
|
|
9
|
+
* to the runner service (hot-reload).
|
|
10
|
+
*
|
|
11
|
+
* @module web/lib/db
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Pool } from 'pg';
|
|
15
|
+
import { createClient } from 'redis';
|
|
16
|
+
import type {
|
|
17
|
+
Agent,
|
|
18
|
+
McpServer,
|
|
19
|
+
Skill,
|
|
20
|
+
Permission,
|
|
21
|
+
Memory,
|
|
22
|
+
Session,
|
|
23
|
+
ScheduledJob,
|
|
24
|
+
JobRun,
|
|
25
|
+
CreateJobRequest,
|
|
26
|
+
UpdateJobRequest,
|
|
27
|
+
AgentEvent,
|
|
28
|
+
AgentStatus,
|
|
29
|
+
UpsertMcpServerRequest,
|
|
30
|
+
CreateAgentRequest,
|
|
31
|
+
UpdateAgentRequest,
|
|
32
|
+
AgentSnapshot,
|
|
33
|
+
SnapshotSkill,
|
|
34
|
+
SnapshotTrigger,
|
|
35
|
+
Restriction,
|
|
36
|
+
} from '@slackhive/shared';
|
|
37
|
+
import { AGENT_EVENTS_CHANNEL } from '@slackhive/shared';
|
|
38
|
+
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// Connection pool
|
|
41
|
+
// =============================================================================
|
|
42
|
+
|
|
43
|
+
let pool: Pool | null = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the singleton Postgres connection pool for the web app.
|
|
47
|
+
*
|
|
48
|
+
* @returns {Pool} Postgres connection pool.
|
|
49
|
+
* @throws {Error} If DATABASE_URL is not configured.
|
|
50
|
+
*/
|
|
51
|
+
function getPool(): Pool {
|
|
52
|
+
if (!pool) {
|
|
53
|
+
const url = process.env.DATABASE_URL;
|
|
54
|
+
if (!url) throw new Error('DATABASE_URL is required');
|
|
55
|
+
pool = new Pool({ connectionString: url, max: 5 });
|
|
56
|
+
}
|
|
57
|
+
return pool;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Redis publisher
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
let redisPublisher: ReturnType<typeof createClient> | null = null;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns a connected Redis client for publishing agent events.
|
|
68
|
+
* Lazy-connects on first call.
|
|
69
|
+
*
|
|
70
|
+
* @returns {Promise<ReturnType<typeof createClient>>} Connected Redis client.
|
|
71
|
+
*/
|
|
72
|
+
async function getRedis(): Promise<ReturnType<typeof createClient>> {
|
|
73
|
+
if (!redisPublisher || !redisPublisher.isOpen) {
|
|
74
|
+
redisPublisher = createClient({ url: process.env.REDIS_URL ?? 'redis://localhost:6379' });
|
|
75
|
+
await redisPublisher.connect();
|
|
76
|
+
}
|
|
77
|
+
return redisPublisher;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Publishes an agent lifecycle event to the runner via Redis.
|
|
82
|
+
* Used to trigger hot-reload when config changes in the UI.
|
|
83
|
+
*
|
|
84
|
+
* @param {AgentEvent} event - The lifecycle event to publish.
|
|
85
|
+
* @returns {Promise<void>}
|
|
86
|
+
*/
|
|
87
|
+
export async function publishAgentEvent(event: AgentEvent): Promise<void> {
|
|
88
|
+
const redis = await getRedis();
|
|
89
|
+
await redis.publish(AGENT_EVENTS_CHANNEL, JSON.stringify(event));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// Row mappers
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Maps a raw DB row to an {@link Agent} interface.
|
|
98
|
+
*
|
|
99
|
+
* @param {Record<string, unknown>} row - Raw row from the agents table.
|
|
100
|
+
* @returns {Agent} Typed agent object.
|
|
101
|
+
*/
|
|
102
|
+
function rowToAgent(row: Record<string, unknown>): Agent {
|
|
103
|
+
return {
|
|
104
|
+
id: row.id as string,
|
|
105
|
+
slug: row.slug as string,
|
|
106
|
+
name: row.name as string,
|
|
107
|
+
persona: row.persona as string | undefined,
|
|
108
|
+
description: row.description as string | undefined,
|
|
109
|
+
slackBotToken: row.slack_bot_token as string,
|
|
110
|
+
slackAppToken: row.slack_app_token as string,
|
|
111
|
+
slackSigningSecret: row.slack_signing_secret as string,
|
|
112
|
+
slackBotUserId: row.slack_bot_user_id as string | undefined,
|
|
113
|
+
model: row.model as string,
|
|
114
|
+
status: row.status as AgentStatus,
|
|
115
|
+
enabled: row.enabled !== false,
|
|
116
|
+
isBoss: row.is_boss as boolean,
|
|
117
|
+
reportsTo: (row.reports_to as string[]) ?? [],
|
|
118
|
+
claudeMd: (row.claude_md as string) ?? '',
|
|
119
|
+
createdBy: (row.created_by as string) ?? 'system',
|
|
120
|
+
createdAt: row.created_at as Date,
|
|
121
|
+
updatedAt: row.updated_at as Date,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Maps a raw DB row to a {@link McpServer} interface.
|
|
127
|
+
*
|
|
128
|
+
* @param {Record<string, unknown>} row - Raw row from the mcp_servers table.
|
|
129
|
+
* @returns {McpServer} Typed MCP server object.
|
|
130
|
+
*/
|
|
131
|
+
function rowToMcpServer(row: Record<string, unknown>): McpServer {
|
|
132
|
+
return {
|
|
133
|
+
id: row.id as string,
|
|
134
|
+
name: row.name as string,
|
|
135
|
+
type: row.type as McpServer['type'],
|
|
136
|
+
config: row.config as McpServer['config'],
|
|
137
|
+
description: row.description as string | undefined,
|
|
138
|
+
enabled: row.enabled as boolean,
|
|
139
|
+
createdAt: row.created_at as Date,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Maps a raw DB row to a {@link Skill} interface.
|
|
145
|
+
*
|
|
146
|
+
* @param {Record<string, unknown>} row - Raw row from the skills table.
|
|
147
|
+
* @returns {Skill} Typed skill object.
|
|
148
|
+
*/
|
|
149
|
+
function rowToSkill(row: Record<string, unknown>): Skill {
|
|
150
|
+
return {
|
|
151
|
+
id: row.id as string,
|
|
152
|
+
agentId: row.agent_id as string,
|
|
153
|
+
category: row.category as string,
|
|
154
|
+
filename: row.filename as string,
|
|
155
|
+
content: row.content as string,
|
|
156
|
+
sortOrder: row.sort_order as number,
|
|
157
|
+
createdAt: row.created_at as Date,
|
|
158
|
+
updatedAt: row.updated_at as Date,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Maps a raw DB row to a {@link Memory} interface.
|
|
164
|
+
*
|
|
165
|
+
* @param {Record<string, unknown>} row - Raw row from the memories table.
|
|
166
|
+
* @returns {Memory} Typed memory object.
|
|
167
|
+
*/
|
|
168
|
+
function rowToMemory(row: Record<string, unknown>): Memory {
|
|
169
|
+
return {
|
|
170
|
+
id: row.id as string,
|
|
171
|
+
agentId: row.agent_id as string,
|
|
172
|
+
type: row.type as Memory['type'],
|
|
173
|
+
name: row.name as string,
|
|
174
|
+
content: row.content as string,
|
|
175
|
+
createdAt: row.created_at as Date,
|
|
176
|
+
updatedAt: row.updated_at as Date,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// =============================================================================
|
|
181
|
+
// Agent queries
|
|
182
|
+
// =============================================================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Returns all agents ordered boss-first, then alphabetically by name.
|
|
186
|
+
*
|
|
187
|
+
* @returns {Promise<Agent[]>} All registered agents.
|
|
188
|
+
*/
|
|
189
|
+
export async function getAllAgents(): Promise<Agent[]> {
|
|
190
|
+
const r = await getPool().query('SELECT * FROM agents ORDER BY is_boss DESC, name ASC');
|
|
191
|
+
return r.rows.map(rowToAgent);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns a single agent by UUID.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} id - Agent UUID.
|
|
198
|
+
* @returns {Promise<Agent | null>} The agent, or null if not found.
|
|
199
|
+
*/
|
|
200
|
+
export async function getAgentById(id: string): Promise<Agent | null> {
|
|
201
|
+
const r = await getPool().query('SELECT * FROM agents WHERE id = $1', [id]);
|
|
202
|
+
return r.rows.length ? rowToAgent(r.rows[0]) : null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Returns a single agent by its URL-safe slug.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} slug - Agent slug (e.g. `data-analyst`).
|
|
209
|
+
* @returns {Promise<Agent | null>} The agent, or null if not found.
|
|
210
|
+
*/
|
|
211
|
+
export async function getAgentBySlug(slug: string): Promise<Agent | null> {
|
|
212
|
+
const r = await getPool().query('SELECT * FROM agents WHERE slug = $1', [slug]);
|
|
213
|
+
return r.rows.length ? rowToAgent(r.rows[0]) : null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Creates a new agent record in the database.
|
|
218
|
+
*
|
|
219
|
+
* @param {CreateAgentRequest} req - Agent creation data.
|
|
220
|
+
* @returns {Promise<Agent>} The created agent.
|
|
221
|
+
*/
|
|
222
|
+
export async function createAgent(req: CreateAgentRequest, createdBy = 'system'): Promise<Agent> {
|
|
223
|
+
const r = await getPool().query(
|
|
224
|
+
`INSERT INTO agents
|
|
225
|
+
(slug, name, persona, description, slack_bot_token, slack_app_token,
|
|
226
|
+
slack_signing_secret, model, is_boss, reports_to, created_by)
|
|
227
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
|
|
228
|
+
RETURNING *`,
|
|
229
|
+
[
|
|
230
|
+
req.slug, req.name, req.persona ?? null, req.description ?? null,
|
|
231
|
+
req.slackBotToken, req.slackAppToken, req.slackSigningSecret,
|
|
232
|
+
req.model ?? 'claude-opus-4-6', req.isBoss ?? false, req.reportsTo ?? [],
|
|
233
|
+
createdBy,
|
|
234
|
+
]
|
|
235
|
+
);
|
|
236
|
+
return rowToAgent(r.rows[0]);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Updates an agent's status field.
|
|
241
|
+
*
|
|
242
|
+
* @param {string} id - Agent UUID.
|
|
243
|
+
* @param {AgentStatus} status - New status.
|
|
244
|
+
* @returns {Promise<void>}
|
|
245
|
+
*/
|
|
246
|
+
export async function updateAgentStatus(id: string, status: AgentStatus): Promise<void> {
|
|
247
|
+
await getPool().query(
|
|
248
|
+
'UPDATE agents SET status = $1, updated_at = now() WHERE id = $2',
|
|
249
|
+
[status, id]
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function updateAgentEnabled(id: string, enabled: boolean): Promise<void> {
|
|
254
|
+
await getPool().query(
|
|
255
|
+
'UPDATE agents SET enabled = $1, updated_at = now() WHERE id = $2',
|
|
256
|
+
[enabled, id]
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Updates mutable fields on an agent record.
|
|
262
|
+
*
|
|
263
|
+
* @param {string} id - Agent UUID.
|
|
264
|
+
* @param {UpdateAgentRequest} req - Fields to update.
|
|
265
|
+
* @returns {Promise<Agent | null>} The updated agent, or null if not found.
|
|
266
|
+
*/
|
|
267
|
+
export async function updateAgent(id: string, req: UpdateAgentRequest): Promise<Agent | null> {
|
|
268
|
+
const fields: string[] = [];
|
|
269
|
+
const values: unknown[] = [];
|
|
270
|
+
let idx = 1;
|
|
271
|
+
|
|
272
|
+
if (req.name !== undefined) { fields.push(`name = $${idx++}`); values.push(req.name); }
|
|
273
|
+
if (req.persona !== undefined) { fields.push(`persona = $${idx++}`); values.push(req.persona); }
|
|
274
|
+
if (req.description !== undefined) { fields.push(`description = $${idx++}`); values.push(req.description); }
|
|
275
|
+
if (req.slackBotToken !== undefined) { fields.push(`slack_bot_token = $${idx++}`); values.push(req.slackBotToken); }
|
|
276
|
+
if (req.slackAppToken !== undefined) { fields.push(`slack_app_token = $${idx++}`); values.push(req.slackAppToken); }
|
|
277
|
+
if (req.slackSigningSecret !== undefined) { fields.push(`slack_signing_secret = $${idx++}`); values.push(req.slackSigningSecret); }
|
|
278
|
+
if (req.model !== undefined) { fields.push(`model = $${idx++}`); values.push(req.model); }
|
|
279
|
+
if (req.isBoss !== undefined) { fields.push(`is_boss = $${idx++}`); values.push(req.isBoss); }
|
|
280
|
+
if (req.reportsTo !== undefined) { fields.push(`reports_to = $${idx++}`); values.push(req.reportsTo); }
|
|
281
|
+
|
|
282
|
+
if (fields.length === 0) return getAgentById(id);
|
|
283
|
+
|
|
284
|
+
fields.push(`updated_at = now()`);
|
|
285
|
+
values.push(id);
|
|
286
|
+
const r = await getPool().query(
|
|
287
|
+
`UPDATE agents SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
|
|
288
|
+
values
|
|
289
|
+
);
|
|
290
|
+
return r.rows.length ? rowToAgent(r.rows[0]) : null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Replaces the CLAUDE.md content for an agent.
|
|
295
|
+
* This is the main instruction/identity file, separate from skills.
|
|
296
|
+
*
|
|
297
|
+
* @param {string} id - Agent UUID.
|
|
298
|
+
* @param {string} content - New CLAUDE.md content.
|
|
299
|
+
* @returns {Promise<void>}
|
|
300
|
+
*/
|
|
301
|
+
export async function updateAgentClaudeMd(id: string, content: string): Promise<void> {
|
|
302
|
+
await getPool().query(
|
|
303
|
+
'UPDATE agents SET claude_md = $1, updated_at = now() WHERE id = $2',
|
|
304
|
+
[content, id]
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Deletes an agent and all related records (cascades via FK).
|
|
310
|
+
*
|
|
311
|
+
* @param {string} id - Agent UUID.
|
|
312
|
+
* @returns {Promise<void>}
|
|
313
|
+
*/
|
|
314
|
+
export async function deleteAgent(id: string): Promise<void> {
|
|
315
|
+
await getPool().query('DELETE FROM agents WHERE id = $1', [id]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Returns all active sessions for an agent.
|
|
320
|
+
*
|
|
321
|
+
* @param {string} agentId - Agent UUID.
|
|
322
|
+
* @returns {Promise<Session[]>} Array of sessions.
|
|
323
|
+
*/
|
|
324
|
+
export async function getAgentSessions(agentId: string): Promise<Session[]> {
|
|
325
|
+
const r = await getPool().query(
|
|
326
|
+
'SELECT * FROM sessions WHERE agent_id = $1 ORDER BY last_activity DESC',
|
|
327
|
+
[agentId]
|
|
328
|
+
);
|
|
329
|
+
return r.rows.map(row => ({
|
|
330
|
+
id: row.id as string,
|
|
331
|
+
agentId: row.agent_id as string,
|
|
332
|
+
sessionKey: row.session_key as string,
|
|
333
|
+
claudeSessionId: row.claude_session_id as string | undefined,
|
|
334
|
+
lastActivity: row.last_activity as Date,
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Creates or updates a memory entry for an agent.
|
|
340
|
+
*
|
|
341
|
+
* @param {string} agentId - Agent UUID.
|
|
342
|
+
* @param {string} type - Memory type.
|
|
343
|
+
* @param {string} name - Memory name.
|
|
344
|
+
* @param {string} content - Memory markdown content.
|
|
345
|
+
* @returns {Promise<Memory>} The upserted memory.
|
|
346
|
+
*/
|
|
347
|
+
export async function upsertMemory(
|
|
348
|
+
agentId: string,
|
|
349
|
+
type: string,
|
|
350
|
+
name: string,
|
|
351
|
+
content: string
|
|
352
|
+
): Promise<Memory> {
|
|
353
|
+
const r = await getPool().query(
|
|
354
|
+
`INSERT INTO memories (agent_id, type, name, content)
|
|
355
|
+
VALUES ($1, $2, $3, $4)
|
|
356
|
+
ON CONFLICT (agent_id, name) DO UPDATE
|
|
357
|
+
SET type = EXCLUDED.type, content = EXCLUDED.content, updated_at = now()
|
|
358
|
+
RETURNING *`,
|
|
359
|
+
[agentId, type, name, content]
|
|
360
|
+
);
|
|
361
|
+
return rowToMemory(r.rows[0]);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// =============================================================================
|
|
365
|
+
// MCP server queries
|
|
366
|
+
// =============================================================================
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Returns all MCP servers in the global catalog, ordered by name.
|
|
370
|
+
*
|
|
371
|
+
* @returns {Promise<McpServer[]>} All registered MCP servers.
|
|
372
|
+
*/
|
|
373
|
+
export async function getAllMcpServers(): Promise<McpServer[]> {
|
|
374
|
+
const r = await getPool().query('SELECT * FROM mcp_servers ORDER BY name ASC');
|
|
375
|
+
return r.rows.map(rowToMcpServer);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Returns all MCP servers currently assigned to an agent.
|
|
380
|
+
*
|
|
381
|
+
* @param {string} agentId - Agent UUID.
|
|
382
|
+
* @returns {Promise<McpServer[]>} MCP servers assigned to this agent.
|
|
383
|
+
*/
|
|
384
|
+
export async function getAgentMcpServers(agentId: string): Promise<McpServer[]> {
|
|
385
|
+
const r = await getPool().query(
|
|
386
|
+
`SELECT m.* FROM mcp_servers m
|
|
387
|
+
JOIN agent_mcps am ON am.mcp_id = m.id
|
|
388
|
+
WHERE am.agent_id = $1 ORDER BY m.name`,
|
|
389
|
+
[agentId]
|
|
390
|
+
);
|
|
391
|
+
return r.rows.map(rowToMcpServer);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Creates a new MCP server in the global catalog.
|
|
396
|
+
*
|
|
397
|
+
* @param {UpsertMcpServerRequest} req - MCP server data.
|
|
398
|
+
* @returns {Promise<McpServer>} The created MCP server.
|
|
399
|
+
*/
|
|
400
|
+
export async function createMcpServer(req: UpsertMcpServerRequest): Promise<McpServer> {
|
|
401
|
+
const r = await getPool().query(
|
|
402
|
+
`INSERT INTO mcp_servers (name, type, config, description, enabled)
|
|
403
|
+
VALUES ($1, $2, $3, $4, $5) RETURNING *`,
|
|
404
|
+
[req.name, req.type, JSON.stringify(req.config), req.description ?? null, req.enabled ?? true]
|
|
405
|
+
);
|
|
406
|
+
return rowToMcpServer(r.rows[0]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Updates an existing MCP server.
|
|
411
|
+
*
|
|
412
|
+
* @param {string} id - MCP server UUID.
|
|
413
|
+
* @param {Partial<UpsertMcpServerRequest>} req - Fields to update.
|
|
414
|
+
* @returns {Promise<McpServer | null>} The updated record, or null if not found.
|
|
415
|
+
*/
|
|
416
|
+
export async function updateMcpServer(
|
|
417
|
+
id: string,
|
|
418
|
+
req: Partial<UpsertMcpServerRequest>
|
|
419
|
+
): Promise<McpServer | null> {
|
|
420
|
+
const fields: string[] = [];
|
|
421
|
+
const values: unknown[] = [];
|
|
422
|
+
let idx = 1;
|
|
423
|
+
|
|
424
|
+
if (req.name !== undefined) { fields.push(`name = $${idx++}`); values.push(req.name); }
|
|
425
|
+
if (req.type !== undefined) { fields.push(`type = $${idx++}`); values.push(req.type); }
|
|
426
|
+
if (req.config !== undefined) { fields.push(`config = $${idx++}`); values.push(JSON.stringify(req.config)); }
|
|
427
|
+
if (req.description !== undefined) { fields.push(`description = $${idx++}`); values.push(req.description); }
|
|
428
|
+
if (req.enabled !== undefined) { fields.push(`enabled = $${idx++}`); values.push(req.enabled); }
|
|
429
|
+
|
|
430
|
+
if (fields.length === 0) return getMcpServerById(id);
|
|
431
|
+
|
|
432
|
+
values.push(id);
|
|
433
|
+
const r = await getPool().query(
|
|
434
|
+
`UPDATE mcp_servers SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`,
|
|
435
|
+
values
|
|
436
|
+
);
|
|
437
|
+
return r.rows.length ? rowToMcpServer(r.rows[0]) : null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Returns a single MCP server by UUID.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} id - MCP server UUID.
|
|
444
|
+
* @returns {Promise<McpServer | null>} The server, or null if not found.
|
|
445
|
+
*/
|
|
446
|
+
export async function getMcpServerById(id: string): Promise<McpServer | null> {
|
|
447
|
+
const r = await getPool().query('SELECT * FROM mcp_servers WHERE id = $1', [id]);
|
|
448
|
+
return r.rows.length ? rowToMcpServer(r.rows[0]) : null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Deletes an MCP server from the global catalog.
|
|
453
|
+
* Any agent_mcps assignments are removed via CASCADE.
|
|
454
|
+
*
|
|
455
|
+
* @param {string} id - MCP server UUID.
|
|
456
|
+
* @returns {Promise<void>}
|
|
457
|
+
*/
|
|
458
|
+
export async function deleteMcpServer(id: string): Promise<void> {
|
|
459
|
+
await getPool().query('DELETE FROM mcp_servers WHERE id = $1', [id]);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Atomically replaces all MCP server assignments for an agent.
|
|
464
|
+
* Deletes existing assignments and inserts the new set in a single transaction.
|
|
465
|
+
*
|
|
466
|
+
* @param {string} agentId - Agent UUID.
|
|
467
|
+
* @param {string[]} mcpIds - UUIDs of MCP servers to assign.
|
|
468
|
+
* @returns {Promise<void>}
|
|
469
|
+
* @throws {Error} If the transaction fails; changes are rolled back automatically.
|
|
470
|
+
*/
|
|
471
|
+
export async function setAgentMcps(agentId: string, mcpIds: string[]): Promise<void> {
|
|
472
|
+
const client = await getPool().connect();
|
|
473
|
+
try {
|
|
474
|
+
await client.query('BEGIN');
|
|
475
|
+
await client.query('DELETE FROM agent_mcps WHERE agent_id = $1', [agentId]);
|
|
476
|
+
for (const mcpId of mcpIds) {
|
|
477
|
+
await client.query(
|
|
478
|
+
'INSERT INTO agent_mcps (agent_id, mcp_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
|
|
479
|
+
[agentId, mcpId]
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
await client.query('COMMIT');
|
|
483
|
+
} catch (e) {
|
|
484
|
+
await client.query('ROLLBACK');
|
|
485
|
+
throw e;
|
|
486
|
+
} finally {
|
|
487
|
+
client.release();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// =============================================================================
|
|
492
|
+
// Skills queries
|
|
493
|
+
// =============================================================================
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Returns all skills for an agent ordered for CLAUDE.md compilation:
|
|
497
|
+
* category ASC → sort_order ASC → filename ASC.
|
|
498
|
+
*
|
|
499
|
+
* @param {string} agentId - Agent UUID.
|
|
500
|
+
* @returns {Promise<Skill[]>} Ordered skill files.
|
|
501
|
+
*/
|
|
502
|
+
export async function getAgentSkills(agentId: string): Promise<Skill[]> {
|
|
503
|
+
const r = await getPool().query(
|
|
504
|
+
'SELECT * FROM skills WHERE agent_id = $1 ORDER BY category, sort_order, filename',
|
|
505
|
+
[agentId]
|
|
506
|
+
);
|
|
507
|
+
return r.rows.map(rowToSkill);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Creates or updates a skill file for an agent.
|
|
512
|
+
* Conflicts on (agent_id, category, filename) update content and sort_order.
|
|
513
|
+
*
|
|
514
|
+
* @param {string} agentId - Agent UUID.
|
|
515
|
+
* @param {string} category - Skill category directory (e.g. `'00-core'`).
|
|
516
|
+
* @param {string} filename - Skill filename (e.g. `'main.md'`).
|
|
517
|
+
* @param {string} content - Full markdown content of the skill.
|
|
518
|
+
* @param {number} [sortOrder=0] - Sort order within the category.
|
|
519
|
+
* @returns {Promise<Skill>} The upserted skill.
|
|
520
|
+
*/
|
|
521
|
+
export async function upsertSkill(
|
|
522
|
+
agentId: string,
|
|
523
|
+
category: string,
|
|
524
|
+
filename: string,
|
|
525
|
+
content: string,
|
|
526
|
+
sortOrder = 0
|
|
527
|
+
): Promise<Skill> {
|
|
528
|
+
const r = await getPool().query(
|
|
529
|
+
`INSERT INTO skills (agent_id, category, filename, content, sort_order)
|
|
530
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
531
|
+
ON CONFLICT (agent_id, category, filename)
|
|
532
|
+
DO UPDATE SET content = EXCLUDED.content, sort_order = EXCLUDED.sort_order, updated_at = now()
|
|
533
|
+
RETURNING *`,
|
|
534
|
+
[agentId, category, filename, content, sortOrder]
|
|
535
|
+
);
|
|
536
|
+
return rowToSkill(r.rows[0]);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Deletes a single skill by UUID.
|
|
541
|
+
*
|
|
542
|
+
* @param {string} id - Skill UUID.
|
|
543
|
+
* @returns {Promise<void>}
|
|
544
|
+
*/
|
|
545
|
+
export async function deleteSkill(id: string): Promise<void> {
|
|
546
|
+
await getPool().query('DELETE FROM skills WHERE id = $1', [id]);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Deletes all skill files for an agent. Used when replacing skills wholesale.
|
|
551
|
+
*
|
|
552
|
+
* @param {string} agentId - Agent UUID.
|
|
553
|
+
* @returns {Promise<void>}
|
|
554
|
+
*/
|
|
555
|
+
export async function deleteSkillsByAgent(agentId: string): Promise<void> {
|
|
556
|
+
await getPool().query('DELETE FROM skills WHERE agent_id = $1', [agentId]);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// =============================================================================
|
|
560
|
+
// Permissions queries
|
|
561
|
+
// =============================================================================
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Returns the tool allow/deny permissions for an agent.
|
|
565
|
+
*
|
|
566
|
+
* @param {string} agentId - Agent UUID.
|
|
567
|
+
* @returns {Promise<Permission | null>} The permission record, or null if not configured.
|
|
568
|
+
*/
|
|
569
|
+
export async function getAgentPermissions(agentId: string): Promise<Permission | null> {
|
|
570
|
+
const r = await getPool().query('SELECT * FROM permissions WHERE agent_id = $1', [agentId]);
|
|
571
|
+
if (!r.rows.length) return null;
|
|
572
|
+
const row = r.rows[0];
|
|
573
|
+
return {
|
|
574
|
+
id: row.id,
|
|
575
|
+
agentId: row.agent_id,
|
|
576
|
+
allowedTools: row.allowed_tools,
|
|
577
|
+
deniedTools: row.denied_tools,
|
|
578
|
+
updatedAt: row.updated_at,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Creates or replaces tool permissions for an agent.
|
|
584
|
+
* Conflicts on agent_id update both arrays in place.
|
|
585
|
+
*
|
|
586
|
+
* @param {string} agentId - Agent UUID.
|
|
587
|
+
* @param {string[]} allowedTools - Tools the agent is allowed to use.
|
|
588
|
+
* @param {string[]} deniedTools - Tools explicitly blocked for this agent.
|
|
589
|
+
* @returns {Promise<void>}
|
|
590
|
+
*/
|
|
591
|
+
export async function upsertPermissions(
|
|
592
|
+
agentId: string,
|
|
593
|
+
allowedTools: string[],
|
|
594
|
+
deniedTools: string[]
|
|
595
|
+
): Promise<void> {
|
|
596
|
+
await getPool().query(
|
|
597
|
+
`INSERT INTO permissions (agent_id, allowed_tools, denied_tools)
|
|
598
|
+
VALUES ($1, $2, $3)
|
|
599
|
+
ON CONFLICT (agent_id)
|
|
600
|
+
DO UPDATE SET allowed_tools = $2, denied_tools = $3, updated_at = now()`,
|
|
601
|
+
[agentId, allowedTools, deniedTools]
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// =============================================================================
|
|
606
|
+
// Restrictions queries
|
|
607
|
+
// =============================================================================
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Returns the channel restrictions for an agent.
|
|
611
|
+
*
|
|
612
|
+
* @param {string} agentId - Agent UUID.
|
|
613
|
+
* @returns {Promise<Restriction | null>} The restriction record, or null if not configured.
|
|
614
|
+
*/
|
|
615
|
+
export async function getAgentRestrictions(agentId: string): Promise<Restriction | null> {
|
|
616
|
+
const r = await getPool().query('SELECT * FROM agent_restrictions WHERE agent_id = $1', [agentId]);
|
|
617
|
+
if (!r.rows.length) return null;
|
|
618
|
+
const row = r.rows[0];
|
|
619
|
+
return {
|
|
620
|
+
id: row.id,
|
|
621
|
+
agentId: row.agent_id,
|
|
622
|
+
allowedChannels: (row.allowed_channels as string[]) ?? [],
|
|
623
|
+
updatedAt: row.updated_at,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Creates or replaces channel restrictions for an agent.
|
|
629
|
+
*
|
|
630
|
+
* @param {string} agentId - Agent UUID.
|
|
631
|
+
* @param {string[]} allowedChannels - Channel IDs the bot is allowed to respond in.
|
|
632
|
+
* @returns {Promise<void>}
|
|
633
|
+
*/
|
|
634
|
+
export async function upsertRestrictions(
|
|
635
|
+
agentId: string,
|
|
636
|
+
allowedChannels: string[],
|
|
637
|
+
): Promise<void> {
|
|
638
|
+
await getPool().query(
|
|
639
|
+
`INSERT INTO agent_restrictions (agent_id, allowed_channels)
|
|
640
|
+
VALUES ($1, $2)
|
|
641
|
+
ON CONFLICT (agent_id)
|
|
642
|
+
DO UPDATE SET allowed_channels = $2, updated_at = now()`,
|
|
643
|
+
[agentId, allowedChannels]
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// =============================================================================
|
|
648
|
+
// Memory queries
|
|
649
|
+
// =============================================================================
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Returns all memory entries for an agent, ordered by type then creation time.
|
|
653
|
+
*
|
|
654
|
+
* @param {string} agentId - Agent UUID.
|
|
655
|
+
* @returns {Promise<Memory[]>} All memory entries for this agent.
|
|
656
|
+
*/
|
|
657
|
+
export async function getAgentMemories(agentId: string): Promise<Memory[]> {
|
|
658
|
+
const r = await getPool().query(
|
|
659
|
+
'SELECT * FROM memories WHERE agent_id = $1 ORDER BY type, created_at',
|
|
660
|
+
[agentId]
|
|
661
|
+
);
|
|
662
|
+
return r.rows.map(rowToMemory);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Deletes a memory entry by UUID.
|
|
667
|
+
*
|
|
668
|
+
* @param {string} id - Memory UUID.
|
|
669
|
+
* @returns {Promise<void>}
|
|
670
|
+
*/
|
|
671
|
+
export async function deleteMemory(id: string): Promise<void> {
|
|
672
|
+
await getPool().query('DELETE FROM memories WHERE id = $1', [id]);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// =============================================================================
|
|
676
|
+
// Settings queries
|
|
677
|
+
// =============================================================================
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Returns a single setting value by key.
|
|
681
|
+
*
|
|
682
|
+
* @param {string} key - The setting key to look up.
|
|
683
|
+
* @returns {Promise<string | null>} The value, or null if not set.
|
|
684
|
+
*/
|
|
685
|
+
export async function getSetting(key: string): Promise<string | null> {
|
|
686
|
+
const r = await getPool().query('SELECT value FROM settings WHERE key = $1', [key]);
|
|
687
|
+
return r.rows.length ? (r.rows[0].value as string) : null;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Creates or updates a single setting.
|
|
692
|
+
*
|
|
693
|
+
* @param {string} key - The setting key.
|
|
694
|
+
* @param {string} value - The setting value.
|
|
695
|
+
* @returns {Promise<void>}
|
|
696
|
+
*/
|
|
697
|
+
export async function setSetting(key: string, value: string): Promise<void> {
|
|
698
|
+
await getPool().query(
|
|
699
|
+
`INSERT INTO settings (key, value)
|
|
700
|
+
VALUES ($1, $2)
|
|
701
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`,
|
|
702
|
+
[key, value]
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Returns all settings as a flat key-value map.
|
|
708
|
+
*
|
|
709
|
+
* @returns {Promise<Record<string, string>>} All stored settings.
|
|
710
|
+
*/
|
|
711
|
+
export async function getAllSettings(): Promise<Record<string, string>> {
|
|
712
|
+
const r = await getPool().query('SELECT key, value FROM settings ORDER BY key');
|
|
713
|
+
const result: Record<string, string> = {};
|
|
714
|
+
for (const row of r.rows) {
|
|
715
|
+
result[row.key as string] = row.value as string;
|
|
716
|
+
}
|
|
717
|
+
return result;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// =============================================================================
|
|
721
|
+
// User queries
|
|
722
|
+
// =============================================================================
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Returns a user by username including password hash (for auth).
|
|
726
|
+
*
|
|
727
|
+
* @param {string} username - The username to look up.
|
|
728
|
+
* @returns {Promise<{ id: string; username: string; passwordHash: string; role: string; createdAt: string } | null>}
|
|
729
|
+
*/
|
|
730
|
+
export async function getUserByUsername(username: string): Promise<{ id: string; username: string; passwordHash: string; role: string; createdAt: string } | null> {
|
|
731
|
+
const r = await getPool().query(
|
|
732
|
+
'SELECT id, username, password_hash, role, created_at FROM users WHERE username = $1',
|
|
733
|
+
[username]
|
|
734
|
+
);
|
|
735
|
+
if (!r.rows.length) return null;
|
|
736
|
+
const row = r.rows[0];
|
|
737
|
+
return { id: row.id, username: row.username, passwordHash: row.password_hash, role: row.role, createdAt: row.created_at };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Returns all users (without password hashes).
|
|
742
|
+
*
|
|
743
|
+
* @returns {Promise<Array<{ id: string; username: string; role: string; createdAt: string }>>}
|
|
744
|
+
*/
|
|
745
|
+
export async function getAllUsers(): Promise<Array<{ id: string; username: string; role: string; createdAt: string }>> {
|
|
746
|
+
const r = await getPool().query('SELECT id, username, role, created_at FROM users ORDER BY created_at');
|
|
747
|
+
return r.rows.map(row => ({ id: row.id, username: row.username, role: row.role, createdAt: row.created_at }));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Creates a new user.
|
|
752
|
+
*
|
|
753
|
+
* @param {string} username - Unique username.
|
|
754
|
+
* @param {string} passwordHash - Bcrypt hash.
|
|
755
|
+
* @param {string} role - 'admin' or 'viewer'.
|
|
756
|
+
* @returns {Promise<{ id: string; username: string; role: string }>}
|
|
757
|
+
*/
|
|
758
|
+
export async function createUser(username: string, passwordHash: string, role: string): Promise<{ id: string; username: string; role: string }> {
|
|
759
|
+
const r = await getPool().query(
|
|
760
|
+
'INSERT INTO users (username, password_hash, role) VALUES ($1, $2, $3) RETURNING id, username, role',
|
|
761
|
+
[username, passwordHash, role]
|
|
762
|
+
);
|
|
763
|
+
return r.rows[0];
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Deletes a user by ID.
|
|
768
|
+
*
|
|
769
|
+
* @param {string} id - User UUID.
|
|
770
|
+
*/
|
|
771
|
+
export async function deleteUser(id: string): Promise<void> {
|
|
772
|
+
await getPool().query('DELETE FROM users WHERE id = $1', [id]);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Updates a user's role.
|
|
777
|
+
*
|
|
778
|
+
* @param {string} id - User UUID.
|
|
779
|
+
* @param {string} role - New role (admin | editor | viewer).
|
|
780
|
+
* @returns {Promise<void>}
|
|
781
|
+
*/
|
|
782
|
+
export async function updateUserRole(id: string, role: string): Promise<void> {
|
|
783
|
+
await getPool().query('UPDATE users SET role = $1 WHERE id = $2', [role, id]);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// =============================================================================
|
|
787
|
+
// Agent access control
|
|
788
|
+
// =============================================================================
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Returns the list of user IDs that have explicit write access to an agent.
|
|
792
|
+
*/
|
|
793
|
+
export async function getAgentWriteUsers(agentId: string): Promise<{ userId: string; username: string }[]> {
|
|
794
|
+
const r = await getPool().query(
|
|
795
|
+
`SELECT aa.user_id, u.username
|
|
796
|
+
FROM agent_access aa
|
|
797
|
+
JOIN users u ON u.id = aa.user_id
|
|
798
|
+
WHERE aa.agent_id = $1
|
|
799
|
+
ORDER BY u.username`,
|
|
800
|
+
[agentId]
|
|
801
|
+
);
|
|
802
|
+
return r.rows.map(row => ({ userId: row.user_id as string, username: row.username as string }));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Grants write access to a user for an agent.
|
|
807
|
+
*/
|
|
808
|
+
export async function grantAgentWrite(agentId: string, userId: string): Promise<void> {
|
|
809
|
+
await getPool().query(
|
|
810
|
+
'INSERT INTO agent_access (agent_id, user_id) VALUES ($1,$2) ON CONFLICT DO NOTHING',
|
|
811
|
+
[agentId, userId]
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Revokes write access from a user for an agent.
|
|
817
|
+
*/
|
|
818
|
+
export async function revokeAgentWrite(agentId: string, userId: string): Promise<void> {
|
|
819
|
+
await getPool().query('DELETE FROM agent_access WHERE agent_id = $1 AND user_id = $2', [agentId, userId]);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Returns true if a user has write access to an agent.
|
|
824
|
+
* Write access = admin/superadmin role, OR own created agent, OR explicit grant.
|
|
825
|
+
*/
|
|
826
|
+
export async function userCanWriteAgent(agentId: string, username: string, role: string): Promise<boolean> {
|
|
827
|
+
if (role === 'admin' || role === 'superadmin') return true;
|
|
828
|
+
// Check if creator or explicitly granted
|
|
829
|
+
const r = await getPool().query(
|
|
830
|
+
`SELECT 1 FROM agents WHERE id = $1 AND created_by = $2
|
|
831
|
+
UNION
|
|
832
|
+
SELECT 1 FROM agent_access aa JOIN users u ON u.id = aa.user_id
|
|
833
|
+
WHERE aa.agent_id = $1 AND u.username = $2
|
|
834
|
+
LIMIT 1`,
|
|
835
|
+
[agentId, username]
|
|
836
|
+
);
|
|
837
|
+
return r.rows.length > 0;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// =============================================================================
|
|
841
|
+
// Scheduled Jobs
|
|
842
|
+
// =============================================================================
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Maps a DB row to a ScheduledJob object.
|
|
846
|
+
*/
|
|
847
|
+
function rowToJob(row: Record<string, unknown>): ScheduledJob {
|
|
848
|
+
return {
|
|
849
|
+
id: row.id as string,
|
|
850
|
+
agentId: row.agent_id as string,
|
|
851
|
+
name: row.name as string,
|
|
852
|
+
prompt: row.prompt as string,
|
|
853
|
+
cronSchedule: row.cron_schedule as string,
|
|
854
|
+
targetType: row.target_type as 'channel' | 'dm',
|
|
855
|
+
targetId: row.target_id as string,
|
|
856
|
+
enabled: row.enabled as boolean,
|
|
857
|
+
createdAt: row.created_at as Date,
|
|
858
|
+
updatedAt: row.updated_at as Date,
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Maps a DB row to a JobRun object.
|
|
864
|
+
*/
|
|
865
|
+
function rowToJobRun(row: Record<string, unknown>): JobRun {
|
|
866
|
+
return {
|
|
867
|
+
id: row.id as string,
|
|
868
|
+
jobId: row.job_id as string,
|
|
869
|
+
startedAt: row.started_at as Date,
|
|
870
|
+
finishedAt: (row.finished_at as Date) ?? undefined,
|
|
871
|
+
status: row.status as 'running' | 'success' | 'error',
|
|
872
|
+
output: (row.output as string) ?? undefined,
|
|
873
|
+
error: (row.error as string) ?? undefined,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Returns all scheduled jobs with their most recent run info.
|
|
879
|
+
*
|
|
880
|
+
* @returns {Promise<Array<ScheduledJob & { lastRun?: JobRun }>>}
|
|
881
|
+
*/
|
|
882
|
+
export async function getAllJobs(): Promise<Array<ScheduledJob & { lastRun?: JobRun }>> {
|
|
883
|
+
const r = await getPool().query(`
|
|
884
|
+
SELECT j.*,
|
|
885
|
+
lr.id AS lr_id, lr.started_at AS lr_started_at, lr.finished_at AS lr_finished_at,
|
|
886
|
+
lr.status AS lr_status, lr.output AS lr_output, lr.error AS lr_error
|
|
887
|
+
FROM scheduled_jobs j
|
|
888
|
+
LEFT JOIN LATERAL (
|
|
889
|
+
SELECT * FROM job_runs WHERE job_id = j.id ORDER BY started_at DESC LIMIT 1
|
|
890
|
+
) lr ON true
|
|
891
|
+
ORDER BY j.created_at DESC
|
|
892
|
+
`);
|
|
893
|
+
return r.rows.map(row => ({
|
|
894
|
+
...rowToJob(row),
|
|
895
|
+
lastRun: row.lr_id ? {
|
|
896
|
+
id: row.lr_id, jobId: row.id as string,
|
|
897
|
+
startedAt: row.lr_started_at, finishedAt: row.lr_finished_at ?? undefined,
|
|
898
|
+
status: row.lr_status, output: row.lr_output ?? undefined, error: row.lr_error ?? undefined,
|
|
899
|
+
} : undefined,
|
|
900
|
+
}));
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Returns a single job by ID.
|
|
905
|
+
*
|
|
906
|
+
* @param {string} id - Job UUID.
|
|
907
|
+
* @returns {Promise<ScheduledJob | null>}
|
|
908
|
+
*/
|
|
909
|
+
export async function getJobById(id: string): Promise<ScheduledJob | null> {
|
|
910
|
+
const r = await getPool().query('SELECT * FROM scheduled_jobs WHERE id = $1', [id]);
|
|
911
|
+
return r.rows.length ? rowToJob(r.rows[0]) : null;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Creates a new scheduled job.
|
|
916
|
+
*
|
|
917
|
+
* @param {CreateJobRequest} req - Job creation payload.
|
|
918
|
+
* @returns {Promise<ScheduledJob>}
|
|
919
|
+
*/
|
|
920
|
+
export async function createJob(req: CreateJobRequest): Promise<ScheduledJob> {
|
|
921
|
+
const r = await getPool().query(
|
|
922
|
+
`INSERT INTO scheduled_jobs (agent_id, name, prompt, cron_schedule, target_type, target_id, enabled)
|
|
923
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
|
|
924
|
+
[req.agentId, req.name, req.prompt, req.cronSchedule, req.targetType ?? 'channel', req.targetId, req.enabled ?? true]
|
|
925
|
+
);
|
|
926
|
+
return rowToJob(r.rows[0]);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Updates an existing scheduled job.
|
|
931
|
+
*
|
|
932
|
+
* @param {string} id - Job UUID.
|
|
933
|
+
* @param {UpdateJobRequest} req - Fields to update.
|
|
934
|
+
* @returns {Promise<ScheduledJob | null>}
|
|
935
|
+
*/
|
|
936
|
+
export async function updateJob(id: string, req: UpdateJobRequest): Promise<ScheduledJob | null> {
|
|
937
|
+
const sets: string[] = [];
|
|
938
|
+
const vals: unknown[] = [];
|
|
939
|
+
let i = 1;
|
|
940
|
+
if (req.agentId !== undefined) { sets.push(`agent_id = $${i++}`); vals.push(req.agentId); }
|
|
941
|
+
if (req.name !== undefined) { sets.push(`name = $${i++}`); vals.push(req.name); }
|
|
942
|
+
if (req.prompt !== undefined) { sets.push(`prompt = $${i++}`); vals.push(req.prompt); }
|
|
943
|
+
if (req.cronSchedule !== undefined) { sets.push(`cron_schedule = $${i++}`); vals.push(req.cronSchedule); }
|
|
944
|
+
if (req.targetType !== undefined) { sets.push(`target_type = $${i++}`); vals.push(req.targetType); }
|
|
945
|
+
if (req.targetId !== undefined) { sets.push(`target_id = $${i++}`); vals.push(req.targetId); }
|
|
946
|
+
if (req.enabled !== undefined) { sets.push(`enabled = $${i++}`); vals.push(req.enabled); }
|
|
947
|
+
if (!sets.length) return getJobById(id);
|
|
948
|
+
sets.push(`updated_at = now()`);
|
|
949
|
+
vals.push(id);
|
|
950
|
+
const r = await getPool().query(
|
|
951
|
+
`UPDATE scheduled_jobs SET ${sets.join(', ')} WHERE id = $${i} RETURNING *`, vals
|
|
952
|
+
);
|
|
953
|
+
return r.rows.length ? rowToJob(r.rows[0]) : null;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Deletes a scheduled job and all its runs (CASCADE).
|
|
958
|
+
*
|
|
959
|
+
* @param {string} id - Job UUID.
|
|
960
|
+
*/
|
|
961
|
+
export async function deleteJob(id: string): Promise<void> {
|
|
962
|
+
await getPool().query('DELETE FROM scheduled_jobs WHERE id = $1', [id]);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Returns paginated run history for a job.
|
|
967
|
+
*
|
|
968
|
+
* @param {string} jobId - Job UUID.
|
|
969
|
+
* @param {number} limit - Max results.
|
|
970
|
+
* @param {number} offset - Offset.
|
|
971
|
+
* @returns {Promise<JobRun[]>}
|
|
972
|
+
*/
|
|
973
|
+
export async function getJobRuns(jobId: string, limit = 20, offset = 0): Promise<JobRun[]> {
|
|
974
|
+
const r = await getPool().query(
|
|
975
|
+
'SELECT * FROM job_runs WHERE job_id = $1 ORDER BY started_at DESC LIMIT $2 OFFSET $3',
|
|
976
|
+
[jobId, limit, offset]
|
|
977
|
+
);
|
|
978
|
+
return r.rows.map(rowToJobRun);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// =============================================================================
|
|
982
|
+
// Agent Snapshots — version control
|
|
983
|
+
// =============================================================================
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Maps a raw DB row to an {@link AgentSnapshot}.
|
|
987
|
+
*
|
|
988
|
+
* @param {Record<string, unknown>} row - Raw row from agent_snapshots.
|
|
989
|
+
* @returns {AgentSnapshot}
|
|
990
|
+
*/
|
|
991
|
+
function rowToSnapshot(row: Record<string, unknown>): AgentSnapshot {
|
|
992
|
+
return {
|
|
993
|
+
id: row.id as string,
|
|
994
|
+
agentId: row.agent_id as string,
|
|
995
|
+
label: row.label as string | undefined,
|
|
996
|
+
trigger: row.trigger as SnapshotTrigger,
|
|
997
|
+
createdBy: row.created_by as string,
|
|
998
|
+
skillsJson: (row.skills_json as SnapshotSkill[]) ?? [],
|
|
999
|
+
allowedTools: (row.allowed_tools as string[]) ?? [],
|
|
1000
|
+
deniedTools: (row.denied_tools as string[]) ?? [],
|
|
1001
|
+
mcpIds: (row.mcp_ids as string[]) ?? [],
|
|
1002
|
+
compiledMd: row.compiled_md as string,
|
|
1003
|
+
allowedChannels: (row.allowed_channels as string[]) ?? [],
|
|
1004
|
+
createdAt: row.created_at as Date,
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Creates a new snapshot for an agent.
|
|
1010
|
+
*
|
|
1011
|
+
* Auto-snapshots (trigger !== 'manual') are capped at 10 per agent —
|
|
1012
|
+
* the oldest ones beyond that cap are purged in the same transaction.
|
|
1013
|
+
* Manual snapshots are never auto-purged.
|
|
1014
|
+
*
|
|
1015
|
+
* @param {string} agentId - Agent UUID.
|
|
1016
|
+
* @param {SnapshotTrigger} trigger - What caused this snapshot.
|
|
1017
|
+
* @param {string} createdBy - Username of the person who triggered the change.
|
|
1018
|
+
* @param {string | null} label - Optional label (used for manual snapshots).
|
|
1019
|
+
* @param {SnapshotSkill[]} skills - Skills array at snapshot time.
|
|
1020
|
+
* @param {string[]} allowedTools - Allowed tools at snapshot time.
|
|
1021
|
+
* @param {string[]} deniedTools - Denied tools at snapshot time.
|
|
1022
|
+
* @param {string[]} mcpIds - MCP server UUIDs at snapshot time.
|
|
1023
|
+
* @param {string} compiledMd - Skills-only compiled CLAUDE.md.
|
|
1024
|
+
* @returns {Promise<AgentSnapshot>} The created snapshot.
|
|
1025
|
+
*/
|
|
1026
|
+
export async function createSnapshot(
|
|
1027
|
+
agentId: string,
|
|
1028
|
+
trigger: SnapshotTrigger,
|
|
1029
|
+
createdBy: string,
|
|
1030
|
+
label: string | null,
|
|
1031
|
+
skills: SnapshotSkill[],
|
|
1032
|
+
allowedTools: string[],
|
|
1033
|
+
deniedTools: string[],
|
|
1034
|
+
mcpIds: string[],
|
|
1035
|
+
compiledMd: string,
|
|
1036
|
+
allowedChannels: string[] = [],
|
|
1037
|
+
): Promise<AgentSnapshot> {
|
|
1038
|
+
const client = await getPool().connect();
|
|
1039
|
+
try {
|
|
1040
|
+
await client.query('BEGIN');
|
|
1041
|
+
|
|
1042
|
+
const insertResult = await client.query(
|
|
1043
|
+
`INSERT INTO agent_snapshots
|
|
1044
|
+
(agent_id, label, trigger, created_by, skills_json,
|
|
1045
|
+
allowed_tools, denied_tools, mcp_ids, compiled_md, allowed_channels)
|
|
1046
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
|
|
1047
|
+
RETURNING *`,
|
|
1048
|
+
[agentId, label, trigger, createdBy, JSON.stringify(skills),
|
|
1049
|
+
allowedTools, deniedTools, mcpIds, compiledMd, allowedChannels]
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
// Purge oldest auto-snapshots beyond cap=10 for this agent
|
|
1053
|
+
await client.query(
|
|
1054
|
+
`DELETE FROM agent_snapshots
|
|
1055
|
+
WHERE id IN (
|
|
1056
|
+
SELECT id FROM agent_snapshots
|
|
1057
|
+
WHERE agent_id = $1 AND trigger != 'manual'
|
|
1058
|
+
ORDER BY created_at DESC
|
|
1059
|
+
OFFSET 10
|
|
1060
|
+
)`,
|
|
1061
|
+
[agentId]
|
|
1062
|
+
);
|
|
1063
|
+
|
|
1064
|
+
await client.query('COMMIT');
|
|
1065
|
+
return rowToSnapshot(insertResult.rows[0]);
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
await client.query('ROLLBACK');
|
|
1068
|
+
throw err;
|
|
1069
|
+
} finally {
|
|
1070
|
+
client.release();
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Returns snapshots for an agent, newest first.
|
|
1076
|
+
*
|
|
1077
|
+
* @param {string} agentId - Agent UUID.
|
|
1078
|
+
* @param {number} limit - Max results (default 100).
|
|
1079
|
+
* @param {number} offset - Offset for pagination (default 0).
|
|
1080
|
+
* @returns {Promise<AgentSnapshot[]>}
|
|
1081
|
+
*/
|
|
1082
|
+
export async function listSnapshots(agentId: string, limit = 100, offset = 0): Promise<AgentSnapshot[]> {
|
|
1083
|
+
const r = await getPool().query(
|
|
1084
|
+
`SELECT id, agent_id, trigger, created_by, label, allowed_tools, denied_tools, mcp_ids, allowed_channels, created_at
|
|
1085
|
+
FROM agent_snapshots WHERE agent_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3`,
|
|
1086
|
+
[agentId, limit, offset]
|
|
1087
|
+
);
|
|
1088
|
+
return r.rows.map(row => rowToSnapshot({ ...row, skills_json: [], compiled_md: '' }));
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Returns a single snapshot by UUID.
|
|
1093
|
+
*
|
|
1094
|
+
* @param {string} id - Snapshot UUID.
|
|
1095
|
+
* @returns {Promise<AgentSnapshot | null>}
|
|
1096
|
+
*/
|
|
1097
|
+
export async function getSnapshotById(id: string): Promise<AgentSnapshot | null> {
|
|
1098
|
+
const r = await getPool().query('SELECT * FROM agent_snapshots WHERE id = $1', [id]);
|
|
1099
|
+
return r.rows.length ? rowToSnapshot(r.rows[0]) : null;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Deletes a snapshot by UUID.
|
|
1104
|
+
*
|
|
1105
|
+
* @param {string} id - Snapshot UUID.
|
|
1106
|
+
* @returns {Promise<void>}
|
|
1107
|
+
*/
|
|
1108
|
+
export async function deleteSnapshot(id: string): Promise<void> {
|
|
1109
|
+
await getPool().query('DELETE FROM agent_snapshots WHERE id = $1', [id]);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// =============================================================================
|
|
1113
|
+
// Env Vars
|
|
1114
|
+
// Platform-level secret store. Values are encrypted at rest using pgcrypto
|
|
1115
|
+
// symmetric encryption with ENV_SECRET_KEY from the environment.
|
|
1116
|
+
// Values are write-only — never returned by the web API.
|
|
1117
|
+
// The runner decrypts values at agent start time using the same key.
|
|
1118
|
+
// =============================================================================
|
|
1119
|
+
|
|
1120
|
+
/** The symmetric encryption key for env var values. Must be set in .env. */
|
|
1121
|
+
function getEnvSecretKey(): string {
|
|
1122
|
+
const key = process.env.ENV_SECRET_KEY;
|
|
1123
|
+
if (!key) throw new Error('ENV_SECRET_KEY is not set — required for env var encryption');
|
|
1124
|
+
return key;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Returns decrypted env var values keyed by name. For internal use only — never expose via API.
|
|
1129
|
+
*/
|
|
1130
|
+
export async function getEnvVarValues(): Promise<Record<string, string>> {
|
|
1131
|
+
const encKey = getEnvSecretKey();
|
|
1132
|
+
const r = await getPool().query(
|
|
1133
|
+
'SELECT key, pgp_sym_decrypt(value::bytea, $1::text)::text AS value FROM env_vars',
|
|
1134
|
+
[encKey],
|
|
1135
|
+
);
|
|
1136
|
+
return Object.fromEntries(r.rows.map((row: { key: string; value: string }) => [row.key, row.value]));
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Returns all env var keys + metadata. Values are never included.
|
|
1141
|
+
*
|
|
1142
|
+
* @returns {Promise<Array<{ key: string; description?: string; updatedAt: Date }>>}
|
|
1143
|
+
*/
|
|
1144
|
+
export async function getAllEnvVars(): Promise<Array<{ key: string; description?: string; updatedAt: Date }>> {
|
|
1145
|
+
const r = await getPool().query('SELECT key, description, updated_at FROM env_vars ORDER BY key');
|
|
1146
|
+
return r.rows.map(row => ({
|
|
1147
|
+
key: row.key,
|
|
1148
|
+
description: row.description ?? undefined,
|
|
1149
|
+
updatedAt: row.updated_at as Date,
|
|
1150
|
+
}));
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Upserts an env var. Value is encrypted using pgcrypto before storage.
|
|
1155
|
+
*
|
|
1156
|
+
* @param {string} key - Env var key (e.g. "REDSHIFT_DATABASE_URL").
|
|
1157
|
+
* @param {string} value - Secret value (encrypted before storage).
|
|
1158
|
+
* @param {string} [description] - Optional human-readable description.
|
|
1159
|
+
* @returns {Promise<void>}
|
|
1160
|
+
*/
|
|
1161
|
+
export async function setEnvVar(key: string, value: string, description?: string): Promise<void> {
|
|
1162
|
+
const encKey = getEnvSecretKey();
|
|
1163
|
+
await getPool().query(
|
|
1164
|
+
`INSERT INTO env_vars (key, value, description)
|
|
1165
|
+
VALUES ($1, pgp_sym_encrypt($2, $3, 'cipher-algo=aes256'), $4)
|
|
1166
|
+
ON CONFLICT (key) DO UPDATE SET
|
|
1167
|
+
value = pgp_sym_encrypt(EXCLUDED.value, $3, 'cipher-algo=aes256'),
|
|
1168
|
+
description = COALESCE(EXCLUDED.description, env_vars.description),
|
|
1169
|
+
updated_at = now()`,
|
|
1170
|
+
[key, value, encKey, description ?? null],
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Updates only the description of an existing env var (value unchanged).
|
|
1176
|
+
*
|
|
1177
|
+
* @param {string} key - Env var key.
|
|
1178
|
+
* @param {string} description - New description.
|
|
1179
|
+
* @returns {Promise<void>}
|
|
1180
|
+
*/
|
|
1181
|
+
export async function updateEnvVarDescription(key: string, description: string): Promise<void> {
|
|
1182
|
+
await getPool().query(
|
|
1183
|
+
'UPDATE env_vars SET description = $2, updated_at = now() WHERE key = $1',
|
|
1184
|
+
[key, description],
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Deletes an env var by key.
|
|
1190
|
+
*
|
|
1191
|
+
* @param {string} key - Env var key to delete.
|
|
1192
|
+
* @returns {Promise<void>}
|
|
1193
|
+
*/
|
|
1194
|
+
export async function deleteEnvVar(key: string): Promise<void> {
|
|
1195
|
+
await getPool().query('DELETE FROM env_vars WHERE key = $1', [key]);
|
|
1196
|
+
}
|