@rudderhq/server 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runtimes/registry.d.ts.map +1 -1
- package/dist/agent-runtimes/registry.js +0 -2
- package/dist/agent-runtimes/registry.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -5
- package/dist/app.js.map +1 -1
- package/dist/bootstrap/create-http-app.d.ts +1 -2
- package/dist/bootstrap/create-http-app.d.ts.map +1 -1
- package/dist/bootstrap/create-http-app.js +13 -12
- package/dist/bootstrap/create-http-app.js.map +1 -1
- package/dist/bootstrap/register-api-routes.d.ts +1 -2
- package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +22 -4
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/logger.js +2 -2
- package/dist/middleware/logger.js.map +1 -1
- package/dist/middleware/logger.test.js +26 -10
- package/dist/middleware/logger.test.js.map +1 -1
- package/dist/redaction.d.ts +4 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +15 -0
- package/dist/redaction.js.map +1 -1
- package/dist/routes/agent-issue-creation.d.ts +25 -0
- package/dist/routes/agent-issue-creation.d.ts.map +1 -0
- package/dist/routes/agent-issue-creation.js +201 -0
- package/dist/routes/agent-issue-creation.js.map +1 -0
- package/dist/routes/app-builder.d.ts +3 -1
- package/dist/routes/app-builder.d.ts.map +1 -1
- package/dist/routes/app-builder.js +30 -22
- package/dist/routes/app-builder.js.map +1 -1
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +36 -9
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +23 -4
- package/dist/routes/chats.js.map +1 -1
- package/dist/routes/chats.preflight.d.ts +2 -1
- package/dist/routes/chats.preflight.d.ts.map +1 -1
- package/dist/routes/chats.preflight.js +7 -0
- package/dist/routes/chats.preflight.js.map +1 -1
- package/dist/routes/chats.stream-routes.d.ts.map +1 -1
- package/dist/routes/chats.stream-routes.js +14 -3
- package/dist/routes/chats.stream-routes.js.map +1 -1
- package/dist/routes/chats.stream-support.d.ts.map +1 -1
- package/dist/routes/chats.stream-support.js +8 -0
- package/dist/routes/chats.stream-support.js.map +1 -1
- package/dist/routes/computer.d.ts +30 -0
- package/dist/routes/computer.d.ts.map +1 -0
- package/dist/routes/computer.js +231 -0
- package/dist/routes/computer.js.map +1 -0
- package/dist/routes/goals.d.ts.map +1 -1
- package/dist/routes/goals.js +176 -27
- package/dist/routes/goals.js.map +1 -1
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +2 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/instance-settings.d.ts.map +1 -1
- package/dist/routes/instance-settings.js +3 -0
- package/dist/routes/instance-settings.js.map +1 -1
- package/dist/routes/issues.comments-attachments.d.ts.map +1 -1
- package/dist/routes/issues.comments-attachments.js +90 -8
- package/dist/routes/issues.comments-attachments.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +14 -2
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/issues.mutations.d.ts.map +1 -1
- package/dist/routes/issues.mutations.js +206 -52
- package/dist/routes/issues.mutations.js.map +1 -1
- package/dist/routes/managed-mcp-runtime.d.ts.map +1 -1
- package/dist/routes/managed-mcp-runtime.js +109 -7
- package/dist/routes/managed-mcp-runtime.js.map +1 -1
- package/dist/routes/organization-skills.d.ts.map +1 -1
- package/dist/routes/organization-skills.js +23 -1
- package/dist/routes/organization-skills.js.map +1 -1
- package/dist/routes/requests.d.ts +3 -0
- package/dist/routes/requests.d.ts.map +1 -0
- package/dist/routes/requests.js +122 -0
- package/dist/routes/requests.js.map +1 -0
- package/dist/routes/rudder-plugins.d.ts +4 -0
- package/dist/routes/rudder-plugins.d.ts.map +1 -0
- package/dist/routes/rudder-plugins.js +203 -0
- package/dist/routes/rudder-plugins.js.map +1 -0
- package/dist/services/activity-log.d.ts +0 -3
- package/dist/services/activity-log.d.ts.map +1 -1
- package/dist/services/activity-log.js +19 -42
- package/dist/services/activity-log.js.map +1 -1
- package/dist/services/activity.d.ts +3 -3
- package/dist/services/agent-issue-creation.d.ts +344 -0
- package/dist/services/agent-issue-creation.d.ts.map +1 -0
- package/dist/services/agent-issue-creation.js +675 -0
- package/dist/services/agent-issue-creation.js.map +1 -0
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +10 -4
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/agents.d.ts +14 -14
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/agents.js +2 -1
- package/dist/services/agents.js.map +1 -1
- package/dist/services/app-builder.d.ts.map +1 -1
- package/dist/services/app-builder.js +24 -3
- package/dist/services/app-builder.js.map +1 -1
- package/dist/services/chat-agent-runs.d.ts +1 -0
- package/dist/services/chat-agent-runs.d.ts.map +1 -1
- package/dist/services/chat-agent-runs.js +1 -0
- package/dist/services/chat-agent-runs.js.map +1 -1
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.d.ts +3 -1
- package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.js +25 -0
- package/dist/services/chat-assistant.helpers.js.map +1 -1
- package/dist/services/chat-assistant.js +57 -11
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chat-generation-protocol.test.js +2 -2
- package/dist/services/chat-generation-protocol.test.js.map +1 -1
- package/dist/services/chat-queued-message-materialization.d.ts +1 -1
- package/dist/services/chats.create.d.ts +2 -2
- package/dist/services/chats.create.d.ts.map +1 -1
- package/dist/services/chats.d.ts +15 -15
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.helpers.d.ts +2 -2
- package/dist/services/chats.helpers.d.ts.map +1 -1
- package/dist/services/chats.helpers.goal-context.test.d.ts +2 -0
- package/dist/services/chats.helpers.goal-context.test.d.ts.map +1 -0
- package/dist/services/chats.helpers.goal-context.test.js +40 -0
- package/dist/services/chats.helpers.goal-context.test.js.map +1 -0
- package/dist/services/chats.helpers.js +27 -2
- package/dist/services/chats.helpers.js.map +1 -1
- package/dist/services/chats.js.map +1 -1
- package/dist/services/computer-broker.d.ts +35 -0
- package/dist/services/computer-broker.d.ts.map +1 -0
- package/dist/services/computer-broker.js +211 -0
- package/dist/services/computer-broker.js.map +1 -0
- package/dist/services/computer-broker.test.d.ts +2 -0
- package/dist/services/computer-broker.test.d.ts.map +1 -0
- package/dist/services/computer-broker.test.js +70 -0
- package/dist/services/computer-broker.test.js.map +1 -0
- package/dist/services/computer-capability.d.ts +13 -0
- package/dist/services/computer-capability.d.ts.map +1 -0
- package/dist/services/computer-capability.js +11 -0
- package/dist/services/computer-capability.js.map +1 -0
- package/dist/services/computer-capability.test.d.ts +2 -0
- package/dist/services/computer-capability.test.d.ts.map +1 -0
- package/dist/services/computer-capability.test.js +27 -0
- package/dist/services/computer-capability.test.js.map +1 -0
- package/dist/services/finance.d.ts +2 -2
- package/dist/services/goals.d.ts +267 -244
- package/dist/services/goals.d.ts.map +1 -1
- package/dist/services/goals.js +1279 -297
- package/dist/services/goals.js.map +1 -1
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +8 -3
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/integrations/agent-integrations.d.ts +1 -1
- package/dist/services/issues.d.ts +86 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.helpers.d.ts +4 -0
- package/dist/services/issues.helpers.d.ts.map +1 -1
- package/dist/services/issues.helpers.js +28 -6
- package/dist/services/issues.helpers.js.map +1 -1
- package/dist/services/issues.js +207 -127
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.files.d.ts +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts +7 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.js +10 -0
- package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.d.ts +3 -1
- package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.js +80 -12
- package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
- package/dist/services/mcp/managed-bindings.d.ts.map +1 -1
- package/dist/services/mcp/managed-bindings.js +18 -2
- package/dist/services/mcp/managed-bindings.js.map +1 -1
- package/dist/services/mcp/managed-client.d.ts +15 -0
- package/dist/services/mcp/managed-client.d.ts.map +1 -1
- package/dist/services/mcp/managed-client.js +23 -1
- package/dist/services/mcp/managed-client.js.map +1 -1
- package/dist/services/mcp/managed-client.test.js +73 -9
- package/dist/services/mcp/managed-client.test.js.map +1 -1
- package/dist/services/messenger.d.ts +3 -3
- package/dist/services/messenger.d.ts.map +1 -1
- package/dist/services/messenger.js +201 -65
- package/dist/services/messenger.js.map +1 -1
- package/dist/services/orgs.d.ts.map +1 -1
- package/dist/services/orgs.js +3 -1
- package/dist/services/orgs.js.map +1 -1
- package/dist/services/product-analytics.d.ts +2 -2
- package/dist/services/requests.d.ts +919 -0
- package/dist/services/requests.d.ts.map +1 -0
- package/dist/services/requests.js +532 -0
- package/dist/services/requests.js.map +1 -0
- package/dist/services/rudder-plugins.d.ts +107 -0
- package/dist/services/rudder-plugins.d.ts.map +1 -0
- package/dist/services/rudder-plugins.js +1760 -0
- package/dist/services/rudder-plugins.js.map +1 -0
- package/dist/services/rudder-plugins.test.d.ts +2 -0
- package/dist/services/rudder-plugins.test.d.ts.map +1 -0
- package/dist/services/rudder-plugins.test.js +191 -0
- package/dist/services/rudder-plugins.test.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.core.d.ts +1 -0
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts +65 -0
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.execute.js +21 -5
- package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.js +320 -108
- package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.recovery.d.ts +1 -0
- package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.recovery.js +39 -4
- package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.js +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +14 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.js +46 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts +1 -0
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.wakeup.js +124 -6
- package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
- package/dist/services/workspace-runtime.services.d.ts +1 -1
- package/package.json +18 -18
- package/resources/bundled-skills/app-builder/SKILL.md +39 -9
- package/resources/bundled-skills/app-builder/assets/scaffold/app/globals.css +109 -16
- package/resources/bundled-skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +176 -152
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/alert.tsx +12 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/badge.tsx +27 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/button.tsx +9 -7
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/card.tsx +15 -3
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/empty.tsx +18 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/field.tsx +23 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/input.tsx +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/label.tsx +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/separator.tsx +6 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/skeleton.tsx +6 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components/ui/table.tsx +30 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/components.json +20 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/next-env.d.ts +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/next.config.ts +19 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/package.json +2 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/rudder.app.json +1 -1
- package/resources/bundled-skills/app-builder/assets/scaffold/rudder.ui.json +13 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/scripts/validate-rudder-ui.mjs +50 -0
- package/resources/bundled-skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +35 -1
- package/resources/bundled-skills/app-builder/evals/evals.json +2 -1
- package/resources/bundled-skills/app-builder/references/design-guidelines.md +19 -2
- package/resources/bundled-skills/app-builder/references/rudder-ui-preset.md +73 -0
- package/resources/bundled-skills/app-builder/references/scaffold-contract.md +12 -0
- package/resources/bundled-skills/app-builder/references/verification.md +6 -0
- package/resources/bundled-skills/app-builder/scripts/report-build-status.mjs +83 -0
- package/resources/bundled-skills/rudder-docs/SKILL.md +2 -2
- package/resources/bundled-skills/rudder-docs/evals/retrieval-authority-evals.json +40 -0
- package/resources/bundled-skills/rudder-docs/evals/trigger-evals.json +2 -2
- package/resources/bundled-skills/rudder-docs/references/api-reference.md +0 -2
- package/resources/bundled-skills/rudder-docs/references/cli-reference.md +7 -2
- package/resources/bundled-skills/rudder-docs/references/operating-practices.md +10 -6
- package/resources/bundled-skills/rudder-docs/references/plugin-authoring.md +38 -114
- package/resources/bundled-skills/rudder-docs/references/source-map.md +2 -0
- package/skills/app-builder/SKILL.md +39 -9
- package/skills/app-builder/assets/scaffold/app/globals.css +109 -16
- package/skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +176 -152
- package/skills/app-builder/assets/scaffold/components/ui/alert.tsx +12 -0
- package/skills/app-builder/assets/scaffold/components/ui/badge.tsx +27 -0
- package/skills/app-builder/assets/scaffold/components/ui/button.tsx +9 -7
- package/skills/app-builder/assets/scaffold/components/ui/card.tsx +15 -3
- package/skills/app-builder/assets/scaffold/components/ui/empty.tsx +18 -0
- package/skills/app-builder/assets/scaffold/components/ui/field.tsx +23 -0
- package/skills/app-builder/assets/scaffold/components/ui/input.tsx +1 -1
- package/skills/app-builder/assets/scaffold/components/ui/label.tsx +1 -1
- package/skills/app-builder/assets/scaffold/components/ui/separator.tsx +6 -0
- package/skills/app-builder/assets/scaffold/components/ui/skeleton.tsx +6 -0
- package/skills/app-builder/assets/scaffold/components/ui/table.tsx +30 -0
- package/skills/app-builder/assets/scaffold/components.json +20 -0
- package/skills/app-builder/assets/scaffold/next-env.d.ts +1 -1
- package/skills/app-builder/assets/scaffold/next.config.ts +19 -0
- package/skills/app-builder/assets/scaffold/package.json +2 -1
- package/skills/app-builder/assets/scaffold/rudder.app.json +1 -1
- package/skills/app-builder/assets/scaffold/rudder.ui.json +13 -0
- package/skills/app-builder/assets/scaffold/scripts/validate-rudder-ui.mjs +50 -0
- package/skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +35 -1
- package/skills/app-builder/evals/evals.json +2 -1
- package/skills/app-builder/references/design-guidelines.md +19 -2
- package/skills/app-builder/references/rudder-ui-preset.md +73 -0
- package/skills/app-builder/references/scaffold-contract.md +12 -0
- package/skills/app-builder/references/verification.md +6 -0
- package/skills/app-builder/scripts/report-build-status.mjs +83 -0
- package/skills/rudder-docs/SKILL.md +2 -2
- package/skills/rudder-docs/evals/retrieval-authority-evals.json +40 -0
- package/skills/rudder-docs/evals/trigger-evals.json +2 -2
- package/skills/rudder-docs/references/api-reference.md +0 -2
- package/skills/rudder-docs/references/cli-reference.md +7 -2
- package/skills/rudder-docs/references/operating-practices.md +10 -6
- package/skills/rudder-docs/references/plugin-authoring.md +38 -114
- package/skills/rudder-docs/references/source-map.md +2 -0
- package/ui-dist/assets/{LibraryLiveSurface-cHCJmmsl.js → LibraryLiveSurface-D4uCo0l0.js} +1 -1
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BORJvkew.js → abnfDiagram-VRR7QNED-FF2qcR6A.js} +1 -1
- package/ui-dist/assets/addon-fit-4gL7JXzc.js +1 -0
- package/ui-dist/assets/{arc-DPUzhx87.js → arc-D5pLjh6B.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-Bz_hY8LM.js → architectureDiagram-ZJ3FMSHR-BvqY-HXg.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-Bq1X3YkP.js → blockDiagram-677ZJIJ3-BU6ZbJ0s.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-BawEM9Pc.js → c4Diagram-LMCZKHZV-Cl5Gsovj.js} +1 -1
- package/ui-dist/assets/channel-HcmprA3I.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-D0D5ZOaA.js → chunk-2Q5K7J3B-4a2agt_p.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-T90P7lug.js → chunk-32BRIVSS-CF0oOO2T.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-jcAERTgh.js → chunk-5VM5RSS4-CsSq2zX2.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-t9-JXwnR.js → chunk-EX3LRPZG-B6riETVZ.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-hqgD1saC.js → chunk-JWPE2WC7-S-p1xu0r.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-BsSUoYzd.js → chunk-MOJQB5TN-mVE4v_LV.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-YcdDh2Zn.js → chunk-RYQCIY6F-CElbgpgs.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-ChFyH9Su.js → chunk-V7JOEXUC-8XM3povP.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-BjVK8IyX.js → chunk-VR4S4FIN-LrThp3ht.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-D-TvO9vT.js → chunk-XXDRQBXY-D38vLb4t.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-Dpe8LaLf.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-Dpe8LaLf.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-DBwhASJG.js → cose-bilkent-JH36ORCC-CmpZMaDV.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-SlCRmwl2.js → cynefin-VYW2F7L2-D3RnbBvI.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-DImV6N_p.js → cynefinDiagram-TSTJHNR4-DZTcUojp.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-FH4Wv8Zn.js → dagre-VKFMJZFB-6jXSDi0S.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-CrZvQjId.js → diagram-FQU43EPY-DIMm3mfS.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-B7IGl_T3.js → diagram-G47NLZAW-Cyo22v0_.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-CB4i7H1a.js → diagram-NH7WQ7WH-CHDV0nSL.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-DNyWPYY9.js → diagram-OA4YK3LP-Jijx9vQO.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-BAhxbhpi.js → diagram-WEI45ONY-CcL19HLE.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-ClXtQ9qt.js → ebnfDiagram-CCIWWBDH-D94h-RFQ.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-DGXiMYfn.js → erDiagram-Q63AITRT-B3ypUGZX.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-CdZ-02zc.js → flowDiagram-23GEKE2U-DjCT6Y1o.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-TxhzNpta.js → ganttDiagram-NO4QXBWP-C3QVhTc9.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-D6Q_w6wR.js → gitGraphDiagram-IHSO6WYX-D0trBzU5.js} +1 -1
- package/ui-dist/assets/{graph-CxNmbpl9.js → graph-D1zFmVUP.js} +1 -1
- package/ui-dist/assets/{index-Cx6oIZPm.js → index-B0GEsWui.js} +568 -593
- package/ui-dist/assets/{index-BA9tMBhO.js → index-B8bxQLfN.js} +1 -1
- package/ui-dist/assets/{index-C_nwI4Yj.js → index-BFktZ2tu.js} +1 -1
- package/ui-dist/assets/index-BT7dphT4.css +32 -0
- package/ui-dist/assets/{index-BoTz5S-X.js → index-Bk_yesiO.js} +1 -1
- package/ui-dist/assets/{index-CXnqg1Oq.js → index-BwutQLG1.js} +1 -1
- package/ui-dist/assets/{index-AkhgKOxC.js → index-CB4zr_Ku.js} +1 -1
- package/ui-dist/assets/{index-Rau2cGm0.js → index-CC89UPa-.js} +1 -1
- package/ui-dist/assets/{index-kbWUQ1um.js → index-CFWOO6yn.js} +1 -1
- package/ui-dist/assets/{index-DU2LI7f9.js → index-CK8u9b07.js} +1 -1
- package/ui-dist/assets/{index-C12QC1Ex.js → index-CRolpP90.js} +1 -1
- package/ui-dist/assets/{index-Bxz75ICF.js → index-CiCRRUVV.js} +1 -1
- package/ui-dist/assets/{index-Do78GymY.js → index-D8JG-IU9.js} +1 -1
- package/ui-dist/assets/{index-CW21qWFm.js → index-DKCiVvJo.js} +1 -1
- package/ui-dist/assets/{index-BujwWE3j.js → index-G2yfKEf_.js} +1 -1
- package/ui-dist/assets/{index-BQ87dGIq.js → index-or2C22bw.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-CnJd4-Jq.js → infoDiagram-FWYZ7A6U-Co0nkcV4.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-Bq-qc3pD.js → ishikawaDiagram-FXEZZL3T-CkIg1Jdu.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-NSmpUGUA.js → journeyDiagram-5HDEW3XC-AYUT6JuJ.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-D7pgWtfA.js → kanban-definition-HUTT4EX6-C3H2XW08.js} +1 -1
- package/ui-dist/assets/{layout-BRuo6LWe.js → layout-sZWyqNOu.js} +1 -1
- package/ui-dist/assets/{linear-CkaWI9Nd.js → linear-BLTYou8N.js} +1 -1
- package/ui-dist/assets/{map-CgA6tTpZ.js → map-zKBdHkX8.js} +1 -1
- package/ui-dist/assets/{mermaid.core-DrXOt-P6.js → mermaid.core-DbjdFip8.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-D9ImeWBe.js → mindmap-definition-LN4V7U3C-DcDNQ4GQ.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-Cq-zZ_zz.js → pegDiagram-2B236MQR-BGq1PngU.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-FgNgy726.js → pieDiagram-ENE6RG2P-BfXWRzNb.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-BxQbmGmi.js → quadrantDiagram-ABIIQ3AL-AYMVAbo1.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-CrpbkPH2.js → railroadDiagram-RFXS5EU6-B0PoG0Ih.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-DQ-65ZW6.js → requirementDiagram-TGXJPOKE-Cuuu9wfw.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-Bom0lMZm.js → sankeyDiagram-HTMAVEWB-BceAc42f.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-C1MSDLDj.js → sequenceDiagram-DBY2YBRQ-cqJ71-Rw.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-Bn1vxebD.js → sizeCapture-X5ZJPWSS-DQ6MibbA.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-GRRQpmzI.js → stateDiagram-2N3HPSRC-Bdl-XY7t.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-Cjj7yPIO.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-BcOOCZ4q.js → swimlanes-5IMT3BWC-Dpo2HDtE.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CPmIkNCu.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-gi6amswh.js → timeline-definition-FHXFAJF6-BzmJ-xnC.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-B9uqeyQt.js → vennDiagram-L72KCM5P-BF2EI7vn.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-BnqWAMiE.js → wardleyDiagram-EHGQE667-DM65x1i1.js} +1 -1
- package/ui-dist/assets/xterm-DZP7bLZ5.js +9 -0
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-BJXKltIN.js → xychartDiagram-FW5EYKEG-3Q2t9pdU.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/dist/bootstrap/plugin-host-runtime.d.ts +0 -116
- package/dist/bootstrap/plugin-host-runtime.d.ts.map +0 -1
- package/dist/bootstrap/plugin-host-runtime.js +0 -179
- package/dist/bootstrap/plugin-host-runtime.js.map +0 -1
- package/dist/bundled-plugins/plugin-linear/README.md +0 -22
- package/dist/bundled-plugins/plugin-linear/dist/manifest.js +0 -183
- package/dist/bundled-plugins/plugin-linear/dist/manifest.js.map +0 -7
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js +0 -1285
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js.map +0 -7
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +0 -10699
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +0 -7
- package/dist/bundled-plugins/plugin-linear/package.json +0 -42
- package/dist/routes/plugin-ui-static.d.ts +0 -70
- package/dist/routes/plugin-ui-static.d.ts.map +0 -1
- package/dist/routes/plugin-ui-static.js +0 -375
- package/dist/routes/plugin-ui-static.js.map +0 -1
- package/dist/routes/plugins.d.ts +0 -120
- package/dist/routes/plugins.d.ts.map +0 -1
- package/dist/routes/plugins.js +0 -1125
- package/dist/routes/plugins.js.map +0 -1
- package/dist/routes/plugins.operations-routes.d.ts +0 -28
- package/dist/routes/plugins.operations-routes.d.ts.map +0 -1
- package/dist/routes/plugins.operations-routes.js +0 -720
- package/dist/routes/plugins.operations-routes.js.map +0 -1
- package/dist/services/plugin-capability-validator.d.ts +0 -108
- package/dist/services/plugin-capability-validator.d.ts.map +0 -1
- package/dist/services/plugin-capability-validator.js +0 -268
- package/dist/services/plugin-capability-validator.js.map +0 -1
- package/dist/services/plugin-config-validator.d.ts +0 -26
- package/dist/services/plugin-config-validator.d.ts.map +0 -1
- package/dist/services/plugin-config-validator.js +0 -41
- package/dist/services/plugin-config-validator.js.map +0 -1
- package/dist/services/plugin-dev-watcher.d.ts +0 -47
- package/dist/services/plugin-dev-watcher.d.ts.map +0 -1
- package/dist/services/plugin-dev-watcher.js +0 -275
- package/dist/services/plugin-dev-watcher.js.map +0 -1
- package/dist/services/plugin-event-bus.d.ts +0 -149
- package/dist/services/plugin-event-bus.d.ts.map +0 -1
- package/dist/services/plugin-event-bus.js +0 -258
- package/dist/services/plugin-event-bus.js.map +0 -1
- package/dist/services/plugin-host-service-cleanup.d.ts +0 -14
- package/dist/services/plugin-host-service-cleanup.d.ts.map +0 -1
- package/dist/services/plugin-host-service-cleanup.js +0 -37
- package/dist/services/plugin-host-service-cleanup.js.map +0 -1
- package/dist/services/plugin-host-services.d.ts +0 -13
- package/dist/services/plugin-host-services.d.ts.map +0 -1
- package/dist/services/plugin-host-services.js +0 -933
- package/dist/services/plugin-host-services.js.map +0 -1
- package/dist/services/plugin-job-coordinator.d.ts +0 -81
- package/dist/services/plugin-job-coordinator.d.ts.map +0 -1
- package/dist/services/plugin-job-coordinator.js +0 -172
- package/dist/services/plugin-job-coordinator.js.map +0 -1
- package/dist/services/plugin-job-scheduler.d.ts +0 -163
- package/dist/services/plugin-job-scheduler.d.ts.map +0 -1
- package/dist/services/plugin-job-scheduler.js +0 -452
- package/dist/services/plugin-job-scheduler.js.map +0 -1
- package/dist/services/plugin-job-store.d.ts +0 -208
- package/dist/services/plugin-job-store.d.ts.map +0 -1
- package/dist/services/plugin-job-store.js +0 -350
- package/dist/services/plugin-job-store.js.map +0 -1
- package/dist/services/plugin-lifecycle.d.ts +0 -240
- package/dist/services/plugin-lifecycle.d.ts.map +0 -1
- package/dist/services/plugin-lifecycle.js +0 -439
- package/dist/services/plugin-lifecycle.js.map +0 -1
- package/dist/services/plugin-loader.core.d.ts +0 -40
- package/dist/services/plugin-loader.core.d.ts.map +0 -1
- package/dist/services/plugin-loader.core.js +0 -879
- package/dist/services/plugin-loader.core.js.map +0 -1
- package/dist/services/plugin-loader.d.ts +0 -30
- package/dist/services/plugin-loader.d.ts.map +0 -1
- package/dist/services/plugin-loader.helpers.d.ts +0 -495
- package/dist/services/plugin-loader.helpers.d.ts.map +0 -1
- package/dist/services/plugin-loader.helpers.js +0 -238
- package/dist/services/plugin-loader.helpers.js.map +0 -1
- package/dist/services/plugin-loader.js +0 -30
- package/dist/services/plugin-loader.js.map +0 -1
- package/dist/services/plugin-loader.worker-paths.d.ts +0 -37
- package/dist/services/plugin-loader.worker-paths.d.ts.map +0 -1
- package/dist/services/plugin-loader.worker-paths.js +0 -62
- package/dist/services/plugin-loader.worker-paths.js.map +0 -1
- package/dist/services/plugin-log-retention.d.ts +0 -20
- package/dist/services/plugin-log-retention.d.ts.map +0 -1
- package/dist/services/plugin-log-retention.js +0 -63
- package/dist/services/plugin-log-retention.js.map +0 -1
- package/dist/services/plugin-manifest-validator.d.ts +0 -90
- package/dist/services/plugin-manifest-validator.d.ts.map +0 -1
- package/dist/services/plugin-manifest-validator.js +0 -72
- package/dist/services/plugin-manifest-validator.js.map +0 -1
- package/dist/services/plugin-registry.d.ts +0 -2542
- package/dist/services/plugin-registry.d.ts.map +0 -1
- package/dist/services/plugin-registry.js +0 -536
- package/dist/services/plugin-registry.js.map +0 -1
- package/dist/services/plugin-runtime-sandbox.d.ts +0 -40
- package/dist/services/plugin-runtime-sandbox.d.ts.map +0 -1
- package/dist/services/plugin-runtime-sandbox.js +0 -154
- package/dist/services/plugin-runtime-sandbox.js.map +0 -1
- package/dist/services/plugin-secrets-handler.d.ts +0 -81
- package/dist/services/plugin-secrets-handler.d.ts.map +0 -1
- package/dist/services/plugin-secrets-handler.js +0 -275
- package/dist/services/plugin-secrets-handler.js.map +0 -1
- package/dist/services/plugin-state-store.d.ts +0 -92
- package/dist/services/plugin-state-store.d.ts.map +0 -1
- package/dist/services/plugin-state-store.js +0 -190
- package/dist/services/plugin-state-store.js.map +0 -1
- package/dist/services/plugin-stream-bus.d.ts +0 -29
- package/dist/services/plugin-stream-bus.d.ts.map +0 -1
- package/dist/services/plugin-stream-bus.js +0 -48
- package/dist/services/plugin-stream-bus.js.map +0 -1
- package/dist/services/plugin-tool-dispatcher.d.ts +0 -180
- package/dist/services/plugin-tool-dispatcher.d.ts.map +0 -1
- package/dist/services/plugin-tool-dispatcher.js +0 -224
- package/dist/services/plugin-tool-dispatcher.js.map +0 -1
- package/dist/services/plugin-tool-registry.d.ts +0 -192
- package/dist/services/plugin-tool-registry.d.ts.map +0 -1
- package/dist/services/plugin-tool-registry.js +0 -224
- package/dist/services/plugin-tool-registry.js.map +0 -1
- package/dist/services/plugin-worker-manager.d.ts +0 -260
- package/dist/services/plugin-worker-manager.d.ts.map +0 -1
- package/dist/services/plugin-worker-manager.js +0 -836
- package/dist/services/plugin-worker-manager.js.map +0 -1
- package/ui-dist/assets/channel-C08AyYJr.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-BIXq6fec.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-BIXq6fec.js +0 -1
- package/ui-dist/assets/index-BmXb0yC1.css +0 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KvSJhiLM.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-Cr92NnJ4.js +0 -8
|
@@ -0,0 +1,1760 @@
|
|
|
1
|
+
import { agentEnabledSkills, agents, appBuilderApps, installedPlugins, mcpConnections as mcpConnectionRows, organizationSkills, pluginComponentLinks, pluginImportReports, pluginPackages, pluginSources, } from "@rudderhq/db";
|
|
2
|
+
import { and, asc, eq, inArray, ne } from "drizzle-orm";
|
|
3
|
+
import { Unzip, UnzipInflate, UnzipPassThrough } from "fflate";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { conflict, notFound, unprocessable } from "../errors.js";
|
|
7
|
+
import { agentEnabledSkillsService } from "./agent-enabled-skills.js";
|
|
8
|
+
import { managedMcpConnectionService } from "./mcp/managed-connections.js";
|
|
9
|
+
import { organizationSkillService } from "./organization-skills.js";
|
|
10
|
+
const MAX_PLUGIN_BYTES = 10 * 1024 * 1024;
|
|
11
|
+
const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
12
|
+
const MAX_ARCHIVE_RATIO = 100;
|
|
13
|
+
const MAX_MCP_UI_HTML_BYTES = 2 * 1024 * 1024;
|
|
14
|
+
const SENSITIVE_KEY = /(?:authorization|api[_-]?key|password|secret|token|cookie)/i;
|
|
15
|
+
function asRecord(value) {
|
|
16
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
17
|
+
? value
|
|
18
|
+
: {};
|
|
19
|
+
}
|
|
20
|
+
function asString(value) {
|
|
21
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
22
|
+
}
|
|
23
|
+
function safeName(value, fallback = "plugin") {
|
|
24
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
25
|
+
return (normalized || fallback).slice(0, 80);
|
|
26
|
+
}
|
|
27
|
+
function packageIdentityKey(sourceType, publisher, name) {
|
|
28
|
+
return `${sourceType}:${(publisher ?? "unknown").toLocaleLowerCase("en-US")}:${name}`;
|
|
29
|
+
}
|
|
30
|
+
function decodeInputFile(file) {
|
|
31
|
+
if (file.encoding !== "base64")
|
|
32
|
+
return Buffer.from(file.content, "utf8");
|
|
33
|
+
const compact = file.content.replace(/\s/g, "");
|
|
34
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(compact)) {
|
|
35
|
+
throw unprocessable(`Invalid base64 content for ${file.path}`);
|
|
36
|
+
}
|
|
37
|
+
return Buffer.from(compact, "base64");
|
|
38
|
+
}
|
|
39
|
+
function decodeBase64(value, label) {
|
|
40
|
+
const compact = value.replace(/\s/g, "");
|
|
41
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(compact)) {
|
|
42
|
+
throw unprocessable(`Invalid base64 content for ${label}`);
|
|
43
|
+
}
|
|
44
|
+
return Buffer.from(compact, "base64");
|
|
45
|
+
}
|
|
46
|
+
function unzipPackageFiles(content, label, stripPluginRoot) {
|
|
47
|
+
const archive = decodeBase64(content, label);
|
|
48
|
+
if (archive.byteLength > MAX_PLUGIN_BYTES)
|
|
49
|
+
throw unprocessable("Plugin archive exceeds the 10 MiB V1 limit");
|
|
50
|
+
const files = [];
|
|
51
|
+
let totalBytes = 0;
|
|
52
|
+
let failure = null;
|
|
53
|
+
const unzip = new Unzip((file) => {
|
|
54
|
+
if (failure || file.name.endsWith("/"))
|
|
55
|
+
return;
|
|
56
|
+
if (files.length >= 500) {
|
|
57
|
+
failure = new Error("Plugin archive exceeds the 500-file V1 limit");
|
|
58
|
+
file.terminate();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (file.originalSize !== undefined && file.originalSize > MAX_FILE_BYTES) {
|
|
62
|
+
failure = new Error(`Plugin archive entry exceeds 2 MiB: ${file.name}`);
|
|
63
|
+
file.terminate();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (file.size && file.originalSize && file.originalSize / file.size > MAX_ARCHIVE_RATIO) {
|
|
67
|
+
failure = new Error(`Plugin archive entry exceeds the ${MAX_ARCHIVE_RATIO}:1 expansion limit: ${file.name}`);
|
|
68
|
+
file.terminate();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const chunks = [];
|
|
72
|
+
let entryBytes = 0;
|
|
73
|
+
file.ondata = (error, data, final) => {
|
|
74
|
+
if (failure)
|
|
75
|
+
return;
|
|
76
|
+
if (error) {
|
|
77
|
+
failure = error;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
entryBytes += data.byteLength;
|
|
81
|
+
totalBytes += data.byteLength;
|
|
82
|
+
if (entryBytes > MAX_FILE_BYTES || totalBytes > MAX_PLUGIN_BYTES) {
|
|
83
|
+
failure = new Error(entryBytes > MAX_FILE_BYTES
|
|
84
|
+
? `Plugin archive entry exceeds 2 MiB: ${file.name}`
|
|
85
|
+
: "Plugin archive exceeds the 10 MiB V1 expansion limit");
|
|
86
|
+
file.terminate();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
chunks.push(Buffer.from(data));
|
|
90
|
+
if (final) {
|
|
91
|
+
files.push({ path: file.name, content: Buffer.concat(chunks).toString("base64"), encoding: "base64" });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
file.start();
|
|
95
|
+
});
|
|
96
|
+
unzip.register(UnzipInflate);
|
|
97
|
+
unzip.register(UnzipPassThrough);
|
|
98
|
+
try {
|
|
99
|
+
unzip.push(archive, true);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw unprocessable(`Invalid ZIP Plugin archive: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
}
|
|
104
|
+
const archiveFailure = failure;
|
|
105
|
+
if (archiveFailure)
|
|
106
|
+
throw unprocessable(archiveFailure.message);
|
|
107
|
+
if (files.length === 0)
|
|
108
|
+
throw unprocessable("Invalid ZIP Plugin archive: archive contains no files");
|
|
109
|
+
if (totalBytes > archive.byteLength * MAX_ARCHIVE_RATIO) {
|
|
110
|
+
throw unprocessable(`Plugin archive exceeds the ${MAX_ARCHIVE_RATIO}:1 expansion limit`);
|
|
111
|
+
}
|
|
112
|
+
if (!stripPluginRoot || files.some((file) => file.path === ".codex-plugin/plugin.json"))
|
|
113
|
+
return files;
|
|
114
|
+
const manifests = files.filter((file) => file.path.endsWith("/.codex-plugin/plugin.json"));
|
|
115
|
+
if (manifests.length !== 1)
|
|
116
|
+
return files;
|
|
117
|
+
const prefix = manifests[0].path.slice(0, -".codex-plugin/plugin.json".length);
|
|
118
|
+
if (!files.every((file) => file.path.startsWith(prefix)))
|
|
119
|
+
return files;
|
|
120
|
+
return files.map((file) => ({ ...file, path: file.path.slice(prefix.length) }));
|
|
121
|
+
}
|
|
122
|
+
function normalizeFiles(input) {
|
|
123
|
+
if (input.files.length > 500)
|
|
124
|
+
throw unprocessable("Plugin package exceeds the 500-file V1 limit");
|
|
125
|
+
const bytes = new Map();
|
|
126
|
+
const caseFolded = new Set();
|
|
127
|
+
let totalBytes = 0;
|
|
128
|
+
for (const inputFile of input.files) {
|
|
129
|
+
const slashPath = inputFile.path.replace(/\\/g, "/");
|
|
130
|
+
if (slashPath.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
131
|
+
throw unprocessable(`Unsafe Plugin file path: ${inputFile.path}`);
|
|
132
|
+
}
|
|
133
|
+
const normalized = path.posix.normalize(slashPath).replace(/^\.\//, "");
|
|
134
|
+
if (!normalized
|
|
135
|
+
|| normalized === "."
|
|
136
|
+
|| normalized.startsWith("../")
|
|
137
|
+
|| normalized.includes("/../")
|
|
138
|
+
|| path.posix.isAbsolute(normalized)
|
|
139
|
+
|| normalized.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
140
|
+
throw unprocessable(`Unsafe Plugin file path: ${inputFile.path}`);
|
|
141
|
+
}
|
|
142
|
+
const folded = normalized.toLocaleLowerCase("en-US");
|
|
143
|
+
if (caseFolded.has(folded))
|
|
144
|
+
throw unprocessable(`Plugin contains a duplicate or case-colliding path: ${normalized}`);
|
|
145
|
+
caseFolded.add(folded);
|
|
146
|
+
const content = decodeInputFile(inputFile);
|
|
147
|
+
if (content.byteLength > MAX_FILE_BYTES)
|
|
148
|
+
throw unprocessable(`Plugin file exceeds 2 MiB: ${normalized}`);
|
|
149
|
+
totalBytes += content.byteLength;
|
|
150
|
+
if (totalBytes > MAX_PLUGIN_BYTES)
|
|
151
|
+
throw unprocessable("Plugin package exceeds the 10 MiB V1 limit");
|
|
152
|
+
bytes.set(normalized, content);
|
|
153
|
+
}
|
|
154
|
+
const hash = createHash("sha256");
|
|
155
|
+
for (const [filePath, content] of [...bytes.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
156
|
+
hash.update(Buffer.from(filePath, "utf8"));
|
|
157
|
+
hash.update(Buffer.from([0]));
|
|
158
|
+
hash.update(content);
|
|
159
|
+
hash.update(Buffer.from([0]));
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
bytes,
|
|
163
|
+
totalBytes,
|
|
164
|
+
digest: hash.digest("hex"),
|
|
165
|
+
files: [...bytes.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([filePath, content]) => ({
|
|
166
|
+
path: filePath,
|
|
167
|
+
content: content.toString("base64"),
|
|
168
|
+
encoding: "base64",
|
|
169
|
+
})),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function parseJsonFile(bytes, filePath) {
|
|
173
|
+
const content = bytes.get(filePath);
|
|
174
|
+
if (!content)
|
|
175
|
+
return null;
|
|
176
|
+
try {
|
|
177
|
+
const parsed = JSON.parse(content.toString("utf8"));
|
|
178
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
179
|
+
throw new Error("expected a JSON object");
|
|
180
|
+
}
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
throw unprocessable(`${filePath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function containsLiteralSecret(value, parentKey = "") {
|
|
188
|
+
if (typeof value === "string") {
|
|
189
|
+
if (!SENSITIVE_KEY.test(parentKey))
|
|
190
|
+
return false;
|
|
191
|
+
return !/^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(value.trim());
|
|
192
|
+
}
|
|
193
|
+
if (Array.isArray(value))
|
|
194
|
+
return value.some((entry) => containsLiteralSecret(entry, parentKey));
|
|
195
|
+
if (!value || typeof value !== "object")
|
|
196
|
+
return false;
|
|
197
|
+
return Object.entries(value)
|
|
198
|
+
.some(([key, entry]) => containsLiteralSecret(entry, key));
|
|
199
|
+
}
|
|
200
|
+
function normalizeManifestPath(value, field) {
|
|
201
|
+
const slashPath = value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
202
|
+
const normalized = path.posix.normalize(slashPath);
|
|
203
|
+
if (!normalized
|
|
204
|
+
|| normalized === "."
|
|
205
|
+
|| normalized.startsWith("../")
|
|
206
|
+
|| normalized.includes("/../")
|
|
207
|
+
|| path.posix.isAbsolute(normalized)) {
|
|
208
|
+
throw unprocessable(`Plugin manifest ${field} contains an unsafe package path`);
|
|
209
|
+
}
|
|
210
|
+
return normalized.replace(/\/$/, "");
|
|
211
|
+
}
|
|
212
|
+
function skillComponents(bytes, manifest) {
|
|
213
|
+
const declared = asString(manifest.skills);
|
|
214
|
+
const declaredPath = declared ? normalizeManifestPath(declared, "skills") : null;
|
|
215
|
+
const matchesDeclared = (filePath) => declaredPath
|
|
216
|
+
? filePath === declaredPath || filePath.startsWith(`${declaredPath}/`)
|
|
217
|
+
: false;
|
|
218
|
+
return [...bytes.keys()]
|
|
219
|
+
.filter((filePath) => path.posix.basename(filePath).toLowerCase() === "skill.md")
|
|
220
|
+
.filter((filePath) => /^skills\//i.test(filePath) || matchesDeclared(filePath))
|
|
221
|
+
.sort()
|
|
222
|
+
.map((filePath) => {
|
|
223
|
+
const root = path.posix.dirname(filePath);
|
|
224
|
+
const slug = path.posix.basename(root);
|
|
225
|
+
const markdown = bytes.get(filePath).toString("utf8");
|
|
226
|
+
const name = markdown.match(/^name:\s*["']?([^\n"']+)/im)?.[1]?.trim() || slug;
|
|
227
|
+
const description = markdown.match(/^description:\s*["']?([^\n"']+)/im)?.[1]?.trim() || null;
|
|
228
|
+
const inventory = [...bytes.keys()].filter((candidate) => candidate === filePath || candidate.startsWith(`${root}/`));
|
|
229
|
+
const executableFiles = inventory
|
|
230
|
+
.filter((candidate) => candidate !== filePath && (/(?:^|\/)scripts?\//i.test(candidate)
|
|
231
|
+
|| /(?:^|\/)bin\//i.test(candidate)
|
|
232
|
+
|| /\.(?:bash|cjs|js|mjs|ps1|py|sh|ts|tsx|zsh)$/i.test(candidate)))
|
|
233
|
+
.map((candidate) => ({
|
|
234
|
+
path: candidate,
|
|
235
|
+
digest: createHash("sha256").update(bytes.get(candidate)).digest("hex"),
|
|
236
|
+
}));
|
|
237
|
+
return {
|
|
238
|
+
key: `skill:${slug}`,
|
|
239
|
+
type: "skill",
|
|
240
|
+
name,
|
|
241
|
+
path: filePath,
|
|
242
|
+
status: "ready",
|
|
243
|
+
required: true,
|
|
244
|
+
detail: description,
|
|
245
|
+
metadata: {
|
|
246
|
+
slug,
|
|
247
|
+
root,
|
|
248
|
+
fileCount: inventory.length,
|
|
249
|
+
instructionDigest: createHash("sha256").update(markdown).digest("hex"),
|
|
250
|
+
executableFiles,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function manifestConfigs(bytes, manifestValue, defaultPath, field) {
|
|
256
|
+
const configs = [];
|
|
257
|
+
const seenPaths = new Set();
|
|
258
|
+
const addPath = (filePath) => {
|
|
259
|
+
if (seenPaths.has(filePath))
|
|
260
|
+
return;
|
|
261
|
+
seenPaths.add(filePath);
|
|
262
|
+
const value = parseJsonFile(bytes, filePath);
|
|
263
|
+
if (!value)
|
|
264
|
+
throw unprocessable(`Plugin manifest ${field} references a missing file: ${filePath}`);
|
|
265
|
+
configs.push({ path: filePath, value });
|
|
266
|
+
};
|
|
267
|
+
if (bytes.has(defaultPath))
|
|
268
|
+
addPath(defaultPath);
|
|
269
|
+
if (typeof manifestValue === "string" && manifestValue.trim()) {
|
|
270
|
+
addPath(normalizeManifestPath(manifestValue, field));
|
|
271
|
+
}
|
|
272
|
+
else if (manifestValue && typeof manifestValue === "object" && !Array.isArray(manifestValue)) {
|
|
273
|
+
configs.push({ path: ".codex-plugin/plugin.json", value: manifestValue });
|
|
274
|
+
}
|
|
275
|
+
return configs;
|
|
276
|
+
}
|
|
277
|
+
function mcpComponents(configs) {
|
|
278
|
+
const seen = new Set();
|
|
279
|
+
const components = [];
|
|
280
|
+
for (const config of configs) {
|
|
281
|
+
const nested = asRecord(config.value.mcpServers);
|
|
282
|
+
const legacyNested = asRecord(config.value.mcp_servers);
|
|
283
|
+
const definitions = Object.keys(nested).length > 0
|
|
284
|
+
? nested
|
|
285
|
+
: Object.keys(legacyNested).length > 0 ? legacyNested : config.value;
|
|
286
|
+
for (const [name, rawDefinition] of Object.entries(definitions)) {
|
|
287
|
+
if (seen.has(name))
|
|
288
|
+
continue;
|
|
289
|
+
seen.add(name);
|
|
290
|
+
const definition = asRecord(rawDefinition);
|
|
291
|
+
const transport = asString(definition.url) ? "streamable_http" : asString(definition.command) ? "stdio" : "unknown";
|
|
292
|
+
components.push({
|
|
293
|
+
key: `mcp:${name}`,
|
|
294
|
+
type: "mcp",
|
|
295
|
+
name,
|
|
296
|
+
path: config.path,
|
|
297
|
+
status: transport === "unknown" ? "unsupported" : "setup_required",
|
|
298
|
+
required: true,
|
|
299
|
+
detail: transport === "unknown" ? "No supported URL or command transport was declared." : "Review and create a managed MCP connection.",
|
|
300
|
+
metadata: { name, transport, definition, sourcePath: config.path },
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return components;
|
|
305
|
+
}
|
|
306
|
+
function appComponents(configs) {
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
return configs.flatMap((config) => Object.entries(config.value).flatMap(([name, id]) => {
|
|
309
|
+
if (seen.has(name))
|
|
310
|
+
return [];
|
|
311
|
+
seen.add(name);
|
|
312
|
+
return [{
|
|
313
|
+
key: `app:${name}`,
|
|
314
|
+
type: "app",
|
|
315
|
+
name,
|
|
316
|
+
path: config.path,
|
|
317
|
+
status: "unsupported",
|
|
318
|
+
required: false,
|
|
319
|
+
detail: "OpenAI registered App ids are preserved but cannot be executed by Rudder V1.",
|
|
320
|
+
metadata: { alias: name, registeredId: typeof id === "string" ? id : null },
|
|
321
|
+
}];
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
function unsupportedCodexComponents(bytes, manifest) {
|
|
325
|
+
const definitions = [
|
|
326
|
+
{
|
|
327
|
+
key: "unsupported:browser-extensions",
|
|
328
|
+
name: "Browser extensions",
|
|
329
|
+
manifestFields: ["browserExtensions", "browser_extensions"],
|
|
330
|
+
filePattern: /^(?:browser-extensions?|extensions\/browser)(?:\/|$)|^\.browser-extensions?\.json$/i,
|
|
331
|
+
detail: "Browser extensions are preserved in the package snapshot but are not installed or loaded by Rudder V1.",
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
key: "unsupported:scheduled-task-templates",
|
|
335
|
+
name: "Scheduled task templates",
|
|
336
|
+
manifestFields: ["scheduledTasks", "scheduled_tasks", "taskTemplates", "task_templates"],
|
|
337
|
+
filePattern: /^(?:scheduled-tasks?|task-templates?|tasks)(?:\/|$)|^\.(?:scheduled-)?tasks\.json$/i,
|
|
338
|
+
detail: "Scheduled task templates are preserved in the package snapshot but are not scheduled or run by Rudder V1.",
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
return definitions.flatMap((definition) => {
|
|
342
|
+
const files = [...bytes.keys()].filter((filePath) => definition.filePattern.test(filePath));
|
|
343
|
+
const declaredFields = definition.manifestFields.filter((field) => manifest[field] !== undefined);
|
|
344
|
+
if (files.length === 0 && declaredFields.length === 0)
|
|
345
|
+
return [];
|
|
346
|
+
return [{
|
|
347
|
+
key: definition.key,
|
|
348
|
+
type: "unsupported",
|
|
349
|
+
name: definition.name,
|
|
350
|
+
path: files[0] ?? null,
|
|
351
|
+
status: "unsupported",
|
|
352
|
+
required: false,
|
|
353
|
+
detail: definition.detail,
|
|
354
|
+
metadata: { fileCount: files.length, declaredFields },
|
|
355
|
+
}];
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function capabilitySnapshot(component) {
|
|
359
|
+
const metadata = asRecord(component.metadata);
|
|
360
|
+
let executionSurface;
|
|
361
|
+
if (component.type === "skill") {
|
|
362
|
+
executionSurface = {
|
|
363
|
+
instructionDigest: metadata.instructionDigest ?? null,
|
|
364
|
+
executableFiles: Array.isArray(metadata.executableFiles) ? metadata.executableFiles : [],
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
else if (component.type === "mcp") {
|
|
368
|
+
const definition = asRecord(metadata.definition);
|
|
369
|
+
const environment = Object.entries(asRecord(definition.env));
|
|
370
|
+
const staticEnvironment = environment
|
|
371
|
+
.filter(([, value]) => typeof value === "string" && !/^\$\{/.test(value))
|
|
372
|
+
.map(([key, value]) => ({
|
|
373
|
+
key,
|
|
374
|
+
valueDigest: createHash("sha256").update(value).digest("hex"),
|
|
375
|
+
}))
|
|
376
|
+
.sort((left, right) => left.key.localeCompare(right.key));
|
|
377
|
+
const forwardedEnvironment = [...new Set(environment
|
|
378
|
+
.map(([, value]) => value)
|
|
379
|
+
.filter((value) => (typeof value === "string" && /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(value)))
|
|
380
|
+
.map((value) => value.slice(2, -1)))]
|
|
381
|
+
.sort((left, right) => left.localeCompare(right));
|
|
382
|
+
executionSurface = {
|
|
383
|
+
transport: metadata.transport ?? null,
|
|
384
|
+
url: asString(definition.url),
|
|
385
|
+
command: asString(definition.command),
|
|
386
|
+
args: Array.isArray(definition.args) ? definition.args.filter((entry) => typeof entry === "string") : [],
|
|
387
|
+
cwd: asString(definition.cwd),
|
|
388
|
+
staticEnvironment,
|
|
389
|
+
forwardedEnvironment,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
else if (component.type === "app") {
|
|
393
|
+
executionSurface = { registeredId: metadata.registeredId ?? null };
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
executionSurface = { unsupported: true };
|
|
397
|
+
}
|
|
398
|
+
return { status: component.status, executionSurface };
|
|
399
|
+
}
|
|
400
|
+
function capabilityDiff(before, after) {
|
|
401
|
+
const beforeByKey = new Map(before.map((component) => [component.key, component]));
|
|
402
|
+
const afterByKey = new Map(after.map((component) => [component.key, component]));
|
|
403
|
+
const changes = [];
|
|
404
|
+
for (const key of [...new Set([...beforeByKey.keys(), ...afterByKey.keys()])].sort()) {
|
|
405
|
+
const previous = beforeByKey.get(key);
|
|
406
|
+
const next = afterByKey.get(key);
|
|
407
|
+
if (!previous && next) {
|
|
408
|
+
const expanded = next.status !== "unsupported" && (next.type === "skill" || next.type === "mcp");
|
|
409
|
+
changes.push({
|
|
410
|
+
kind: "added",
|
|
411
|
+
key,
|
|
412
|
+
type: next.type,
|
|
413
|
+
name: next.name,
|
|
414
|
+
accessImpact: expanded ? "expanded" : "none",
|
|
415
|
+
detail: expanded ? "Adds a new executable capability." : "Adds package inventory without a new Rudder execution path.",
|
|
416
|
+
before: null,
|
|
417
|
+
after: capabilitySnapshot(next),
|
|
418
|
+
});
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (previous && !next) {
|
|
422
|
+
changes.push({
|
|
423
|
+
kind: "removed",
|
|
424
|
+
key,
|
|
425
|
+
type: previous.type,
|
|
426
|
+
name: previous.name,
|
|
427
|
+
accessImpact: previous.status === "unsupported" ? "none" : "reduced",
|
|
428
|
+
detail: "Removes a previously reviewed capability.",
|
|
429
|
+
before: capabilitySnapshot(previous),
|
|
430
|
+
after: null,
|
|
431
|
+
});
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (!previous || !next)
|
|
435
|
+
continue;
|
|
436
|
+
const beforeSnapshot = capabilitySnapshot(previous);
|
|
437
|
+
const afterSnapshot = capabilitySnapshot(next);
|
|
438
|
+
if (JSON.stringify(beforeSnapshot) === JSON.stringify(afterSnapshot))
|
|
439
|
+
continue;
|
|
440
|
+
const becameExecutable = previous.status === "unsupported" && next.status !== "unsupported";
|
|
441
|
+
const executionChanged = JSON.stringify(beforeSnapshot.executionSurface) !== JSON.stringify(afterSnapshot.executionSurface);
|
|
442
|
+
const expanded = becameExecutable || (executionChanged && (next.type === "mcp" || next.type === "skill"));
|
|
443
|
+
changes.push({
|
|
444
|
+
kind: "changed",
|
|
445
|
+
key,
|
|
446
|
+
type: next.type,
|
|
447
|
+
name: next.name,
|
|
448
|
+
accessImpact: expanded ? "expanded" : executionChanged ? "changed" : "none",
|
|
449
|
+
detail: expanded
|
|
450
|
+
? next.type === "mcp"
|
|
451
|
+
? "Changes the MCP command, endpoint, environment, or runtime launch surface."
|
|
452
|
+
: "Changes Skill instructions or executable files."
|
|
453
|
+
: "Changes capability metadata or availability.",
|
|
454
|
+
before: beforeSnapshot,
|
|
455
|
+
after: afterSnapshot,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
return { changes, accessExpansion: changes.some((change) => change.accessImpact === "expanded") };
|
|
459
|
+
}
|
|
460
|
+
function compatibilityReport(bytes, manifest) {
|
|
461
|
+
const mcpConfigs = manifestConfigs(bytes, manifest.mcpServers, ".mcp.json", "mcpServers");
|
|
462
|
+
const appConfigs = manifestConfigs(bytes, manifest.apps, ".app.json", "apps");
|
|
463
|
+
const components = [
|
|
464
|
+
...skillComponents(bytes, manifest),
|
|
465
|
+
...mcpComponents(mcpConfigs),
|
|
466
|
+
...appComponents(appConfigs),
|
|
467
|
+
...unsupportedCodexComponents(bytes, manifest),
|
|
468
|
+
];
|
|
469
|
+
const warnings = [];
|
|
470
|
+
const errors = [];
|
|
471
|
+
if (mcpConfigs.some((config) => containsLiteralSecret(config.value))) {
|
|
472
|
+
errors.push("MCP configuration appears to contain literal credential material. Replace it with environment references before import.");
|
|
473
|
+
}
|
|
474
|
+
const hookFiles = [...bytes.keys()].filter((filePath) => /^hooks(?:\/|$)/i.test(filePath));
|
|
475
|
+
if (hookFiles.length > 0 || manifest.hooks) {
|
|
476
|
+
components.push({
|
|
477
|
+
key: "unsupported:hooks",
|
|
478
|
+
type: "unsupported",
|
|
479
|
+
name: "Hooks",
|
|
480
|
+
path: hookFiles[0] ?? null,
|
|
481
|
+
status: "unsupported",
|
|
482
|
+
required: false,
|
|
483
|
+
detail: "Hooks are preserved in the package snapshot and never executed by Rudder V1.",
|
|
484
|
+
metadata: { fileCount: hookFiles.length },
|
|
485
|
+
});
|
|
486
|
+
warnings.push("Hooks are preserved but unsupported and will not run.");
|
|
487
|
+
}
|
|
488
|
+
if (bytes.has(".rudder/plugin.json")) {
|
|
489
|
+
components.push({
|
|
490
|
+
key: "unsupported:rudder-app-sidecar",
|
|
491
|
+
type: "unsupported",
|
|
492
|
+
name: "Rudder App sidecar",
|
|
493
|
+
path: ".rudder/plugin.json",
|
|
494
|
+
status: "unsupported",
|
|
495
|
+
required: false,
|
|
496
|
+
detail: "Importing a new Local App from a package sidecar is not enabled in V1.",
|
|
497
|
+
metadata: {},
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
if (components.length === 0)
|
|
501
|
+
errors.push("The package contains no Skills, MCP servers, or App references.");
|
|
502
|
+
if (components.every((component) => component.status === "unsupported")) {
|
|
503
|
+
warnings.push("This package has no currently usable Rudder components.");
|
|
504
|
+
errors.push("The package cannot be installed until it contains at least one supported or setup-capable component.");
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
manifest,
|
|
508
|
+
components,
|
|
509
|
+
warnings,
|
|
510
|
+
errors,
|
|
511
|
+
limits: { fileCount: bytes.size, totalBytes: [...bytes.values()].reduce((sum, content) => sum + content.byteLength, 0) },
|
|
512
|
+
operation: "install",
|
|
513
|
+
installedPluginId: null,
|
|
514
|
+
capabilityDiff: null,
|
|
515
|
+
skillConflicts: [],
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function normalizeManifest(manifest) {
|
|
519
|
+
const interfaceMetadata = asRecord(manifest.interface);
|
|
520
|
+
const author = asRecord(manifest.author);
|
|
521
|
+
return {
|
|
522
|
+
name: asString(manifest.name),
|
|
523
|
+
version: asString(manifest.version),
|
|
524
|
+
description: asString(manifest.description),
|
|
525
|
+
displayName: asString(interfaceMetadata.displayName) ?? asString(manifest.name),
|
|
526
|
+
shortDescription: asString(interfaceMetadata.shortDescription) ?? asString(manifest.description),
|
|
527
|
+
publisher: asString(interfaceMetadata.developerName) ?? asString(author.name) ?? asString(manifest.author),
|
|
528
|
+
category: asString(interfaceMetadata.category),
|
|
529
|
+
capabilities: Array.isArray(interfaceMetadata.capabilities) ? interfaceMetadata.capabilities : [],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function reportFromRow(row) {
|
|
533
|
+
const report = row.report;
|
|
534
|
+
return {
|
|
535
|
+
id: row.id,
|
|
536
|
+
orgId: row.orgId,
|
|
537
|
+
packageId: row.packageId,
|
|
538
|
+
sourceType: row.sourceType,
|
|
539
|
+
sourceLabel: row.sourceLabel,
|
|
540
|
+
status: row.status,
|
|
541
|
+
digest: row.digest,
|
|
542
|
+
manifest: report.manifest,
|
|
543
|
+
components: report.components,
|
|
544
|
+
warnings: report.warnings,
|
|
545
|
+
errors: report.errors,
|
|
546
|
+
limits: report.limits,
|
|
547
|
+
createdAt: row.createdAt.toISOString(),
|
|
548
|
+
operation: report.operation,
|
|
549
|
+
installedPluginId: report.installedPluginId,
|
|
550
|
+
capabilityDiff: report.capabilityDiff ?? null,
|
|
551
|
+
skillConflicts: report.skillConflicts ?? [],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
export function inspectRudderPluginPackage(input) {
|
|
555
|
+
const normalized = normalizeFiles(input);
|
|
556
|
+
const manifest = parseJsonFile(normalized.bytes, ".codex-plugin/plugin.json");
|
|
557
|
+
if (!manifest)
|
|
558
|
+
throw unprocessable("Plugin package must contain .codex-plugin/plugin.json");
|
|
559
|
+
const identity = normalizeManifest(manifest);
|
|
560
|
+
if (!identity.name || !identity.version)
|
|
561
|
+
throw unprocessable("Plugin manifest must declare name and version");
|
|
562
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(identity.name) || identity.name.length > 64) {
|
|
563
|
+
throw unprocessable("Plugin manifest name must be lower-case hyphen-case and at most 64 characters");
|
|
564
|
+
}
|
|
565
|
+
if (!/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(identity.version)) {
|
|
566
|
+
throw unprocessable("Plugin manifest version must be strict semantic versioning");
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
normalized,
|
|
570
|
+
manifest,
|
|
571
|
+
identity: identity,
|
|
572
|
+
report: compatibilityReport(normalized.bytes, manifest),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
export function inspectRudderPluginArchivePackage(input) {
|
|
576
|
+
return inspectRudderPluginPackage({
|
|
577
|
+
sourceType: "local_upload",
|
|
578
|
+
sourceLabel: input.sourceLabel,
|
|
579
|
+
files: unzipPackageFiles(input.content, input.filename, true)
|
|
580
|
+
.map((file) => ({ ...file, encoding: file.encoding ?? "utf8" })),
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
export function rudderPluginService(db, mcpOptions) {
|
|
584
|
+
const skills = organizationSkillService(db);
|
|
585
|
+
const enabledSkills = agentEnabledSkillsService(db);
|
|
586
|
+
const mcpConnections = managedMcpConnectionService(db, mcpOptions);
|
|
587
|
+
async function inspectFromSource(orgId, input, source) {
|
|
588
|
+
const { normalized, manifest, identity, report } = inspectRudderPluginPackage(input);
|
|
589
|
+
const sourceNamespace = asString(source.metadata?.marketplaceName)
|
|
590
|
+
? `${source.type}:${asString(source.metadata?.marketplaceName)}`
|
|
591
|
+
: source.type;
|
|
592
|
+
const normalizedIdentity = { ...identity, sourceNamespace };
|
|
593
|
+
const identityConflict = await db.select({
|
|
594
|
+
digest: pluginPackages.digest,
|
|
595
|
+
normalizedManifest: pluginPackages.normalizedManifest,
|
|
596
|
+
sourceMetadata: pluginSources.metadata,
|
|
597
|
+
})
|
|
598
|
+
.from(pluginImportReports)
|
|
599
|
+
.innerJoin(pluginPackages, eq(pluginImportReports.packageId, pluginPackages.id))
|
|
600
|
+
.innerJoin(pluginSources, eq(pluginImportReports.sourceId, pluginSources.id))
|
|
601
|
+
.where(and(eq(pluginImportReports.orgId, orgId), eq(pluginImportReports.sourceType, source.type), eq(pluginPackages.name, identity.name), eq(pluginPackages.version, identity.version)))
|
|
602
|
+
.then((rows) => rows.find((row) => (asString(asRecord(row.normalizedManifest).publisher) ?? null) === (identity.publisher ?? null)
|
|
603
|
+
&& (asString(row.sourceMetadata.marketplaceName)
|
|
604
|
+
? `${source.type}:${asString(row.sourceMetadata.marketplaceName)}`
|
|
605
|
+
: source.type) === sourceNamespace
|
|
606
|
+
&& row.digest !== normalized.digest) ?? null);
|
|
607
|
+
if (identityConflict)
|
|
608
|
+
report.errors.push("The same Plugin name and version already exists with a different digest.");
|
|
609
|
+
const identityKey = packageIdentityKey(sourceNamespace, identity.publisher, identity.name);
|
|
610
|
+
const currentInstallation = await db.select({
|
|
611
|
+
id: installedPlugins.id,
|
|
612
|
+
digest: pluginPackages.digest,
|
|
613
|
+
version: pluginPackages.version,
|
|
614
|
+
compatibility: pluginPackages.compatibility,
|
|
615
|
+
}).from(installedPlugins)
|
|
616
|
+
.innerJoin(pluginPackages, eq(installedPlugins.packageId, pluginPackages.id))
|
|
617
|
+
.where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.packageName, identityKey), ne(installedPlugins.lifecycleState, "uninstalled")))
|
|
618
|
+
.then((rows) => rows[0] ?? null);
|
|
619
|
+
if (currentInstallation?.digest === normalized.digest) {
|
|
620
|
+
report.errors.push("This exact Plugin package is already installed in this Organization.");
|
|
621
|
+
}
|
|
622
|
+
else if (currentInstallation && currentInstallation.version !== identity.version && !identityConflict) {
|
|
623
|
+
report.operation = "update";
|
|
624
|
+
report.installedPluginId = currentInstallation.id;
|
|
625
|
+
const currentReport = currentInstallation.compatibility;
|
|
626
|
+
report.capabilityDiff = capabilityDiff(currentReport.components ?? [], report.components);
|
|
627
|
+
}
|
|
628
|
+
const existingSkills = await db.select().from(organizationSkills)
|
|
629
|
+
.where(eq(organizationSkills.orgId, orgId));
|
|
630
|
+
const ownedSkillIds = new Set(currentInstallation
|
|
631
|
+
? await db.select({ targetId: pluginComponentLinks.targetId }).from(pluginComponentLinks)
|
|
632
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.installedPluginId, currentInstallation.id), eq(pluginComponentLinks.componentType, "skill"))).then((rows) => rows.map((row) => row.targetId).filter((id) => Boolean(id)))
|
|
633
|
+
: []);
|
|
634
|
+
report.skillConflicts = report.components.flatMap((component) => {
|
|
635
|
+
if (component.type !== "skill")
|
|
636
|
+
return [];
|
|
637
|
+
const slug = asString(component.metadata.slug);
|
|
638
|
+
if (!slug)
|
|
639
|
+
return [];
|
|
640
|
+
const existing = existingSkills.find((skill) => (!ownedSkillIds.has(skill.id)
|
|
641
|
+
&& (skill.slug.toLocaleLowerCase("en-US") === slug.toLocaleLowerCase("en-US")
|
|
642
|
+
|| skill.key.toLocaleLowerCase("en-US").endsWith(`:${slug.toLocaleLowerCase("en-US")}`))));
|
|
643
|
+
return existing ? [{
|
|
644
|
+
componentKey: component.key,
|
|
645
|
+
skillKey: existing.key,
|
|
646
|
+
skillName: component.name,
|
|
647
|
+
existingSkillId: existing.id,
|
|
648
|
+
existingSkillName: existing.name,
|
|
649
|
+
}] : [];
|
|
650
|
+
});
|
|
651
|
+
const [sourceRow] = await db.insert(pluginSources).values({
|
|
652
|
+
orgId,
|
|
653
|
+
sourceType: source.type,
|
|
654
|
+
label: source.label,
|
|
655
|
+
locator: source.locator ?? null,
|
|
656
|
+
metadata: {
|
|
657
|
+
fileCount: normalized.files.length,
|
|
658
|
+
totalBytes: normalized.totalBytes,
|
|
659
|
+
...(source.metadata ?? {}),
|
|
660
|
+
},
|
|
661
|
+
}).returning();
|
|
662
|
+
let packageId = null;
|
|
663
|
+
if (report.errors.length === 0) {
|
|
664
|
+
const existingPackage = await db.select().from(pluginPackages)
|
|
665
|
+
.where(eq(pluginPackages.digest, normalized.digest))
|
|
666
|
+
.then((rows) => rows[0] ?? null);
|
|
667
|
+
const packageRow = existingPackage ?? await db.insert(pluginPackages).values({
|
|
668
|
+
sourceId: sourceRow.id,
|
|
669
|
+
name: identity.name,
|
|
670
|
+
version: identity.version,
|
|
671
|
+
digest: normalized.digest,
|
|
672
|
+
rawManifest: manifest,
|
|
673
|
+
normalizedManifest: normalizedIdentity,
|
|
674
|
+
snapshot: normalized.files,
|
|
675
|
+
compatibility: report,
|
|
676
|
+
}).returning().then((rows) => rows[0]);
|
|
677
|
+
packageId = packageRow.id;
|
|
678
|
+
}
|
|
679
|
+
const [row] = await db.insert(pluginImportReports).values({
|
|
680
|
+
orgId,
|
|
681
|
+
packageId,
|
|
682
|
+
sourceId: sourceRow.id,
|
|
683
|
+
sourceType: source.type,
|
|
684
|
+
sourceLabel: source.label,
|
|
685
|
+
status: report.errors.length > 0 ? "failed" : "review_required",
|
|
686
|
+
digest: normalized.digest,
|
|
687
|
+
report: report,
|
|
688
|
+
}).returning();
|
|
689
|
+
return reportFromRow(row);
|
|
690
|
+
}
|
|
691
|
+
async function inspect(orgId, input) {
|
|
692
|
+
return inspectFromSource(orgId, input, { type: "local_upload", label: input.sourceLabel });
|
|
693
|
+
}
|
|
694
|
+
async function inspectArchive(orgId, input) {
|
|
695
|
+
const files = unzipPackageFiles(input.content, input.filename, true);
|
|
696
|
+
return inspectFromSource(orgId, {
|
|
697
|
+
sourceType: "local_upload",
|
|
698
|
+
sourceLabel: input.sourceLabel,
|
|
699
|
+
files: files.map((file) => ({ ...file, encoding: file.encoding ?? "utf8" })),
|
|
700
|
+
}, {
|
|
701
|
+
type: "local_upload",
|
|
702
|
+
label: input.sourceLabel,
|
|
703
|
+
locator: input.filename,
|
|
704
|
+
metadata: { archive: true, filename: input.filename },
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
async function configureMarketplace(orgId, input) {
|
|
708
|
+
let sourceType = "marketplace";
|
|
709
|
+
let sourceFiles;
|
|
710
|
+
let locator = null;
|
|
711
|
+
let provenance = {};
|
|
712
|
+
if (input.github) {
|
|
713
|
+
const repository = new URL(input.github.repository);
|
|
714
|
+
if (repository.protocol !== "https:" || repository.hostname.toLocaleLowerCase("en-US") !== "github.com") {
|
|
715
|
+
throw unprocessable("Git marketplace repositories must use HTTPS on github.com");
|
|
716
|
+
}
|
|
717
|
+
const segments = repository.pathname.replace(/\.git$/i, "").split("/").filter(Boolean);
|
|
718
|
+
if (segments.length !== 2)
|
|
719
|
+
throw unprocessable("Git marketplace repository must identify one GitHub owner and repository");
|
|
720
|
+
const [owner, repo] = segments;
|
|
721
|
+
const archiveUrl = `https://codeload.github.com/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/zip/${input.github.commit}`;
|
|
722
|
+
const response = await fetch(archiveUrl, {
|
|
723
|
+
redirect: "error",
|
|
724
|
+
headers: { accept: "application/zip", "user-agent": "Rudder-Plugin-V1" },
|
|
725
|
+
});
|
|
726
|
+
if (!response.ok)
|
|
727
|
+
throw unprocessable(`Pinned Git marketplace fetch failed with HTTP ${response.status}`);
|
|
728
|
+
const contentLength = Number(response.headers.get("content-length") ?? "0");
|
|
729
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_PLUGIN_BYTES) {
|
|
730
|
+
throw unprocessable("Pinned Git marketplace archive exceeds the 10 MiB V1 limit");
|
|
731
|
+
}
|
|
732
|
+
const archive = Buffer.from(await response.arrayBuffer());
|
|
733
|
+
if (archive.byteLength > MAX_PLUGIN_BYTES)
|
|
734
|
+
throw unprocessable("Pinned Git marketplace archive exceeds the 10 MiB V1 limit");
|
|
735
|
+
sourceFiles = unzipPackageFiles(archive.toString("base64"), `${repo}@${input.github.commit}.zip`, true);
|
|
736
|
+
sourceType = "git";
|
|
737
|
+
locator = `${repository.toString().replace(/\/$/, "")}#${input.github.commit}`;
|
|
738
|
+
provenance = { repository: repository.toString(), commit: input.github.commit, immutable: true };
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
sourceFiles = input.files ?? [];
|
|
742
|
+
provenance = { local: true };
|
|
743
|
+
}
|
|
744
|
+
const normalizedMarketplace = normalizeFiles({
|
|
745
|
+
sourceType: "local_upload",
|
|
746
|
+
sourceLabel: input.sourceLabel,
|
|
747
|
+
files: sourceFiles.map((file) => ({ ...file, encoding: file.encoding ?? "utf8" })),
|
|
748
|
+
});
|
|
749
|
+
const marketplacePaths = [...normalizedMarketplace.bytes.keys()]
|
|
750
|
+
.filter((filePath) => path.posix.basename(filePath).toLocaleLowerCase("en-US") === "marketplace.json")
|
|
751
|
+
.sort((left, right) => left.length - right.length);
|
|
752
|
+
if (marketplacePaths.length === 0)
|
|
753
|
+
throw unprocessable("Marketplace source must contain marketplace.json");
|
|
754
|
+
const marketplacePath = marketplacePaths[0];
|
|
755
|
+
const marketplace = parseJsonFile(normalizedMarketplace.bytes, marketplacePath);
|
|
756
|
+
const marketplaceName = asString(marketplace.name) ?? input.sourceLabel;
|
|
757
|
+
if (!Array.isArray(marketplace.plugins))
|
|
758
|
+
throw unprocessable("marketplace.json must declare an ordered plugins array");
|
|
759
|
+
const marketplaceRoot = path.posix.dirname(marketplacePath) === "." ? "" : path.posix.dirname(marketplacePath);
|
|
760
|
+
const reports = [];
|
|
761
|
+
for (const [index, rawEntry] of marketplace.plugins.entries()) {
|
|
762
|
+
const entry = asRecord(rawEntry);
|
|
763
|
+
const name = asString(entry.name);
|
|
764
|
+
const source = asRecord(entry.source);
|
|
765
|
+
const policy = asRecord(entry.policy);
|
|
766
|
+
const installation = asString(policy.installation);
|
|
767
|
+
if (!name || source.source !== "local" || !asString(source.path))
|
|
768
|
+
continue;
|
|
769
|
+
if (installation === "NOT_AVAILABLE")
|
|
770
|
+
continue;
|
|
771
|
+
const relativeRoot = normalizeManifestPath(asString(source.path), "marketplace source.path").replace(/^\.\//, "");
|
|
772
|
+
const pluginRoot = marketplaceRoot ? path.posix.join(marketplaceRoot, relativeRoot) : relativeRoot;
|
|
773
|
+
const pluginFiles = [...normalizedMarketplace.bytes.entries()]
|
|
774
|
+
.filter(([filePath]) => filePath.startsWith(`${pluginRoot}/`))
|
|
775
|
+
.map(([filePath, content]) => ({
|
|
776
|
+
path: filePath.slice(pluginRoot.length + 1),
|
|
777
|
+
content: content.toString("base64"),
|
|
778
|
+
encoding: "base64",
|
|
779
|
+
}));
|
|
780
|
+
if (pluginFiles.length === 0)
|
|
781
|
+
throw unprocessable(`Marketplace Plugin ${name} points to a missing local source path`);
|
|
782
|
+
const report = await inspectFromSource(orgId, {
|
|
783
|
+
sourceType: "local_upload",
|
|
784
|
+
sourceLabel: `${marketplaceName}/${name}`,
|
|
785
|
+
files: pluginFiles,
|
|
786
|
+
}, {
|
|
787
|
+
type: sourceType,
|
|
788
|
+
label: `${marketplaceName}/${name}`,
|
|
789
|
+
locator,
|
|
790
|
+
metadata: {
|
|
791
|
+
...provenance,
|
|
792
|
+
marketplaceName,
|
|
793
|
+
marketplacePath,
|
|
794
|
+
marketplaceOrder: index,
|
|
795
|
+
policy,
|
|
796
|
+
category: asString(entry.category),
|
|
797
|
+
declaredInstallation: installation ?? "AVAILABLE",
|
|
798
|
+
declaredAuthentication: asString(policy.authentication),
|
|
799
|
+
},
|
|
800
|
+
});
|
|
801
|
+
reports.push(report);
|
|
802
|
+
}
|
|
803
|
+
if (reports.length === 0)
|
|
804
|
+
throw unprocessable("Marketplace contains no available local Plugins that Rudder can review");
|
|
805
|
+
return reports;
|
|
806
|
+
}
|
|
807
|
+
async function listDiscover(orgId) {
|
|
808
|
+
const rows = await db.select({
|
|
809
|
+
report: pluginImportReports,
|
|
810
|
+
pkg: pluginPackages,
|
|
811
|
+
source: pluginSources,
|
|
812
|
+
}).from(pluginImportReports)
|
|
813
|
+
.innerJoin(pluginPackages, eq(pluginImportReports.packageId, pluginPackages.id))
|
|
814
|
+
.innerJoin(pluginSources, eq(pluginImportReports.sourceId, pluginSources.id))
|
|
815
|
+
.where(and(eq(pluginImportReports.orgId, orgId), eq(pluginImportReports.status, "review_required"), inArray(pluginImportReports.sourceType, ["marketplace", "git"])))
|
|
816
|
+
.orderBy(asc(pluginImportReports.createdAt));
|
|
817
|
+
const latest = new Map();
|
|
818
|
+
for (const row of rows)
|
|
819
|
+
latest.set(`${row.report.sourceType}:${asString(row.source.metadata.marketplaceName) ?? row.source.label}:${row.pkg.name}`, row);
|
|
820
|
+
return [...latest.values()].map(({ report: reportRow, pkg, source }) => {
|
|
821
|
+
const normalized = asRecord(pkg.normalizedManifest);
|
|
822
|
+
const report = reportFromRow(reportRow);
|
|
823
|
+
return {
|
|
824
|
+
reportId: reportRow.id,
|
|
825
|
+
packageId: pkg.id,
|
|
826
|
+
name: pkg.name,
|
|
827
|
+
displayName: asString(normalized.displayName) ?? pkg.name,
|
|
828
|
+
description: asString(normalized.shortDescription) ?? asString(normalized.description),
|
|
829
|
+
version: pkg.version,
|
|
830
|
+
publisher: asString(normalized.publisher),
|
|
831
|
+
sourceLabel: reportRow.sourceLabel,
|
|
832
|
+
sourceType: reportRow.sourceType,
|
|
833
|
+
digest: pkg.digest,
|
|
834
|
+
category: asString(source.metadata.category) ?? asString(normalized.category),
|
|
835
|
+
policy: asRecord(source.metadata.policy),
|
|
836
|
+
components: report.components,
|
|
837
|
+
};
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
async function getImportReport(orgId, reportId) {
|
|
841
|
+
const row = await db.select().from(pluginImportReports)
|
|
842
|
+
.where(and(eq(pluginImportReports.orgId, orgId), eq(pluginImportReports.id, reportId)))
|
|
843
|
+
.then((rows) => rows[0] ?? null);
|
|
844
|
+
return row ? reportFromRow(row) : null;
|
|
845
|
+
}
|
|
846
|
+
async function applyPackageRevision(orgId, pluginId, packageRow, sourceId, operation, skillConflictStrategy = "rename") {
|
|
847
|
+
const current = await getInstalled(orgId, pluginId);
|
|
848
|
+
if (!current)
|
|
849
|
+
throw notFound("Installed Plugin not found");
|
|
850
|
+
const report = packageRow.compatibility;
|
|
851
|
+
const snapshot = packageRow.snapshot;
|
|
852
|
+
const skillRoots = report.components
|
|
853
|
+
.filter((component) => component.type === "skill")
|
|
854
|
+
.map((component) => asString(component.metadata.root))
|
|
855
|
+
.filter((root) => Boolean(root));
|
|
856
|
+
const packageFiles = Object.fromEntries(snapshot
|
|
857
|
+
.filter((file) => skillRoots.some((root) => file.path === `${root}/SKILL.md` || file.path.startsWith(`${root}/`)))
|
|
858
|
+
.map((file) => [file.path, Buffer.from(file.content, "base64").toString("utf8")]));
|
|
859
|
+
const importedSkills = report.components.some((component) => component.type === "skill")
|
|
860
|
+
? await skills.importPackageFiles(orgId, packageFiles, {
|
|
861
|
+
onConflict: skillConflictStrategy === "keep" ? "skip" : skillConflictStrategy,
|
|
862
|
+
})
|
|
863
|
+
: [];
|
|
864
|
+
let switched = false;
|
|
865
|
+
try {
|
|
866
|
+
for (const result of importedSkills) {
|
|
867
|
+
if (result.action === "skipped")
|
|
868
|
+
continue;
|
|
869
|
+
await db.update(organizationSkills).set({
|
|
870
|
+
metadata: {
|
|
871
|
+
...asRecord(result.skill.metadata),
|
|
872
|
+
sourceKind: "plugin_managed",
|
|
873
|
+
pluginManaged: true,
|
|
874
|
+
pluginEnabled: current.enabled,
|
|
875
|
+
pluginDisplayName: asString(asRecord(packageRow.normalizedManifest).displayName) ?? packageRow.name,
|
|
876
|
+
installedPluginId: current.id,
|
|
877
|
+
pluginPackageId: packageRow.id,
|
|
878
|
+
pluginDigest: packageRow.digest,
|
|
879
|
+
},
|
|
880
|
+
updatedAt: new Date(),
|
|
881
|
+
}).where(and(eq(organizationSkills.orgId, orgId), eq(organizationSkills.id, result.skill.id)));
|
|
882
|
+
}
|
|
883
|
+
const oldByKey = new Map(current.components.map((component) => [component.key, component]));
|
|
884
|
+
const existingRetiredMcpLinks = await db.select().from(pluginComponentLinks)
|
|
885
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.installedPluginId, current.id), eq(pluginComponentLinks.componentType, "mcp")))
|
|
886
|
+
.then((rows) => rows.filter((link) => asRecord(link.metadata).retired === true));
|
|
887
|
+
const linkValues = [];
|
|
888
|
+
for (const result of importedSkills) {
|
|
889
|
+
const declared = report.components.find((component) => (component.type === "skill" && asString(component.metadata.slug) === result.originalSlug));
|
|
890
|
+
const componentKey = declared?.key ?? `skill:${result.originalSlug}`;
|
|
891
|
+
const old = oldByKey.get(componentKey);
|
|
892
|
+
linkValues.push({
|
|
893
|
+
orgId,
|
|
894
|
+
installedPluginId: current.id,
|
|
895
|
+
componentType: "skill",
|
|
896
|
+
componentKey,
|
|
897
|
+
displayName: result.skill.name,
|
|
898
|
+
status: result.action === "skipped" ? "disabled" : current.enabled ? "ready" : "disabled",
|
|
899
|
+
targetId: result.action === "skipped" ? null : result.skill.id,
|
|
900
|
+
metadata: {
|
|
901
|
+
skillKey: result.skill.key,
|
|
902
|
+
originalSlug: result.originalSlug,
|
|
903
|
+
action: result.action,
|
|
904
|
+
...(result.action === "skipped" ? { keptExistingSkillId: result.skill.id } : {}),
|
|
905
|
+
enabledAgentIds: Array.isArray(old?.metadata.enabledAgentIds) ? old.metadata.enabledAgentIds : [],
|
|
906
|
+
},
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
for (const component of report.components.filter((entry) => entry.type !== "skill")) {
|
|
910
|
+
const old = oldByKey.get(component.key);
|
|
911
|
+
const definitionUnchanged = component.type !== "mcp"
|
|
912
|
+
|| JSON.stringify(old?.metadata.definition ?? null) === JSON.stringify(component.metadata.definition ?? null);
|
|
913
|
+
const reusableRetiredMcp = component.type === "mcp" && !definitionUnchanged
|
|
914
|
+
? existingRetiredMcpLinks.find((link) => (asString(asRecord(link.metadata).previousComponentKey) === component.key
|
|
915
|
+
&& JSON.stringify(asRecord(link.metadata).definition ?? null)
|
|
916
|
+
=== JSON.stringify(component.metadata.definition ?? null)))
|
|
917
|
+
: null;
|
|
918
|
+
linkValues.push({
|
|
919
|
+
orgId,
|
|
920
|
+
installedPluginId: current.id,
|
|
921
|
+
componentType: component.type,
|
|
922
|
+
componentKey: component.key,
|
|
923
|
+
displayName: component.name,
|
|
924
|
+
status: current.enabled ? component.status : "disabled",
|
|
925
|
+
targetId: definitionUnchanged
|
|
926
|
+
? old?.targetId ?? null
|
|
927
|
+
: reusableRetiredMcp?.targetId ?? null,
|
|
928
|
+
metadata: component.metadata,
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
const retainedMcpTargets = new Set(linkValues
|
|
932
|
+
.filter((link) => link.componentType === "mcp" && link.targetId)
|
|
933
|
+
.map((link) => link.targetId));
|
|
934
|
+
const retiredAt = new Date().toISOString();
|
|
935
|
+
const newlyRetiredMcpLinks = current.components
|
|
936
|
+
.filter((component) => (component.type === "mcp"
|
|
937
|
+
&& component.targetId
|
|
938
|
+
&& !retainedMcpTargets.has(component.targetId)))
|
|
939
|
+
.map((component) => ({
|
|
940
|
+
orgId,
|
|
941
|
+
installedPluginId: current.id,
|
|
942
|
+
componentType: "mcp",
|
|
943
|
+
componentKey: `retired:${component.targetId}`,
|
|
944
|
+
displayName: component.displayName,
|
|
945
|
+
status: "disabled",
|
|
946
|
+
targetId: component.targetId,
|
|
947
|
+
metadata: {
|
|
948
|
+
...component.metadata,
|
|
949
|
+
retired: true,
|
|
950
|
+
retiredAt,
|
|
951
|
+
retiredReason: "package_revision",
|
|
952
|
+
previousComponentKey: component.key,
|
|
953
|
+
},
|
|
954
|
+
}));
|
|
955
|
+
for (const retired of [...existingRetiredMcpLinks, ...newlyRetiredMcpLinks]) {
|
|
956
|
+
if (linkValues.some((link) => link.componentKey === retired.componentKey)
|
|
957
|
+
|| (retired.targetId && retainedMcpTargets.has(retired.targetId)))
|
|
958
|
+
continue;
|
|
959
|
+
linkValues.push({
|
|
960
|
+
orgId,
|
|
961
|
+
installedPluginId: current.id,
|
|
962
|
+
componentType: "mcp",
|
|
963
|
+
componentKey: retired.componentKey,
|
|
964
|
+
displayName: retired.displayName,
|
|
965
|
+
status: "disabled",
|
|
966
|
+
targetId: retired.targetId,
|
|
967
|
+
metadata: retired.metadata,
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
const supported = report.components.filter((component) => component.status !== "unsupported");
|
|
971
|
+
const setupRequired = supported.some((component) => component.status === "setup_required");
|
|
972
|
+
await db.transaction(async (tx) => {
|
|
973
|
+
await tx.delete(pluginComponentLinks).where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.installedPluginId, current.id)));
|
|
974
|
+
if (linkValues.length > 0)
|
|
975
|
+
await tx.insert(pluginComponentLinks).values(linkValues);
|
|
976
|
+
await tx.update(installedPlugins).set({
|
|
977
|
+
packageId: packageRow.id,
|
|
978
|
+
previousPackageId: current.packageId,
|
|
979
|
+
sourceId,
|
|
980
|
+
setupState: supported.length === 0 ? "blocked" : setupRequired ? "setup_required" : "ready",
|
|
981
|
+
healthState: supported.length === 0 ? "unavailable" : report.warnings.length > 0 ? "degraded" : "healthy",
|
|
982
|
+
updateState: "none",
|
|
983
|
+
lastOperation: { kind: operation, result: "succeeded", fromPackageId: current.packageId, toPackageId: packageRow.id, at: new Date().toISOString() },
|
|
984
|
+
updatedAt: new Date(),
|
|
985
|
+
}).where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, current.id)));
|
|
986
|
+
});
|
|
987
|
+
switched = true;
|
|
988
|
+
for (const component of current.components.filter((entry) => entry.type === "skill" && entry.targetId)) {
|
|
989
|
+
await skills.deletePluginManagedSkill(orgId, component.targetId, current.id);
|
|
990
|
+
}
|
|
991
|
+
for (const link of linkValues.filter((entry) => entry.componentType === "skill" && entry.targetId)) {
|
|
992
|
+
const skill = await db.select().from(organizationSkills).where(eq(organizationSkills.id, link.targetId)).then((rows) => rows[0] ?? null);
|
|
993
|
+
const agentIds = Array.isArray(link.metadata?.enabledAgentIds)
|
|
994
|
+
? link.metadata.enabledAgentIds.filter((value) => typeof value === "string")
|
|
995
|
+
: [];
|
|
996
|
+
if (skill)
|
|
997
|
+
for (const agentId of agentIds)
|
|
998
|
+
await enabledSkills.addMissingKeys(orgId, agentId, [skill.key]);
|
|
999
|
+
}
|
|
1000
|
+
return (await getInstalled(orgId, current.id));
|
|
1001
|
+
}
|
|
1002
|
+
catch (error) {
|
|
1003
|
+
if (!switched) {
|
|
1004
|
+
for (const result of importedSkills) {
|
|
1005
|
+
await skills.deletePluginManagedSkill(orgId, result.skill.id, current.id).catch(() => null);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
await db.update(installedPlugins).set({
|
|
1010
|
+
healthState: "degraded",
|
|
1011
|
+
lastOperation: { kind: operation, result: "cleanup_failed", message: error instanceof Error ? error.message : String(error), at: new Date().toISOString() },
|
|
1012
|
+
updatedAt: new Date(),
|
|
1013
|
+
}).where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, current.id)));
|
|
1014
|
+
}
|
|
1015
|
+
throw error;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
async function install(orgId, reportId, enabled = true, confirmAccessExpansion = false, skillConflictStrategy) {
|
|
1019
|
+
const reportRow = await db.select().from(pluginImportReports)
|
|
1020
|
+
.where(and(eq(pluginImportReports.orgId, orgId), eq(pluginImportReports.id, reportId)))
|
|
1021
|
+
.then((rows) => rows[0] ?? null);
|
|
1022
|
+
if (!reportRow)
|
|
1023
|
+
throw notFound("Plugin import report not found");
|
|
1024
|
+
if (reportRow.status !== "review_required" || !reportRow.packageId) {
|
|
1025
|
+
throw unprocessable("Plugin import is not ready for installation");
|
|
1026
|
+
}
|
|
1027
|
+
const packageRow = await db.select().from(pluginPackages)
|
|
1028
|
+
.where(eq(pluginPackages.id, reportRow.packageId))
|
|
1029
|
+
.then((rows) => rows[0] ?? null);
|
|
1030
|
+
if (!packageRow)
|
|
1031
|
+
throw notFound("Plugin package snapshot not found");
|
|
1032
|
+
const normalizedManifest = asRecord(packageRow.normalizedManifest);
|
|
1033
|
+
const sourceRow = reportRow.sourceId
|
|
1034
|
+
? await db.select().from(pluginSources).where(eq(pluginSources.id, reportRow.sourceId)).then((rows) => rows[0] ?? null)
|
|
1035
|
+
: null;
|
|
1036
|
+
const installSourceNamespace = asString(sourceRow?.metadata.marketplaceName)
|
|
1037
|
+
? `${reportRow.sourceType}:${asString(sourceRow?.metadata.marketplaceName)}`
|
|
1038
|
+
: reportRow.sourceType;
|
|
1039
|
+
const installedIdentity = packageIdentityKey(installSourceNamespace, asString(normalizedManifest.publisher), packageRow.name);
|
|
1040
|
+
const existing = await db.select({ id: installedPlugins.id }).from(installedPlugins)
|
|
1041
|
+
.where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.packageName, installedIdentity), ne(installedPlugins.lifecycleState, "uninstalled")))
|
|
1042
|
+
.then((rows) => rows[0] ?? null);
|
|
1043
|
+
const report = packageRow.compatibility;
|
|
1044
|
+
const importReport = reportRow.report;
|
|
1045
|
+
if (importReport.operation === "update" && importReport.capabilityDiff?.accessExpansion && !confirmAccessExpansion) {
|
|
1046
|
+
throw unprocessable("This Plugin update expands execution or external access. Confirm the reviewed access expansion before applying it.");
|
|
1047
|
+
}
|
|
1048
|
+
if ((importReport.skillConflicts?.length ?? 0) > 0 && !skillConflictStrategy) {
|
|
1049
|
+
throw unprocessable("Choose keep, replace, or rename for the reported Skill conflicts before installing");
|
|
1050
|
+
}
|
|
1051
|
+
const resolvedSkillConflictStrategy = skillConflictStrategy ?? "rename";
|
|
1052
|
+
if (existing) {
|
|
1053
|
+
if (importReport.operation === "update" && importReport.installedPluginId === existing.id) {
|
|
1054
|
+
const updated = await applyPackageRevision(orgId, existing.id, packageRow, reportRow.sourceId, "update", resolvedSkillConflictStrategy);
|
|
1055
|
+
await db.update(pluginImportReports).set({ status: "accepted", updatedAt: new Date() })
|
|
1056
|
+
.where(and(eq(pluginImportReports.orgId, orgId), eq(pluginImportReports.id, reportRow.id)));
|
|
1057
|
+
return updated;
|
|
1058
|
+
}
|
|
1059
|
+
throw conflict("Plugin is already installed in this Organization");
|
|
1060
|
+
}
|
|
1061
|
+
const supported = report.components.filter((component) => component.status !== "unsupported");
|
|
1062
|
+
const setupRequired = supported.some((component) => component.status === "setup_required");
|
|
1063
|
+
const [installed] = await db.insert(installedPlugins).values({
|
|
1064
|
+
orgId,
|
|
1065
|
+
packageId: packageRow.id,
|
|
1066
|
+
sourceId: reportRow.sourceId,
|
|
1067
|
+
packageName: installedIdentity,
|
|
1068
|
+
enabled,
|
|
1069
|
+
setupState: "configuring",
|
|
1070
|
+
healthState: "unknown",
|
|
1071
|
+
lastOperation: { kind: "install", result: "running", at: new Date().toISOString() },
|
|
1072
|
+
}).returning();
|
|
1073
|
+
const importedSkillIds = [];
|
|
1074
|
+
try {
|
|
1075
|
+
const snapshot = packageRow.snapshot;
|
|
1076
|
+
const skillRoots = report.components
|
|
1077
|
+
.filter((component) => component.type === "skill")
|
|
1078
|
+
.map((component) => asString(component.metadata.root))
|
|
1079
|
+
.filter((root) => Boolean(root));
|
|
1080
|
+
const packageFiles = Object.fromEntries(snapshot
|
|
1081
|
+
.filter((file) => skillRoots.some((root) => file.path === `${root}/SKILL.md` || file.path.startsWith(`${root}/`)))
|
|
1082
|
+
.map((file) => [file.path, Buffer.from(file.content, "base64").toString("utf8")]));
|
|
1083
|
+
const importedSkills = report.components.some((component) => component.type === "skill")
|
|
1084
|
+
? await skills.importPackageFiles(orgId, packageFiles, {
|
|
1085
|
+
onConflict: resolvedSkillConflictStrategy === "keep" ? "skip" : resolvedSkillConflictStrategy,
|
|
1086
|
+
})
|
|
1087
|
+
: [];
|
|
1088
|
+
for (const result of importedSkills) {
|
|
1089
|
+
if (result.action !== "skipped") {
|
|
1090
|
+
importedSkillIds.push(result.skill.id);
|
|
1091
|
+
const currentMetadata = asRecord(result.skill.metadata);
|
|
1092
|
+
await db.update(organizationSkills).set({
|
|
1093
|
+
metadata: {
|
|
1094
|
+
...currentMetadata,
|
|
1095
|
+
sourceKind: "plugin_managed",
|
|
1096
|
+
pluginManaged: true,
|
|
1097
|
+
pluginEnabled: enabled,
|
|
1098
|
+
pluginDisplayName: asString(asRecord(packageRow.normalizedManifest).displayName) ?? packageRow.name,
|
|
1099
|
+
installedPluginId: installed.id,
|
|
1100
|
+
pluginPackageId: packageRow.id,
|
|
1101
|
+
pluginDigest: packageRow.digest,
|
|
1102
|
+
},
|
|
1103
|
+
updatedAt: new Date(),
|
|
1104
|
+
}).where(and(eq(organizationSkills.orgId, orgId), eq(organizationSkills.id, result.skill.id)));
|
|
1105
|
+
}
|
|
1106
|
+
await db.insert(pluginComponentLinks).values({
|
|
1107
|
+
orgId,
|
|
1108
|
+
installedPluginId: installed.id,
|
|
1109
|
+
componentType: "skill",
|
|
1110
|
+
componentKey: result.originalKey,
|
|
1111
|
+
displayName: result.skill.name,
|
|
1112
|
+
status: result.action === "skipped" ? "disabled" : enabled ? "ready" : "disabled",
|
|
1113
|
+
targetId: result.action === "skipped" ? null : result.skill.id,
|
|
1114
|
+
metadata: {
|
|
1115
|
+
skillKey: result.skill.key,
|
|
1116
|
+
originalSlug: result.originalSlug,
|
|
1117
|
+
action: result.action,
|
|
1118
|
+
...(result.action === "skipped" ? { keptExistingSkillId: result.skill.id } : {}),
|
|
1119
|
+
},
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
for (const component of report.components.filter((entry) => entry.type !== "skill")) {
|
|
1123
|
+
await db.insert(pluginComponentLinks).values({
|
|
1124
|
+
orgId,
|
|
1125
|
+
installedPluginId: installed.id,
|
|
1126
|
+
componentType: component.type,
|
|
1127
|
+
componentKey: component.key,
|
|
1128
|
+
displayName: component.name,
|
|
1129
|
+
status: enabled ? component.status : "disabled",
|
|
1130
|
+
metadata: component.metadata,
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
await db.update(installedPlugins).set({
|
|
1134
|
+
setupState: supported.length === 0 ? "blocked" : setupRequired ? "setup_required" : "ready",
|
|
1135
|
+
healthState: supported.length === 0 ? "unavailable" : report.warnings.length > 0 ? "degraded" : "healthy",
|
|
1136
|
+
lastOperation: { kind: "install", result: "succeeded", at: new Date().toISOString() },
|
|
1137
|
+
updatedAt: new Date(),
|
|
1138
|
+
}).where(eq(installedPlugins.id, installed.id));
|
|
1139
|
+
await db.update(pluginImportReports).set({ status: "accepted", updatedAt: new Date() })
|
|
1140
|
+
.where(eq(pluginImportReports.id, reportRow.id));
|
|
1141
|
+
}
|
|
1142
|
+
catch (error) {
|
|
1143
|
+
for (const skillId of importedSkillIds) {
|
|
1144
|
+
await skills.deletePluginManagedSkill(orgId, skillId, installed.id).catch(() => null);
|
|
1145
|
+
}
|
|
1146
|
+
await db.delete(installedPlugins).where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, installed.id)));
|
|
1147
|
+
throw error;
|
|
1148
|
+
}
|
|
1149
|
+
return (await getInstalled(orgId, installed.id));
|
|
1150
|
+
}
|
|
1151
|
+
async function reconcileMcpState(orgId) {
|
|
1152
|
+
const links = await db.select().from(pluginComponentLinks)
|
|
1153
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.componentType, "mcp")));
|
|
1154
|
+
const targetIds = links.map((link) => link.targetId).filter((id) => Boolean(id));
|
|
1155
|
+
const connections = targetIds.length === 0 ? [] : await db.select().from(mcpConnectionRows)
|
|
1156
|
+
.where(and(eq(mcpConnectionRows.orgId, orgId), inArray(mcpConnectionRows.id, targetIds)));
|
|
1157
|
+
const installations = await db.select().from(installedPlugins)
|
|
1158
|
+
.where(and(eq(installedPlugins.orgId, orgId), ne(installedPlugins.lifecycleState, "uninstalled")));
|
|
1159
|
+
for (const link of links) {
|
|
1160
|
+
const installation = installations.find((entry) => entry.id === link.installedPluginId);
|
|
1161
|
+
if (!installation)
|
|
1162
|
+
continue;
|
|
1163
|
+
if (asRecord(link.metadata).retired === true) {
|
|
1164
|
+
if (link.status !== "disabled") {
|
|
1165
|
+
await db.update(pluginComponentLinks).set({ status: "disabled", updatedAt: new Date() })
|
|
1166
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.id, link.id)));
|
|
1167
|
+
}
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const connection = connections.find((entry) => entry.id === link.targetId);
|
|
1171
|
+
const nextStatus = !installation.enabled
|
|
1172
|
+
? "disabled"
|
|
1173
|
+
: connection?.status === "active" && connection.enabled
|
|
1174
|
+
? "ready"
|
|
1175
|
+
: "setup_required";
|
|
1176
|
+
if (link.status !== nextStatus) {
|
|
1177
|
+
await db.update(pluginComponentLinks).set({ status: nextStatus, updatedAt: new Date() })
|
|
1178
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.id, link.id)));
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
for (const installation of installations) {
|
|
1182
|
+
const ownedLinks = links.filter((link) => (link.installedPluginId === installation.id
|
|
1183
|
+
&& asRecord(link.metadata).retired !== true));
|
|
1184
|
+
const ownedConnections = ownedLinks
|
|
1185
|
+
.map((link) => connections.find((entry) => entry.id === link.targetId))
|
|
1186
|
+
.filter((entry) => Boolean(entry));
|
|
1187
|
+
const hasMcpError = ownedConnections.some((entry) => ["error", "needs_reauth", "revoked"].includes(entry.status));
|
|
1188
|
+
const hasMcpSetup = ownedLinks.some((link) => !link.targetId)
|
|
1189
|
+
|| ownedConnections.some((entry) => entry.status !== "active" || !entry.enabled);
|
|
1190
|
+
if (!installation.enabled)
|
|
1191
|
+
continue;
|
|
1192
|
+
const setupState = hasMcpSetup ? "setup_required" : installation.setupState === "setup_required" ? "ready" : installation.setupState;
|
|
1193
|
+
const healthState = hasMcpError ? "degraded" : installation.healthState === "degraded" && ownedConnections.length > 0 ? "healthy" : installation.healthState;
|
|
1194
|
+
if (setupState !== installation.setupState || healthState !== installation.healthState) {
|
|
1195
|
+
await db.update(installedPlugins).set({ setupState, healthState, updatedAt: new Date() })
|
|
1196
|
+
.where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, installation.id)));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
async function listInstalled(orgId) {
|
|
1201
|
+
await reconcileMcpState(orgId);
|
|
1202
|
+
const rows = await db.select({
|
|
1203
|
+
installed: installedPlugins,
|
|
1204
|
+
pkg: pluginPackages,
|
|
1205
|
+
sourceLabel: pluginSources.label,
|
|
1206
|
+
}).from(installedPlugins)
|
|
1207
|
+
.innerJoin(pluginPackages, eq(installedPlugins.packageId, pluginPackages.id))
|
|
1208
|
+
.leftJoin(pluginSources, eq(installedPlugins.sourceId, pluginSources.id))
|
|
1209
|
+
.where(and(eq(installedPlugins.orgId, orgId), ne(installedPlugins.lifecycleState, "uninstalled")))
|
|
1210
|
+
.orderBy(asc(installedPlugins.packageName));
|
|
1211
|
+
const ids = rows.map((row) => row.installed.id);
|
|
1212
|
+
const links = ids.length === 0 ? [] : await db.select().from(pluginComponentLinks)
|
|
1213
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), inArray(pluginComponentLinks.installedPluginId, ids)))
|
|
1214
|
+
.orderBy(asc(pluginComponentLinks.componentType), asc(pluginComponentLinks.displayName));
|
|
1215
|
+
const pendingPackageIds = rows
|
|
1216
|
+
.map(({ installed }) => asString(asRecord(installed.lastOperation).pendingPackageId))
|
|
1217
|
+
.filter((id) => Boolean(id));
|
|
1218
|
+
const pendingPackages = pendingPackageIds.length === 0 ? [] : await db.select().from(pluginPackages)
|
|
1219
|
+
.where(inArray(pluginPackages.id, pendingPackageIds));
|
|
1220
|
+
return rows.map(({ installed, pkg, sourceLabel }) => {
|
|
1221
|
+
const normalized = asRecord(pkg.normalizedManifest);
|
|
1222
|
+
const pendingPackageId = asString(asRecord(installed.lastOperation).pendingPackageId);
|
|
1223
|
+
const pendingPackage = pendingPackageId
|
|
1224
|
+
? pendingPackages.find((candidate) => candidate.id === pendingPackageId) ?? null
|
|
1225
|
+
: null;
|
|
1226
|
+
const pendingNormalized = pendingPackage ? asRecord(pendingPackage.normalizedManifest) : null;
|
|
1227
|
+
return {
|
|
1228
|
+
id: installed.id,
|
|
1229
|
+
orgId: installed.orgId,
|
|
1230
|
+
packageId: pkg.id,
|
|
1231
|
+
previousPackageId: installed.previousPackageId,
|
|
1232
|
+
name: pkg.name,
|
|
1233
|
+
displayName: asString(normalized.displayName) ?? pkg.name,
|
|
1234
|
+
description: asString(normalized.shortDescription) ?? asString(normalized.description),
|
|
1235
|
+
version: pkg.version,
|
|
1236
|
+
publisher: asString(normalized.publisher),
|
|
1237
|
+
sourceLabel: sourceLabel ?? "Imported package",
|
|
1238
|
+
digest: pkg.digest,
|
|
1239
|
+
enabled: installed.enabled,
|
|
1240
|
+
lifecycleState: installed.lifecycleState,
|
|
1241
|
+
setupState: installed.setupState,
|
|
1242
|
+
healthState: installed.healthState,
|
|
1243
|
+
updateState: installed.updateState,
|
|
1244
|
+
components: links.filter((link) => (link.installedPluginId === installed.id
|
|
1245
|
+
&& asRecord(link.metadata).retired !== true)).map((link) => ({
|
|
1246
|
+
id: link.id,
|
|
1247
|
+
type: link.componentType,
|
|
1248
|
+
key: link.componentKey,
|
|
1249
|
+
displayName: link.displayName,
|
|
1250
|
+
status: link.status,
|
|
1251
|
+
targetId: link.targetId,
|
|
1252
|
+
metadata: link.metadata,
|
|
1253
|
+
})),
|
|
1254
|
+
manifest: pkg.rawManifest,
|
|
1255
|
+
pendingUpdate: pendingPackage && pendingNormalized ? {
|
|
1256
|
+
packageId: pendingPackage.id,
|
|
1257
|
+
version: pendingPackage.version,
|
|
1258
|
+
digest: pendingPackage.digest,
|
|
1259
|
+
displayName: asString(pendingNormalized.displayName) ?? pendingPackage.name,
|
|
1260
|
+
sourceLabel: asString(asRecord(installed.lastOperation).pendingSourceLabel) ?? "App Builder",
|
|
1261
|
+
} : null,
|
|
1262
|
+
installedAt: installed.installedAt.toISOString(),
|
|
1263
|
+
updatedAt: installed.updatedAt.toISOString(),
|
|
1264
|
+
};
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
async function getInstalled(orgId, pluginId) {
|
|
1268
|
+
return (await listInstalled(orgId)).find((plugin) => plugin.id === pluginId) ?? null;
|
|
1269
|
+
}
|
|
1270
|
+
async function ensureLocalAppPlugins(orgId) {
|
|
1271
|
+
const apps = await db.select().from(appBuilderApps).where(eq(appBuilderApps.orgId, orgId));
|
|
1272
|
+
const activeProjections = await db.select({
|
|
1273
|
+
link: pluginComponentLinks,
|
|
1274
|
+
installed: installedPlugins,
|
|
1275
|
+
packageDigest: pluginPackages.digest,
|
|
1276
|
+
}).from(pluginComponentLinks)
|
|
1277
|
+
.innerJoin(installedPlugins, eq(pluginComponentLinks.installedPluginId, installedPlugins.id))
|
|
1278
|
+
.innerJoin(pluginPackages, eq(installedPlugins.packageId, pluginPackages.id))
|
|
1279
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.componentType, "app"), ne(installedPlugins.lifecycleState, "uninstalled")));
|
|
1280
|
+
for (const app of apps) {
|
|
1281
|
+
if (app.buildStatus !== "ready")
|
|
1282
|
+
continue;
|
|
1283
|
+
const packageName = `local-app-${app.id}`;
|
|
1284
|
+
const revision = {
|
|
1285
|
+
appId: app.id,
|
|
1286
|
+
name: app.name,
|
|
1287
|
+
sourceRoot: app.sourceRoot,
|
|
1288
|
+
buildStatus: app.buildStatus,
|
|
1289
|
+
latestBuildRunId: app.latestBuildRunId,
|
|
1290
|
+
latestVerificationRunId: app.latestVerificationRunId,
|
|
1291
|
+
desktopInstallationId: app.desktopInstallationId,
|
|
1292
|
+
appPublicId: app.appPublicId,
|
|
1293
|
+
localBindingId: app.localBindingId,
|
|
1294
|
+
updatedAt: app.updatedAt.toISOString(),
|
|
1295
|
+
};
|
|
1296
|
+
const revisionDigest = createHash("sha256").update(JSON.stringify(revision)).digest("hex");
|
|
1297
|
+
const version = `0.0.0-local.${revisionDigest.slice(0, 12)}`;
|
|
1298
|
+
const rawManifest = {
|
|
1299
|
+
name: packageName,
|
|
1300
|
+
version,
|
|
1301
|
+
description: "Rudder Local App",
|
|
1302
|
+
interface: { displayName: app.name, developerName: "Rudder", category: "Local App" },
|
|
1303
|
+
rudder: { kind: "local_app", appId: app.id, revisionDigest },
|
|
1304
|
+
};
|
|
1305
|
+
const manifestContent = JSON.stringify(rawManifest);
|
|
1306
|
+
const digest = createHash("sha256").update(`rudder-local-app\0${orgId}\0${revisionDigest}\0${manifestContent}`).digest("hex");
|
|
1307
|
+
const status = app.buildStatus === "ready" ? "ready" : "setup_required";
|
|
1308
|
+
const componentMetadata = {
|
|
1309
|
+
projectionKind: "rudder_local_app",
|
|
1310
|
+
revisionDigest,
|
|
1311
|
+
appId: app.id,
|
|
1312
|
+
appKey: app.buildStatus === "ready" ? `managed:${app.id}` : null,
|
|
1313
|
+
buildStatus: app.buildStatus,
|
|
1314
|
+
sourceRoot: app.sourceRoot,
|
|
1315
|
+
desktopInstallationId: app.desktopInstallationId,
|
|
1316
|
+
appPublicId: app.appPublicId,
|
|
1317
|
+
localBindingId: app.localBindingId,
|
|
1318
|
+
};
|
|
1319
|
+
const compatibility = {
|
|
1320
|
+
manifest: rawManifest,
|
|
1321
|
+
components: [{
|
|
1322
|
+
key: `app:${app.id}`,
|
|
1323
|
+
type: "app",
|
|
1324
|
+
name: app.name,
|
|
1325
|
+
path: null,
|
|
1326
|
+
status,
|
|
1327
|
+
required: true,
|
|
1328
|
+
detail: "Rudder Local App managed by App Builder and Desktop.",
|
|
1329
|
+
metadata: componentMetadata,
|
|
1330
|
+
}],
|
|
1331
|
+
warnings: [],
|
|
1332
|
+
errors: [],
|
|
1333
|
+
limits: { fileCount: 1, totalBytes: Buffer.byteLength(manifestContent) },
|
|
1334
|
+
operation: "install",
|
|
1335
|
+
installedPluginId: null,
|
|
1336
|
+
capabilityDiff: null,
|
|
1337
|
+
skillConflicts: [],
|
|
1338
|
+
};
|
|
1339
|
+
const existingProjection = activeProjections.find((entry) => (entry.link.targetId === app.id
|
|
1340
|
+
&& asString(asRecord(entry.link.metadata).projectionKind) === "rudder_local_app"));
|
|
1341
|
+
if (existingProjection?.packageDigest === digest) {
|
|
1342
|
+
await db.update(pluginComponentLinks).set({
|
|
1343
|
+
displayName: app.name,
|
|
1344
|
+
status: existingProjection.installed.enabled ? status : "disabled",
|
|
1345
|
+
metadata: componentMetadata,
|
|
1346
|
+
updatedAt: new Date(),
|
|
1347
|
+
}).where(eq(pluginComponentLinks.id, existingProjection.link.id));
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
if (asString(asRecord(existingProjection?.installed.lastOperation).pendingDigest) === digest)
|
|
1351
|
+
continue;
|
|
1352
|
+
const [source] = await db.insert(pluginSources).values({
|
|
1353
|
+
orgId,
|
|
1354
|
+
sourceType: "package",
|
|
1355
|
+
label: "App Builder",
|
|
1356
|
+
metadata: { kind: "rudder_local_app", appId: app.id, sourceRoot: app.sourceRoot, revisionDigest },
|
|
1357
|
+
}).returning();
|
|
1358
|
+
const existingPackage = await db.select().from(pluginPackages)
|
|
1359
|
+
.where(eq(pluginPackages.digest, digest))
|
|
1360
|
+
.then((rows) => rows[0] ?? null);
|
|
1361
|
+
const pkg = existingPackage ?? await db.insert(pluginPackages).values({
|
|
1362
|
+
sourceId: source.id,
|
|
1363
|
+
name: packageName,
|
|
1364
|
+
version,
|
|
1365
|
+
digest,
|
|
1366
|
+
rawManifest,
|
|
1367
|
+
normalizedManifest: {
|
|
1368
|
+
name: packageName,
|
|
1369
|
+
version,
|
|
1370
|
+
description: "Rudder Local App",
|
|
1371
|
+
displayName: app.name,
|
|
1372
|
+
shortDescription: "Private interactive capability built in Rudder.",
|
|
1373
|
+
publisher: "Rudder",
|
|
1374
|
+
category: "Local App",
|
|
1375
|
+
capabilities: ["app"],
|
|
1376
|
+
},
|
|
1377
|
+
snapshot: [{ path: ".codex-plugin/plugin.json", content: Buffer.from(manifestContent).toString("base64"), encoding: "base64" }],
|
|
1378
|
+
compatibility: compatibility,
|
|
1379
|
+
}).returning().then((rows) => rows[0]);
|
|
1380
|
+
if (existingProjection) {
|
|
1381
|
+
await db.update(installedPlugins).set({
|
|
1382
|
+
updateState: "review_required",
|
|
1383
|
+
lastOperation: {
|
|
1384
|
+
kind: "review_local_app_revision",
|
|
1385
|
+
result: "review_required",
|
|
1386
|
+
fromPackageId: existingProjection.installed.packageId,
|
|
1387
|
+
pendingPackageId: pkg.id,
|
|
1388
|
+
pendingSourceId: source.id,
|
|
1389
|
+
pendingSourceLabel: "App Builder",
|
|
1390
|
+
pendingDigest: digest,
|
|
1391
|
+
pendingRevisionDigest: revisionDigest,
|
|
1392
|
+
at: new Date().toISOString(),
|
|
1393
|
+
},
|
|
1394
|
+
updatedAt: new Date(),
|
|
1395
|
+
}).where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, existingProjection.installed.id)));
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
await db.transaction(async (tx) => {
|
|
1399
|
+
const raced = await tx.select({ id: pluginComponentLinks.id }).from(pluginComponentLinks)
|
|
1400
|
+
.innerJoin(installedPlugins, eq(pluginComponentLinks.installedPluginId, installedPlugins.id))
|
|
1401
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.componentType, "app"), eq(pluginComponentLinks.targetId, app.id), ne(installedPlugins.lifecycleState, "uninstalled"))).then((rows) => rows[0] ?? null);
|
|
1402
|
+
if (raced)
|
|
1403
|
+
return;
|
|
1404
|
+
const [installed] = await tx.insert(installedPlugins).values({
|
|
1405
|
+
orgId,
|
|
1406
|
+
packageId: pkg.id,
|
|
1407
|
+
sourceId: source.id,
|
|
1408
|
+
packageName: packageIdentityKey("package", "Rudder", packageName),
|
|
1409
|
+
enabled: true,
|
|
1410
|
+
setupState: app.buildStatus === "ready" ? "ready" : "setup_required",
|
|
1411
|
+
healthState: app.buildStatus === "ready" ? "healthy" : "unknown",
|
|
1412
|
+
lastOperation: { kind: "project_local_app", result: "succeeded", revisionDigest, at: new Date().toISOString() },
|
|
1413
|
+
}).returning();
|
|
1414
|
+
await tx.insert(pluginComponentLinks).values({
|
|
1415
|
+
orgId,
|
|
1416
|
+
installedPluginId: installed.id,
|
|
1417
|
+
componentType: "app",
|
|
1418
|
+
componentKey: `app:${app.id}`,
|
|
1419
|
+
displayName: app.name,
|
|
1420
|
+
status,
|
|
1421
|
+
targetId: app.id,
|
|
1422
|
+
metadata: componentMetadata,
|
|
1423
|
+
});
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
async function syncAllLocalApps() {
|
|
1428
|
+
const appOrganizations = await db.selectDistinct({ orgId: appBuilderApps.orgId })
|
|
1429
|
+
.from(appBuilderApps);
|
|
1430
|
+
for (const { orgId } of appOrganizations) {
|
|
1431
|
+
await ensureLocalAppPlugins(orgId);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
async function listLocalApps(orgId) {
|
|
1435
|
+
void orgId;
|
|
1436
|
+
return [];
|
|
1437
|
+
}
|
|
1438
|
+
async function directory(orgId) {
|
|
1439
|
+
await ensureLocalAppPlugins(orgId);
|
|
1440
|
+
const discover = await listDiscover(orgId);
|
|
1441
|
+
return {
|
|
1442
|
+
installed: await listInstalled(orgId),
|
|
1443
|
+
localApps: await listLocalApps(orgId),
|
|
1444
|
+
discover,
|
|
1445
|
+
discoverSource: discover.length > 0 ? "configured" : "none",
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
async function applyPendingLocalAppUpdate(orgId, pluginId) {
|
|
1449
|
+
const row = await db.select().from(installedPlugins)
|
|
1450
|
+
.where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, pluginId), ne(installedPlugins.lifecycleState, "uninstalled")))
|
|
1451
|
+
.then((rows) => rows[0] ?? null);
|
|
1452
|
+
if (!row)
|
|
1453
|
+
throw notFound("Installed Plugin not found");
|
|
1454
|
+
const operation = asRecord(row.lastOperation);
|
|
1455
|
+
const packageId = asString(operation.pendingPackageId);
|
|
1456
|
+
if (!["review_required", "failed"].includes(row.updateState) || !packageId) {
|
|
1457
|
+
throw unprocessable("This Local App Plugin has no reviewed update to apply");
|
|
1458
|
+
}
|
|
1459
|
+
const pkg = await db.select().from(pluginPackages).where(eq(pluginPackages.id, packageId))
|
|
1460
|
+
.then((rows) => rows[0] ?? null);
|
|
1461
|
+
if (!pkg || asString(asRecord(asRecord(pkg.rawManifest).rudder).kind) !== "local_app") {
|
|
1462
|
+
throw unprocessable("Pending update is not a Rudder Local App revision");
|
|
1463
|
+
}
|
|
1464
|
+
const sourceId = asString(operation.pendingSourceId) ?? row.sourceId;
|
|
1465
|
+
await db.update(installedPlugins).set({ updateState: "applying", updatedAt: new Date() })
|
|
1466
|
+
.where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, pluginId)));
|
|
1467
|
+
try {
|
|
1468
|
+
return await applyPackageRevision(orgId, pluginId, pkg, sourceId, "update");
|
|
1469
|
+
}
|
|
1470
|
+
catch (error) {
|
|
1471
|
+
await db.update(installedPlugins).set({
|
|
1472
|
+
updateState: "failed",
|
|
1473
|
+
lastOperation: {
|
|
1474
|
+
...operation,
|
|
1475
|
+
result: "failed",
|
|
1476
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1477
|
+
failedAt: new Date().toISOString(),
|
|
1478
|
+
},
|
|
1479
|
+
updatedAt: new Date(),
|
|
1480
|
+
}).where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, pluginId)));
|
|
1481
|
+
throw error;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
async function setEnabled(orgId, pluginId, enabled) {
|
|
1485
|
+
const plugin = await getInstalled(orgId, pluginId);
|
|
1486
|
+
if (!plugin)
|
|
1487
|
+
throw notFound("Installed Plugin not found");
|
|
1488
|
+
const installedRow = await db.select({ lastOperation: installedPlugins.lastOperation }).from(installedPlugins)
|
|
1489
|
+
.where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, pluginId)))
|
|
1490
|
+
.then((rows) => rows[0] ?? null);
|
|
1491
|
+
const skillLinks = plugin.components.filter((component) => component.type === "skill" && component.targetId);
|
|
1492
|
+
const skillIds = skillLinks.map((component) => component.targetId);
|
|
1493
|
+
const skillRows = skillIds.length === 0 ? [] : await db.select().from(organizationSkills)
|
|
1494
|
+
.where(and(eq(organizationSkills.orgId, orgId), inArray(organizationSkills.id, skillIds)));
|
|
1495
|
+
if (!enabled) {
|
|
1496
|
+
const skillKeys = skillRows.map((skill) => skill.key);
|
|
1497
|
+
const selected = skillKeys.length === 0 ? [] : await db.select().from(agentEnabledSkills)
|
|
1498
|
+
.where(and(eq(agentEnabledSkills.orgId, orgId), inArray(agentEnabledSkills.skillKey, skillKeys)));
|
|
1499
|
+
for (const link of skillLinks) {
|
|
1500
|
+
const skill = skillRows.find((row) => row.id === link.targetId);
|
|
1501
|
+
if (!skill)
|
|
1502
|
+
continue;
|
|
1503
|
+
const enabledAgentIds = selected.filter((row) => row.skillKey === skill.key).map((row) => row.agentId);
|
|
1504
|
+
await db.update(pluginComponentLinks).set({
|
|
1505
|
+
status: "disabled",
|
|
1506
|
+
metadata: { ...link.metadata, enabledAgentIds },
|
|
1507
|
+
updatedAt: new Date(),
|
|
1508
|
+
}).where(eq(pluginComponentLinks.id, link.id));
|
|
1509
|
+
}
|
|
1510
|
+
await enabledSkills.removeSkillKeys(orgId, skillKeys);
|
|
1511
|
+
}
|
|
1512
|
+
else {
|
|
1513
|
+
for (const link of skillLinks) {
|
|
1514
|
+
const skill = skillRows.find((row) => row.id === link.targetId);
|
|
1515
|
+
if (!skill)
|
|
1516
|
+
continue;
|
|
1517
|
+
const agentIds = Array.isArray(link.metadata.enabledAgentIds)
|
|
1518
|
+
? link.metadata.enabledAgentIds.filter((value) => typeof value === "string")
|
|
1519
|
+
: [];
|
|
1520
|
+
for (const agentId of agentIds)
|
|
1521
|
+
await enabledSkills.addMissingKeys(orgId, agentId, [skill.key]);
|
|
1522
|
+
await db.update(pluginComponentLinks).set({ status: "ready", updatedAt: new Date() })
|
|
1523
|
+
.where(eq(pluginComponentLinks.id, link.id));
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
for (const skill of skillRows) {
|
|
1527
|
+
await db.update(organizationSkills).set({
|
|
1528
|
+
metadata: { ...asRecord(skill.metadata), pluginEnabled: enabled },
|
|
1529
|
+
updatedAt: new Date(),
|
|
1530
|
+
}).where(eq(organizationSkills.id, skill.id));
|
|
1531
|
+
}
|
|
1532
|
+
if (!enabled) {
|
|
1533
|
+
await db.update(pluginComponentLinks).set({ status: "disabled", updatedAt: new Date() })
|
|
1534
|
+
.where(and(eq(pluginComponentLinks.installedPluginId, pluginId), eq(pluginComponentLinks.componentType, "mcp")));
|
|
1535
|
+
}
|
|
1536
|
+
await db.update(installedPlugins).set({
|
|
1537
|
+
enabled,
|
|
1538
|
+
lastOperation: {
|
|
1539
|
+
...asRecord(installedRow?.lastOperation),
|
|
1540
|
+
kind: enabled ? "enable" : "disable",
|
|
1541
|
+
result: "succeeded",
|
|
1542
|
+
at: new Date().toISOString(),
|
|
1543
|
+
},
|
|
1544
|
+
updatedAt: new Date(),
|
|
1545
|
+
}).where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, pluginId)));
|
|
1546
|
+
await reconcileMcpState(orgId);
|
|
1547
|
+
return (await getInstalled(orgId, pluginId));
|
|
1548
|
+
}
|
|
1549
|
+
async function configureSkills(orgId, pluginId, agentIds) {
|
|
1550
|
+
const plugin = await getInstalled(orgId, pluginId);
|
|
1551
|
+
if (!plugin)
|
|
1552
|
+
throw notFound("Installed Plugin not found");
|
|
1553
|
+
if (!plugin.enabled)
|
|
1554
|
+
throw unprocessable("Enable the Plugin before assigning its Skills");
|
|
1555
|
+
const validAgents = agentIds.length === 0 ? [] : await db.select({ id: agents.id }).from(agents)
|
|
1556
|
+
.where(and(eq(agents.orgId, orgId), inArray(agents.id, agentIds)));
|
|
1557
|
+
if (validAgents.length !== new Set(agentIds).size)
|
|
1558
|
+
throw unprocessable("One or more Agents do not belong to this Organization");
|
|
1559
|
+
const skillIds = plugin.components.filter((component) => component.type === "skill" && component.targetId).map((component) => component.targetId);
|
|
1560
|
+
const skillRows = skillIds.length === 0 ? [] : await db.select().from(organizationSkills)
|
|
1561
|
+
.where(and(eq(organizationSkills.orgId, orgId), inArray(organizationSkills.id, skillIds)));
|
|
1562
|
+
await enabledSkills.removeSkillKeys(orgId, skillRows.map((skill) => skill.key));
|
|
1563
|
+
for (const agentId of agentIds)
|
|
1564
|
+
await enabledSkills.addMissingKeys(orgId, agentId, skillRows.map((skill) => skill.key));
|
|
1565
|
+
for (const component of plugin.components.filter((entry) => entry.type === "skill")) {
|
|
1566
|
+
await db.update(pluginComponentLinks).set({
|
|
1567
|
+
metadata: { ...component.metadata, enabledAgentIds: agentIds },
|
|
1568
|
+
updatedAt: new Date(),
|
|
1569
|
+
}).where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.id, component.id)));
|
|
1570
|
+
}
|
|
1571
|
+
return getInstalled(orgId, pluginId);
|
|
1572
|
+
}
|
|
1573
|
+
async function customizeSkill(orgId, pluginId, componentId) {
|
|
1574
|
+
const plugin = await getInstalled(orgId, pluginId);
|
|
1575
|
+
if (!plugin)
|
|
1576
|
+
throw notFound("Installed Plugin not found");
|
|
1577
|
+
const component = plugin.components.find((entry) => entry.id === componentId && entry.type === "skill");
|
|
1578
|
+
if (!component?.targetId)
|
|
1579
|
+
throw notFound("Plugin Skill component not found");
|
|
1580
|
+
const source = await db.select().from(organizationSkills)
|
|
1581
|
+
.where(and(eq(organizationSkills.orgId, orgId), eq(organizationSkills.id, component.targetId)))
|
|
1582
|
+
.then((rows) => rows[0] ?? null);
|
|
1583
|
+
if (!source)
|
|
1584
|
+
throw notFound("Plugin-managed Skill not found");
|
|
1585
|
+
const fork = await skills.createLocalSkill(orgId, {
|
|
1586
|
+
name: `${source.name} Custom`,
|
|
1587
|
+
slug: `${source.slug}-custom-${Date.now().toString(36)}`,
|
|
1588
|
+
description: source.description,
|
|
1589
|
+
markdown: source.markdown,
|
|
1590
|
+
});
|
|
1591
|
+
await db.update(organizationSkills).set({
|
|
1592
|
+
metadata: {
|
|
1593
|
+
...asRecord(fork.metadata),
|
|
1594
|
+
sourceKind: "managed_local",
|
|
1595
|
+
forkedFromPluginId: plugin.id,
|
|
1596
|
+
forkedFromPackageId: plugin.packageId,
|
|
1597
|
+
forkedFromSkillId: source.id,
|
|
1598
|
+
forkedFromDigest: plugin.digest,
|
|
1599
|
+
},
|
|
1600
|
+
updatedAt: new Date(),
|
|
1601
|
+
}).where(and(eq(organizationSkills.orgId, orgId), eq(organizationSkills.id, fork.id)));
|
|
1602
|
+
return db.select().from(organizationSkills)
|
|
1603
|
+
.where(and(eq(organizationSkills.orgId, orgId), eq(organizationSkills.id, fork.id)))
|
|
1604
|
+
.then((rows) => rows[0]);
|
|
1605
|
+
}
|
|
1606
|
+
async function configureMcp(orgId, pluginId, componentId, actor) {
|
|
1607
|
+
const plugin = await getInstalled(orgId, pluginId);
|
|
1608
|
+
if (!plugin)
|
|
1609
|
+
throw notFound("Installed Plugin not found");
|
|
1610
|
+
const component = plugin.components.find((entry) => entry.id === componentId && entry.type === "mcp");
|
|
1611
|
+
if (!component)
|
|
1612
|
+
throw notFound("Plugin MCP component not found");
|
|
1613
|
+
if (component.targetId)
|
|
1614
|
+
return component;
|
|
1615
|
+
const definition = asRecord(component.metadata.definition);
|
|
1616
|
+
const transport = component.metadata.transport === "stdio" ? "stdio" : "streamable_http";
|
|
1617
|
+
const connectionName = safeName(`${plugin.name}-${asString(component.metadata.name) ?? component.displayName}`);
|
|
1618
|
+
const safeConfig = transport === "stdio"
|
|
1619
|
+
? {
|
|
1620
|
+
command: asString(definition.command),
|
|
1621
|
+
...(Array.isArray(definition.args) ? { args: definition.args.filter((value) => typeof value === "string") } : {}),
|
|
1622
|
+
...(asString(definition.cwd) ? { cwd: asString(definition.cwd) } : {}),
|
|
1623
|
+
...(definition.env && typeof definition.env === "object" ? {
|
|
1624
|
+
staticEnv: Object.fromEntries(Object.entries(asRecord(definition.env)).filter(([, value]) => typeof value === "string" && !/^\$\{/.test(value)).map(([key, value]) => [key, value])),
|
|
1625
|
+
forwardedEnv: Object.values(asRecord(definition.env)).filter((value) => typeof value === "string" && /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(value)).map((value) => value.slice(2, -1)),
|
|
1626
|
+
} : {}),
|
|
1627
|
+
}
|
|
1628
|
+
: { url: asString(definition.url) };
|
|
1629
|
+
const connection = await mcpConnections.create(orgId, {
|
|
1630
|
+
name: connectionName,
|
|
1631
|
+
displayName: component.displayName,
|
|
1632
|
+
provider: "custom",
|
|
1633
|
+
scope: "organization",
|
|
1634
|
+
ownerAgentId: null,
|
|
1635
|
+
transport,
|
|
1636
|
+
accessMode: "provider_default",
|
|
1637
|
+
safeConfig,
|
|
1638
|
+
startupTimeoutMs: 10_000,
|
|
1639
|
+
toolTimeoutMs: 60_000,
|
|
1640
|
+
enabled: false,
|
|
1641
|
+
required: false,
|
|
1642
|
+
}, actor);
|
|
1643
|
+
await db.update(pluginComponentLinks).set({ targetId: connection.id, updatedAt: new Date() })
|
|
1644
|
+
.where(and(eq(pluginComponentLinks.orgId, orgId), eq(pluginComponentLinks.id, componentId)));
|
|
1645
|
+
return (await getInstalled(orgId, pluginId)).components.find((entry) => entry.id === componentId);
|
|
1646
|
+
}
|
|
1647
|
+
async function listMcpUiResources(orgId, pluginId, componentId) {
|
|
1648
|
+
const plugin = await getInstalled(orgId, pluginId);
|
|
1649
|
+
if (!plugin?.enabled)
|
|
1650
|
+
throw unprocessable("Enable the Plugin before opening MCP UI resources");
|
|
1651
|
+
const component = plugin.components.find((entry) => entry.id === componentId && entry.type === "mcp");
|
|
1652
|
+
if (!component?.targetId || component.status !== "ready") {
|
|
1653
|
+
throw unprocessable("Complete Managed MCP setup before opening UI resources");
|
|
1654
|
+
}
|
|
1655
|
+
const client = await mcpConnections.openRuntimeClient(orgId, component.targetId);
|
|
1656
|
+
try {
|
|
1657
|
+
return (await client.listResources())
|
|
1658
|
+
.filter((resource) => resource.mimeType?.toLowerCase().startsWith("text/html"))
|
|
1659
|
+
.map((resource) => ({
|
|
1660
|
+
uri: resource.uri,
|
|
1661
|
+
name: resource.title ?? resource.name ?? resource.uri,
|
|
1662
|
+
description: resource.description ?? null,
|
|
1663
|
+
mimeType: resource.mimeType,
|
|
1664
|
+
}));
|
|
1665
|
+
}
|
|
1666
|
+
finally {
|
|
1667
|
+
await client.close();
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
async function readMcpUiResource(orgId, pluginId, componentId, uri) {
|
|
1671
|
+
const plugin = await getInstalled(orgId, pluginId);
|
|
1672
|
+
if (!plugin?.enabled)
|
|
1673
|
+
throw unprocessable("Enable the Plugin before opening MCP UI resources");
|
|
1674
|
+
const component = plugin.components.find((entry) => entry.id === componentId && entry.type === "mcp");
|
|
1675
|
+
if (!component?.targetId || component.status !== "ready") {
|
|
1676
|
+
throw unprocessable("Complete Managed MCP setup before opening UI resources");
|
|
1677
|
+
}
|
|
1678
|
+
const client = await mcpConnections.openRuntimeClient(orgId, component.targetId);
|
|
1679
|
+
try {
|
|
1680
|
+
const resource = (await client.listResources())
|
|
1681
|
+
.filter((entry) => entry.mimeType?.toLowerCase().startsWith("text/html"))
|
|
1682
|
+
.map((entry) => ({
|
|
1683
|
+
uri: entry.uri,
|
|
1684
|
+
name: entry.title ?? entry.name ?? entry.uri,
|
|
1685
|
+
description: entry.description ?? null,
|
|
1686
|
+
mimeType: entry.mimeType,
|
|
1687
|
+
}))
|
|
1688
|
+
.find((entry) => entry.uri === uri);
|
|
1689
|
+
if (!resource)
|
|
1690
|
+
throw notFound("MCP UI resource not found");
|
|
1691
|
+
const result = await client.readResource(uri);
|
|
1692
|
+
const content = result.contents.find((entry) => entry.uri === uri && typeof entry.text === "string");
|
|
1693
|
+
if (!content?.text)
|
|
1694
|
+
throw unprocessable("MCP UI resource did not return HTML text content");
|
|
1695
|
+
if (!(content.mimeType ?? resource.mimeType).toLowerCase().startsWith("text/html")) {
|
|
1696
|
+
throw unprocessable("MCP resource is not a supported HTML UI resource");
|
|
1697
|
+
}
|
|
1698
|
+
if (Buffer.byteLength(content.text, "utf8") > MAX_MCP_UI_HTML_BYTES) {
|
|
1699
|
+
throw unprocessable("MCP HTML UI resource exceeds the 2 MiB display limit");
|
|
1700
|
+
}
|
|
1701
|
+
return { ...resource, html: content.text };
|
|
1702
|
+
}
|
|
1703
|
+
finally {
|
|
1704
|
+
await client.close();
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
async function uninstall(orgId, pluginId) {
|
|
1708
|
+
const plugin = await getInstalled(orgId, pluginId);
|
|
1709
|
+
if (!plugin)
|
|
1710
|
+
throw notFound("Installed Plugin not found");
|
|
1711
|
+
await setEnabled(orgId, pluginId, false);
|
|
1712
|
+
const skillIds = plugin.components.filter((component) => component.type === "skill" && component.targetId).map((component) => component.targetId);
|
|
1713
|
+
for (const skillId of skillIds) {
|
|
1714
|
+
await skills.deletePluginManagedSkill(orgId, skillId, pluginId);
|
|
1715
|
+
}
|
|
1716
|
+
await db.update(installedPlugins).set({
|
|
1717
|
+
lifecycleState: "uninstalled",
|
|
1718
|
+
enabled: false,
|
|
1719
|
+
lastOperation: { kind: "uninstall", result: "succeeded", preservedTargets: plugin.components.filter((component) => component.type !== "skill" && component.targetId).map((component) => component.targetId), at: new Date().toISOString() },
|
|
1720
|
+
updatedAt: new Date(),
|
|
1721
|
+
}).where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, pluginId)));
|
|
1722
|
+
return { id: pluginId, uninstalled: true };
|
|
1723
|
+
}
|
|
1724
|
+
async function rollback(orgId, pluginId) {
|
|
1725
|
+
const row = await db.select().from(installedPlugins)
|
|
1726
|
+
.where(and(eq(installedPlugins.orgId, orgId), eq(installedPlugins.id, pluginId), ne(installedPlugins.lifecycleState, "uninstalled")))
|
|
1727
|
+
.then((rows) => rows[0] ?? null);
|
|
1728
|
+
if (!row)
|
|
1729
|
+
throw notFound("Installed Plugin not found");
|
|
1730
|
+
if (!row.previousPackageId)
|
|
1731
|
+
throw unprocessable("No previous Plugin package is available for rollback");
|
|
1732
|
+
const pkg = await db.select().from(pluginPackages).where(eq(pluginPackages.id, row.previousPackageId))
|
|
1733
|
+
.then((rows) => rows[0] ?? null);
|
|
1734
|
+
if (!pkg)
|
|
1735
|
+
throw notFound("Previous Plugin package snapshot not found");
|
|
1736
|
+
return applyPackageRevision(orgId, pluginId, pkg, row.sourceId, "rollback");
|
|
1737
|
+
}
|
|
1738
|
+
return {
|
|
1739
|
+
applyPendingLocalAppUpdate,
|
|
1740
|
+
configureMarketplace,
|
|
1741
|
+
configureMcp,
|
|
1742
|
+
configureSkills,
|
|
1743
|
+
customizeSkill,
|
|
1744
|
+
directory,
|
|
1745
|
+
getImportReport,
|
|
1746
|
+
getInstalled,
|
|
1747
|
+
inspect,
|
|
1748
|
+
inspectArchive,
|
|
1749
|
+
install,
|
|
1750
|
+
listInstalled,
|
|
1751
|
+
listMcpUiResources,
|
|
1752
|
+
readMcpUiResource,
|
|
1753
|
+
rollback,
|
|
1754
|
+
setEnabled,
|
|
1755
|
+
syncAllLocalApps,
|
|
1756
|
+
syncLocalApps: ensureLocalAppPlugins,
|
|
1757
|
+
uninstall,
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
//# sourceMappingURL=rudder-plugins.js.map
|