@kb-labs/rest-api-app 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.kb/agents/sessions/session-1771531145393-ws3uh6q/turns.json +42 -0
- package/.kb/agents/sessions/session-1771548930486-ui6rtvm/turns.json +33 -0
- package/.kb/agents/sessions/session-1771549691485-nibqroq/turns.json +133 -0
- package/.kb/agents/sessions/session-1771550943182-dkwqdjc/turns.json +130 -0
- package/.kb/agents/sessions/session-1771551564182-cr4bwzn/turns.json +349 -0
- package/.kb/agents/sessions/session-1771552401370-g4ztwd1/turns.json +206 -0
- package/.kb/commit/current/plan.json +248 -0
- package/.kb/commit/current/status.json +472 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.js +215 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/health.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.js +271 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/list.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.js +230 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/logs.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.js +228 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/metrics.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.js +281 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.js +266 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/status.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.js +284 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/commands/workflow-run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/flags-WLdpSZl4.d.ts +77 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.d.ts +2 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.js +441 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.d.ts +196 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.js +441 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/manifest.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.d.ts +19 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.js +48 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/cron-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.d.ts +6 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.js +170 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.d.ts +22 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-logs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.js +43 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/job-steps-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.d.ts +26 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.js +63 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/jobs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/pending-approvals-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.d.ts +16 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.js +58 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/resolve-approval-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/run-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.js +50 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/runs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.d.ts +7 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.js +39 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/stats-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.js +47 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.d.ts +14 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.js +53 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-run-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.js +54 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflow-runs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.js +51 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/rest/workflows-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.d.ts +17 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.js +133 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/logs-channel.js.map +1 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.js +46 -0
- package/.kb/runtime/workspaces/ws_94c2deab-4cf9-49cb-94ac-3958062754a1/ws/progress-channel.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.js +215 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/health.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.js +271 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/list.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.js +230 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/logs.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.js +228 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/metrics.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.js +281 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.js +266 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/status.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.js +284 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/commands/workflow-run.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/flags-WLdpSZl4.d.ts +77 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.d.ts +2 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.js +441 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.d.ts +196 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.js +441 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/manifest.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.d.ts +19 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.js +48 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/cron-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.d.ts +6 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.js +170 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/index.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.d.ts +22 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.js +52 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-logs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.js +43 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/job-steps-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.d.ts +26 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.js +63 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/jobs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/pending-approvals-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.d.ts +16 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.js +58 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/resolve-approval-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.d.ts +9 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/run-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.js +50 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/runs-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.d.ts +7 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.js +39 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/stats-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.d.ts +10 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-detail-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.js +47 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-cancel-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.d.ts +14 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.js +53 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-run-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.d.ts +13 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.js +54 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflow-runs-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.d.ts +12 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.js +51 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/rest/workflows-list-handler.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.d.ts +17 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.js +133 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/logs-channel.js.map +1 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.d.ts +11 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.js +46 -0
- package/.kb/runtime/workspaces/ws_f0f301b8-388d-4dc4-b301-475f2bc95f02/ws/progress-channel.js.map +1 -0
- package/README.md +54 -0
- package/eslint.config.js +23 -0
- package/package.json +72 -0
- package/src/.kb/agents/sessions/session-1773470583796-ximqnfk/kpi-baseline.json +17 -0
- package/src/.kb/memory/sessions/session-1773470583796-ximqnfk/fact-sheet.json +1 -0
- package/src/bootstrap.ts +233 -0
- package/src/diagnostics/header-debug.ts +37 -0
- package/src/events/hub.ts +60 -0
- package/src/events/index.ts +1 -0
- package/src/index.ts +13 -0
- package/src/manifest.ts +21 -0
- package/src/middleware/__tests__/cache.spec.ts +184 -0
- package/src/middleware/__tests__/envelope.spec.ts +268 -0
- package/src/middleware/__tests__/metrics.redis.spec.ts +64 -0
- package/src/middleware/__tests__/rate-limit.spec.ts +283 -0
- package/src/middleware/__tests__/request-id.spec.ts +160 -0
- package/src/middleware/cache.ts +67 -0
- package/src/middleware/envelope.ts +136 -0
- package/src/middleware/error-guard.ts +69 -0
- package/src/middleware/index.ts +37 -0
- package/src/middleware/metrics.ts +810 -0
- package/src/middleware/mock-mode.ts +44 -0
- package/src/middleware/prom-metrics.ts +284 -0
- package/src/middleware/rate-limit-routes.ts +73 -0
- package/src/middleware/rate-limit.ts +72 -0
- package/src/middleware/request-id.ts +41 -0
- package/src/middleware/request-timeout.ts +69 -0
- package/src/middleware/security-headers.ts +24 -0
- package/src/middleware/startup-guard.ts +107 -0
- package/src/observability/service-contract.ts +192 -0
- package/src/plugins/index.ts +118 -0
- package/src/routes/__tests__/adapters-date-helpers.spec.ts +332 -0
- package/src/routes/__tests__/adapters.spec.ts +680 -0
- package/src/routes/__tests__/cache.spec.ts +264 -0
- package/src/routes/__tests__/events.spec.ts +434 -0
- package/src/routes/__tests__/health.spec.ts +414 -0
- package/src/routes/__tests__/logs.spec.ts +494 -0
- package/src/routes/__tests__/observability.spec.ts +392 -0
- package/src/routes/__tests__/plugins.spec.ts +931 -0
- package/src/routes/__tests__/workflow-management.spec.ts +698 -0
- package/src/routes/__tests__/workflows.spec.ts +204 -0
- package/src/routes/adapter-call.ts +220 -0
- package/src/routes/adapters.ts +1206 -0
- package/src/routes/analytics.ts +328 -0
- package/src/routes/cache.ts +104 -0
- package/src/routes/debug-routes.ts +95 -0
- package/src/routes/events.ts +115 -0
- package/src/routes/health.ts +348 -0
- package/src/routes/hello.ts +47 -0
- package/src/routes/index.ts +395 -0
- package/src/routes/jobs.ts +253 -0
- package/src/routes/logs.ts +718 -0
- package/src/routes/metrics.ts +120 -0
- package/src/routes/observability.ts +1388 -0
- package/src/routes/openapi.ts +101 -0
- package/src/routes/platform.ts +143 -0
- package/src/routes/plugins.ts +998 -0
- package/src/routes/readiness.ts +83 -0
- package/src/routes/workflow-management.ts +469 -0
- package/src/routes/workflows.ts +256 -0
- package/src/server.ts +203 -0
- package/src/services/historical-metrics.ts +496 -0
- package/src/services/incident-analyzer.ts +289 -0
- package/src/services/incident-detector.ts +691 -0
- package/src/services/incident-schema.sql +76 -0
- package/src/services/incident-storage.ts +593 -0
- package/src/services/system-metrics-collector.ts +244 -0
- package/src/types/fastify.d.ts +58 -0
- package/src/utils/__tests__/sse-auth.spec.ts +113 -0
- package/src/utils/path-helpers.ts +15 -0
- package/src/utils/repo.ts +42 -0
- package/src/utils/schema.ts +20 -0
- package/src/utils/sse-auth.ts +81 -0
- package/test-sandbox.sh +105 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +18 -0
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/rest-api-app/routes/plugins
|
|
3
|
+
* Plugin routes registration
|
|
4
|
+
*
|
|
5
|
+
* Uses @kb-labs/plugin-execution for unified execution layer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { FastifyInstance } from 'fastify';
|
|
9
|
+
import type { RestApiConfig } from '@kb-labs/rest-api-core';
|
|
10
|
+
import type { IEntityRegistry, RegistrySnapshot } from '@kb-labs/core-registry';
|
|
11
|
+
import type { ManifestV3 } from '@kb-labs/plugin-contracts';
|
|
12
|
+
import { validateManifest } from '@kb-labs/plugin-contracts';
|
|
13
|
+
import { mountRoutes } from '@kb-labs/plugin-execution/http';
|
|
14
|
+
import { mountWebSocketChannels } from '@kb-labs/plugin-execution';
|
|
15
|
+
import { combineManifestsToRegistry } from '@kb-labs/rest-api-core';
|
|
16
|
+
import { platform } from '@kb-labs/core-runtime';
|
|
17
|
+
import {
|
|
18
|
+
logDiagnosticEvent,
|
|
19
|
+
type DiagnosticLogLevel,
|
|
20
|
+
} from '@kb-labs/core-platform';
|
|
21
|
+
import type { DiagnosticReasonCode } from '@kb-labs/core-contracts';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
import * as fs from 'node:fs/promises';
|
|
24
|
+
import type { ReadinessState } from './readiness';
|
|
25
|
+
import { resolveWorkspaceRoot } from '@kb-labs/core-workspace';
|
|
26
|
+
import { metricsCollector, restDomainOperationMetrics } from '../middleware/metrics.js';
|
|
27
|
+
import { performance } from 'node:perf_hooks';
|
|
28
|
+
|
|
29
|
+
interface SnapshotManifestEntry {
|
|
30
|
+
pluginId: string;
|
|
31
|
+
manifest: ManifestV3;
|
|
32
|
+
pluginRoot: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extractSnapshotManifests(snapshot: RegistrySnapshot): SnapshotManifestEntry[] {
|
|
36
|
+
return (snapshot.manifests || []).map(entry => ({
|
|
37
|
+
pluginId: entry.pluginId,
|
|
38
|
+
manifest: entry.manifest,
|
|
39
|
+
pluginRoot: entry.pluginRoot,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register plugin routes
|
|
45
|
+
*/
|
|
46
|
+
export async function registerPluginRoutes(
|
|
47
|
+
server: FastifyInstance,
|
|
48
|
+
config: RestApiConfig,
|
|
49
|
+
repoRoot: string,
|
|
50
|
+
registry: IEntityRegistry,
|
|
51
|
+
readiness?: ReadinessState
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const gatewayTimeoutMs = config.timeouts?.requestTimeout ?? 30_000;
|
|
54
|
+
// Stats aggregated from sequential mount results
|
|
55
|
+
const stats = {
|
|
56
|
+
mountedRoutes: 0,
|
|
57
|
+
mountedChannels: 0,
|
|
58
|
+
errors: 0,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (readiness) {
|
|
62
|
+
readiness.pluginRoutesMounted = false;
|
|
63
|
+
readiness.pluginMountInProgress = true;
|
|
64
|
+
readiness.pluginRoutesCount = 0;
|
|
65
|
+
readiness.pluginRouteErrors = 0;
|
|
66
|
+
readiness.pluginRouteFailures = [];
|
|
67
|
+
readiness.pluginRoutesLastDurationMs = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
metricsCollector.resetPluginRouteBudgets();
|
|
71
|
+
|
|
72
|
+
const mountMetrics = metricsCollector.beginPluginMount();
|
|
73
|
+
const mountStart = performance.now();
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const workspaceResolution = await resolveWorkspaceRoot({
|
|
77
|
+
startDir: repoRoot,
|
|
78
|
+
env: {
|
|
79
|
+
KB_LABS_WORKSPACE_ROOT: process.env.KB_LABS_WORKSPACE_ROOT,
|
|
80
|
+
KB_LABS_REPO_ROOT: process.env.KB_LABS_REPO_ROOT,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const workspaceRoot = workspaceResolution.rootDir;
|
|
84
|
+
platform.logger.info('Resolved workspace root', {
|
|
85
|
+
repoRoot,
|
|
86
|
+
workspaceRoot,
|
|
87
|
+
source: workspaceResolution.source,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const snapshot = registry.snapshot();
|
|
91
|
+
const manifests = extractSnapshotManifests(snapshot);
|
|
92
|
+
|
|
93
|
+
// Log discovery summary
|
|
94
|
+
platform.logger.info('Plugin discovery summary', {
|
|
95
|
+
totalManifests: manifests.length,
|
|
96
|
+
rev: snapshot.rev,
|
|
97
|
+
partial: snapshot.partial,
|
|
98
|
+
stale: snapshot.stale,
|
|
99
|
+
plugins: manifests.map(e => ({
|
|
100
|
+
id: e.manifest.id,
|
|
101
|
+
restRoutes: e.manifest.rest?.routes?.length ?? 0,
|
|
102
|
+
wsChannels: e.manifest.ws?.channels?.length ?? 0,
|
|
103
|
+
})),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (snapshot.partial || snapshot.stale) {
|
|
107
|
+
if (snapshot.partial) {
|
|
108
|
+
logDiagnosticEvent(platform.logger, {
|
|
109
|
+
domain: 'registry',
|
|
110
|
+
event: 'plugin.registry.snapshot',
|
|
111
|
+
level: 'warn',
|
|
112
|
+
reasonCode: 'snapshot_partial',
|
|
113
|
+
message: 'Registry snapshot is partial',
|
|
114
|
+
outcome: 'failed',
|
|
115
|
+
serviceId: 'rest',
|
|
116
|
+
evidence: {
|
|
117
|
+
rev: snapshot.rev,
|
|
118
|
+
generatedAt: snapshot.generatedAt,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (snapshot.stale) {
|
|
124
|
+
logDiagnosticEvent(platform.logger, {
|
|
125
|
+
domain: 'registry',
|
|
126
|
+
event: 'plugin.registry.snapshot',
|
|
127
|
+
level: 'warn',
|
|
128
|
+
reasonCode: 'snapshot_stale',
|
|
129
|
+
message: 'Registry snapshot is stale',
|
|
130
|
+
outcome: 'failed',
|
|
131
|
+
serviceId: 'rest',
|
|
132
|
+
evidence: {
|
|
133
|
+
rev: snapshot.rev,
|
|
134
|
+
generatedAt: snapshot.generatedAt,
|
|
135
|
+
expiresAt: snapshot.expiresAt,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
emitRegistrySnapshotDiagnostics(snapshot);
|
|
142
|
+
|
|
143
|
+
// Use platform's unified ExecutionBackend (initialized in bootstrap.ts)
|
|
144
|
+
const backend = platform.executionBackend;
|
|
145
|
+
|
|
146
|
+
// Filter plugins that have routes or channels to mount
|
|
147
|
+
const mountableManifests = manifests
|
|
148
|
+
.filter(entry =>
|
|
149
|
+
(entry.manifest.rest?.routes && entry.manifest.rest.routes.length > 0) ||
|
|
150
|
+
(entry.manifest.ws?.channels && entry.manifest.ws.channels.length > 0)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
platform.logger.info('Plugins selected for route mounting', {
|
|
154
|
+
mountable: mountableManifests.length,
|
|
155
|
+
skipped: manifests.length - mountableManifests.length,
|
|
156
|
+
mountablePlugins: mountableManifests.map(e => e.manifest.id),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ── Phase 1: Batch-validate all handler files in one I/O pass ──
|
|
160
|
+
// Collect all handler file paths across all plugins, then validate in a single Promise.all()
|
|
161
|
+
// This avoids interleaved fs.access() calls that caused race conditions with parallel mounting.
|
|
162
|
+
//
|
|
163
|
+
// Handler paths in manifests are relative to the plugin's dist/ directory, because manifests
|
|
164
|
+
// are loaded from dist/manifest.js. pluginRoot points to the package root, so we resolve
|
|
165
|
+
// handlers relative to pluginRoot/dist/ to match where the compiled files actually live.
|
|
166
|
+
const handlerChecks: Array<{ key: string; filePath: string }> = [];
|
|
167
|
+
for (const entry of mountableManifests) {
|
|
168
|
+
if (entry.manifest.rest?.routes) {
|
|
169
|
+
for (const route of entry.manifest.rest.routes) {
|
|
170
|
+
const handlerFile = route.handler.split('#')[0];
|
|
171
|
+
if (handlerFile) {
|
|
172
|
+
const pluginDistRoot = path.join(entry.pluginRoot, 'dist');
|
|
173
|
+
const handlerPath = path.resolve(pluginDistRoot, handlerFile);
|
|
174
|
+
handlerChecks.push({
|
|
175
|
+
key: `${entry.manifest.id}::${route.method} ${route.path}`,
|
|
176
|
+
filePath: handlerPath,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const handlerAccessResults = await Promise.all(
|
|
184
|
+
handlerChecks.map(async (check) => {
|
|
185
|
+
try {
|
|
186
|
+
await fs.access(check.filePath);
|
|
187
|
+
return { key: check.key, filePath: check.filePath, exists: true };
|
|
188
|
+
} catch {
|
|
189
|
+
return { key: check.key, filePath: check.filePath, exists: false };
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Build lookup: "pluginId::METHOD /path" → { exists, filePath }
|
|
195
|
+
const handlerExistsMap = new Map<string, { exists: boolean; filePath: string }>();
|
|
196
|
+
for (const result of handlerAccessResults) {
|
|
197
|
+
handlerExistsMap.set(result.key, { exists: result.exists, filePath: result.filePath });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
platform.logger.debug('Batch handler file validation completed', {
|
|
201
|
+
totalHandlers: handlerChecks.length,
|
|
202
|
+
validHandlers: handlerAccessResults.filter(r => r.exists).length,
|
|
203
|
+
invalidHandlers: handlerAccessResults.filter(r => !r.exists).length,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ── Phase 2: Sequential plugin mounting ──
|
|
207
|
+
// Routes are registered in-memory on Fastify (~0.1ms per route).
|
|
208
|
+
// Sequential mounting eliminates race conditions with zero performance cost.
|
|
209
|
+
let mountedRoutes = 0;
|
|
210
|
+
let mountedChannels = 0;
|
|
211
|
+
let errors = 0;
|
|
212
|
+
const succeeded: string[] = [];
|
|
213
|
+
const failed: string[] = [];
|
|
214
|
+
const routeFailures: Array<{ id: string; error: string }> = [];
|
|
215
|
+
|
|
216
|
+
for (const entry of mountableManifests) {
|
|
217
|
+
const { manifest, pluginRoot } = entry;
|
|
218
|
+
const pluginDistRoot = path.join(pluginRoot, 'dist');
|
|
219
|
+
|
|
220
|
+
// Validate routes using pre-built handler map (no I/O here)
|
|
221
|
+
const restValidationErrors: string[] = [];
|
|
222
|
+
if (manifest.rest?.routes) {
|
|
223
|
+
for (const route of manifest.rest.routes) {
|
|
224
|
+
const handlerFile = route.handler.split('#')[0];
|
|
225
|
+
if (!handlerFile) {
|
|
226
|
+
restValidationErrors.push(
|
|
227
|
+
`Route ${route.method} ${route.path}: Invalid handler reference "${route.handler}"`
|
|
228
|
+
);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const lookupKey = `${manifest.id}::${route.method} ${route.path}`;
|
|
232
|
+
const check = handlerExistsMap.get(lookupKey);
|
|
233
|
+
if (check && !check.exists) {
|
|
234
|
+
restValidationErrors.push(
|
|
235
|
+
`Route ${route.method} ${route.path}: Handler file not found: ${check.filePath}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (restValidationErrors.length > 0) {
|
|
242
|
+
const reasonCode = inferRouteValidationReasonCode(restValidationErrors);
|
|
243
|
+
logDiagnosticEvent(platform.logger, {
|
|
244
|
+
event: 'plugin.routes.validation',
|
|
245
|
+
level: resolveRouteValidationLogLevel(restValidationErrors, manifest.rest?.routes?.length ?? 0),
|
|
246
|
+
reasonCode,
|
|
247
|
+
message: 'Plugin route validation failed',
|
|
248
|
+
outcome: 'failed',
|
|
249
|
+
pluginId: manifest.id,
|
|
250
|
+
pluginVersion: manifest.version,
|
|
251
|
+
serviceId: 'rest',
|
|
252
|
+
issues: restValidationErrors,
|
|
253
|
+
remediation: 'Verify handler file paths, exports, and route schema.',
|
|
254
|
+
evidence: {
|
|
255
|
+
pluginRoot,
|
|
256
|
+
totalRoutes: manifest.rest?.routes?.length ?? 0,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
platform.logger.warn('REST validation errors found, will skip problematic routes but continue mounting others', {
|
|
261
|
+
plugin: `${manifest.id}@${manifest.version}`,
|
|
262
|
+
pluginRoot,
|
|
263
|
+
remediation: 'Verify REST handler file and export exist',
|
|
264
|
+
errors: restValidationErrors,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const errorPaths = new Set(restValidationErrors.map((error) => {
|
|
268
|
+
const match = error.match(/Route\s+(\w+)\s+([^\s:]+)/);
|
|
269
|
+
return match ? `${match[1]} ${match[2]}` : null;
|
|
270
|
+
}).filter(Boolean));
|
|
271
|
+
|
|
272
|
+
const validRoutes = manifest.rest!.routes!.filter((route) => {
|
|
273
|
+
const routeKey = `${route.method} ${route.path}`;
|
|
274
|
+
return !errorPaths.has(routeKey);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (validRoutes.length === 0) {
|
|
278
|
+
restDomainOperationMetrics.recordOperation('plugin.routes.mount', 0, 'error');
|
|
279
|
+
logDiagnosticEvent(platform.logger, {
|
|
280
|
+
event: 'plugin.routes.validation',
|
|
281
|
+
level: 'error',
|
|
282
|
+
reasonCode,
|
|
283
|
+
message: 'All plugin routes failed validation; skipping plugin',
|
|
284
|
+
outcome: 'failed',
|
|
285
|
+
pluginId: manifest.id,
|
|
286
|
+
pluginVersion: manifest.version,
|
|
287
|
+
serviceId: 'rest',
|
|
288
|
+
issues: restValidationErrors,
|
|
289
|
+
evidence: {
|
|
290
|
+
pluginRoot,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
errors += 1;
|
|
294
|
+
failed.push(manifest.id);
|
|
295
|
+
routeFailures.push({
|
|
296
|
+
id: manifest.id,
|
|
297
|
+
error: summarizeValidationErrors(restValidationErrors),
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
manifest.rest!.routes = validRoutes;
|
|
303
|
+
platform.logger.info('Filtered routes, mounting valid ones', {
|
|
304
|
+
plugin: `${manifest.id}@${manifest.version}`,
|
|
305
|
+
totalRoutes: manifest.rest!.routes.length + restValidationErrors.length,
|
|
306
|
+
validRoutes: validRoutes.length,
|
|
307
|
+
skippedRoutes: restValidationErrors.length,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const start = performance.now();
|
|
313
|
+
|
|
314
|
+
let pluginBasePath: string;
|
|
315
|
+
if (manifest.rest?.basePath) {
|
|
316
|
+
if (manifest.rest.basePath.startsWith('/api/')) {
|
|
317
|
+
pluginBasePath = manifest.rest.basePath;
|
|
318
|
+
} else if (manifest.rest.basePath.startsWith('/v1/')) {
|
|
319
|
+
pluginBasePath = manifest.rest.basePath.replace(/^\/v1/, config.basePath);
|
|
320
|
+
} else {
|
|
321
|
+
pluginBasePath = `${config.basePath}${manifest.rest.basePath}`;
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
pluginBasePath = `${config.basePath}/plugins/${manifest.id}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
platform.logger.info('Mounting plugin routes', {
|
|
328
|
+
plugin: `${manifest.id}@${manifest.version}`,
|
|
329
|
+
configBasePath: config.basePath,
|
|
330
|
+
manifestBasePath: manifest.rest?.basePath,
|
|
331
|
+
pluginBasePath,
|
|
332
|
+
pluginRoot,
|
|
333
|
+
routes: manifest.rest?.routes?.length ?? 0,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
await mountRoutes(server, manifest, {
|
|
337
|
+
backend,
|
|
338
|
+
pluginRoot: pluginDistRoot,
|
|
339
|
+
workspaceRoot,
|
|
340
|
+
basePath: pluginBasePath,
|
|
341
|
+
defaultTimeoutMs: gatewayTimeoutMs,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const duration = performance.now() - start;
|
|
345
|
+
const routesCount = manifest.rest?.routes?.length ?? 0;
|
|
346
|
+
|
|
347
|
+
for (const route of manifest.rest?.routes ?? []) {
|
|
348
|
+
metricsCollector.registerRouteBudget(
|
|
349
|
+
route.method,
|
|
350
|
+
`${pluginBasePath}${route.path}`,
|
|
351
|
+
route.timeoutMs ?? gatewayTimeoutMs,
|
|
352
|
+
manifest.id
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
mountMetrics.recordSuccess(manifest.id, routesCount, duration);
|
|
357
|
+
restDomainOperationMetrics.recordOperation('plugin.routes.mount', duration, 'ok');
|
|
358
|
+
|
|
359
|
+
for (const route of manifest.rest?.routes ?? []) {
|
|
360
|
+
platform.logger.info('Mounted route', {
|
|
361
|
+
plugin: manifest.id,
|
|
362
|
+
method: route.method,
|
|
363
|
+
path: `${pluginBasePath}${route.path}`,
|
|
364
|
+
handler: route.handler,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
platform.logger.info('Successfully mounted plugin routes', {
|
|
369
|
+
plugin: `${manifest.id}@${manifest.version}`,
|
|
370
|
+
pluginBasePath,
|
|
371
|
+
routesCount,
|
|
372
|
+
durationMs: Number(duration.toFixed(2)),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Mount WebSocket channels if present
|
|
376
|
+
let channelsCount = 0;
|
|
377
|
+
if (manifest.ws?.channels && manifest.ws.channels.length > 0) {
|
|
378
|
+
try {
|
|
379
|
+
const wsStart = performance.now();
|
|
380
|
+
let wsBasePath: string = manifest.ws.basePath || `/v1/ws/plugins/${manifest.id}`;
|
|
381
|
+
// Normalize: /v1/ → config.basePath (same as REST routes) so gateway
|
|
382
|
+
// can proxy both HTTP and WS under the same /api/v1 prefix.
|
|
383
|
+
if (wsBasePath.startsWith('/v1/')) {
|
|
384
|
+
wsBasePath = wsBasePath.replace(/^\/v1/, config.basePath);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
platform.logger.info('Mounting WebSocket channels', {
|
|
388
|
+
plugin: `${manifest.id}@${manifest.version}`,
|
|
389
|
+
wsBasePath,
|
|
390
|
+
channels: manifest.ws.channels.length,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const wsResult = await mountWebSocketChannels(server, manifest, {
|
|
394
|
+
backend,
|
|
395
|
+
pluginRoot: pluginDistRoot,
|
|
396
|
+
workspaceRoot,
|
|
397
|
+
basePath: wsBasePath,
|
|
398
|
+
defaultTimeoutMs: manifest.ws.defaults?.timeoutMs ?? gatewayTimeoutMs,
|
|
399
|
+
defaultMaxMessageSize: manifest.ws.defaults?.maxMessageSize,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const wsDuration = performance.now() - wsStart;
|
|
403
|
+
channelsCount = wsResult.mounted;
|
|
404
|
+
|
|
405
|
+
if (wsResult.mounted > 0) {
|
|
406
|
+
platform.logger.info('Successfully mounted WebSocket channels', {
|
|
407
|
+
plugin: `${manifest.id}@${manifest.version}`,
|
|
408
|
+
channels: wsResult.mounted,
|
|
409
|
+
durationMs: Number(wsDuration.toFixed(2)),
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (wsResult.errors.length > 0) {
|
|
414
|
+
platform.logger.warn('WebSocket channel mounting had errors', {
|
|
415
|
+
plugin: `${manifest.id}@${manifest.version}`,
|
|
416
|
+
errors: wsResult.errors,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
} catch (wsError) {
|
|
420
|
+
logDiagnosticEvent(platform.logger, {
|
|
421
|
+
event: 'plugin.ws.mount',
|
|
422
|
+
level: 'error',
|
|
423
|
+
reasonCode: 'ws_mount_failed',
|
|
424
|
+
message: 'Failed to mount plugin WebSocket channels',
|
|
425
|
+
outcome: 'failed',
|
|
426
|
+
error: wsError instanceof Error ? wsError : new Error(String(wsError)),
|
|
427
|
+
pluginId: manifest.id,
|
|
428
|
+
pluginVersion: manifest.version,
|
|
429
|
+
serviceId: 'rest',
|
|
430
|
+
});
|
|
431
|
+
platform.logger.error(
|
|
432
|
+
'Failed to mount WebSocket channels',
|
|
433
|
+
wsError instanceof Error ? wsError : new Error(String(wsError)),
|
|
434
|
+
{ plugin: manifest.id }
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
mountedRoutes += routesCount;
|
|
440
|
+
mountedChannels += channelsCount;
|
|
441
|
+
succeeded.push(manifest.id);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
mountMetrics.recordFailure(manifest.id, shortErrorMessage(error));
|
|
444
|
+
restDomainOperationMetrics.recordOperation('plugin.routes.mount', 0, 'error');
|
|
445
|
+
logDiagnosticEvent(platform.logger, {
|
|
446
|
+
event: 'plugin.routes.mount',
|
|
447
|
+
level: 'error',
|
|
448
|
+
reasonCode: 'route_mount_failed',
|
|
449
|
+
message: 'Failed to mount plugin routes',
|
|
450
|
+
outcome: 'failed',
|
|
451
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
452
|
+
pluginId: manifest.id,
|
|
453
|
+
pluginVersion: manifest.version,
|
|
454
|
+
serviceId: 'rest',
|
|
455
|
+
});
|
|
456
|
+
platform.logger.error(
|
|
457
|
+
'Failed to mount plugin routes',
|
|
458
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
459
|
+
{ plugin: manifest.id }
|
|
460
|
+
);
|
|
461
|
+
errors += 1;
|
|
462
|
+
failed.push(manifest.id);
|
|
463
|
+
routeFailures.push({
|
|
464
|
+
id: manifest.id,
|
|
465
|
+
error: `rest_mount_failed ${shortErrorMessage(error)}`,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Update stats and readiness
|
|
471
|
+
stats.mountedRoutes = mountedRoutes;
|
|
472
|
+
stats.mountedChannels = mountedChannels;
|
|
473
|
+
stats.errors = errors;
|
|
474
|
+
if (readiness) {
|
|
475
|
+
readiness.pluginRouteFailures = routeFailures;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Log summary
|
|
479
|
+
platform.logger.info('Sequential plugin route and WebSocket channel mounting completed', {
|
|
480
|
+
total: mountableManifests.length,
|
|
481
|
+
succeeded: succeeded.length,
|
|
482
|
+
failed: failed.length,
|
|
483
|
+
mountedRoutes,
|
|
484
|
+
mountedChannels,
|
|
485
|
+
errors,
|
|
486
|
+
});
|
|
487
|
+
} catch (error) {
|
|
488
|
+
logDiagnosticEvent(platform.logger, {
|
|
489
|
+
domain: 'registry',
|
|
490
|
+
event: 'plugin.discovery',
|
|
491
|
+
level: 'error',
|
|
492
|
+
reasonCode: 'plugin_discovery_failed',
|
|
493
|
+
message: 'Plugin discovery failed during route registration',
|
|
494
|
+
outcome: 'failed',
|
|
495
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
496
|
+
serviceId: 'rest',
|
|
497
|
+
});
|
|
498
|
+
platform.logger.error(
|
|
499
|
+
'Plugin discovery failed',
|
|
500
|
+
error instanceof Error ? error : new Error(String(error))
|
|
501
|
+
);
|
|
502
|
+
stats.errors += 1;
|
|
503
|
+
if (readiness) {
|
|
504
|
+
readiness.pluginRoutesCount = stats.mountedRoutes;
|
|
505
|
+
readiness.pluginRouteErrors = stats.errors;
|
|
506
|
+
readiness.pluginRoutesMounted = false;
|
|
507
|
+
readiness.pluginMountInProgress = false;
|
|
508
|
+
readiness.pluginRoutesLastDurationMs = null;
|
|
509
|
+
readiness.pluginRouteFailures.push({
|
|
510
|
+
id: 'discovery',
|
|
511
|
+
error: `rest_discovery_failed ${shortErrorMessage(error)}`,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
metricsCollector.completePluginMount(platform.logger as any);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (readiness) {
|
|
519
|
+
readiness.pluginRoutesCount = stats.mountedRoutes;
|
|
520
|
+
readiness.pluginRouteErrors = stats.errors;
|
|
521
|
+
readiness.pluginRoutesMounted = stats.errors === 0;
|
|
522
|
+
readiness.pluginMountInProgress = false;
|
|
523
|
+
readiness.lastPluginMountTs = new Date().toISOString();
|
|
524
|
+
readiness.pluginRoutesLastDurationMs = Number(
|
|
525
|
+
(performance.now() - mountStart).toFixed(2)
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
metricsCollector.completePluginMount(platform.logger as any);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Register plugin snapshot endpoints for Studio
|
|
534
|
+
*/
|
|
535
|
+
export async function registerPluginSnapshotRoutes(
|
|
536
|
+
server: FastifyInstance,
|
|
537
|
+
config: RestApiConfig,
|
|
538
|
+
registry: IEntityRegistry
|
|
539
|
+
): Promise<void> {
|
|
540
|
+
const basePath = config.basePath;
|
|
541
|
+
|
|
542
|
+
server.post(`${basePath}/plugins/refresh`, async (_request, reply) => {
|
|
543
|
+
try {
|
|
544
|
+
await restDomainOperationMetrics.observeOperation('plugin.registry.refresh', async () => {
|
|
545
|
+
await registry.refresh();
|
|
546
|
+
});
|
|
547
|
+
const snapshot = await restDomainOperationMetrics.observeOperation('plugin.registry.snapshot', async () =>
|
|
548
|
+
registry.snapshot(),
|
|
549
|
+
);
|
|
550
|
+
emitRegistrySnapshotDiagnostics(snapshot);
|
|
551
|
+
|
|
552
|
+
reply.type('application/json');
|
|
553
|
+
return {
|
|
554
|
+
ok: true,
|
|
555
|
+
data: {
|
|
556
|
+
rev: snapshot.rev,
|
|
557
|
+
total: snapshot.plugins.length,
|
|
558
|
+
generatedAt: snapshot.generatedAt,
|
|
559
|
+
partial: snapshot.partial,
|
|
560
|
+
stale: snapshot.stale,
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
} catch (error) {
|
|
564
|
+
logDiagnosticEvent(platform.logger, {
|
|
565
|
+
domain: 'registry',
|
|
566
|
+
event: 'plugin.registry.refresh',
|
|
567
|
+
level: 'error',
|
|
568
|
+
reasonCode: 'registry_refresh_failed',
|
|
569
|
+
message: 'Failed to refresh plugin registry',
|
|
570
|
+
outcome: 'failed',
|
|
571
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
572
|
+
serviceId: 'rest',
|
|
573
|
+
});
|
|
574
|
+
platform.logger.error(
|
|
575
|
+
'Failed to refresh plugin registry',
|
|
576
|
+
error instanceof Error ? error : new Error(String(error))
|
|
577
|
+
);
|
|
578
|
+
reply.code(500).send({
|
|
579
|
+
ok: false,
|
|
580
|
+
error: 'Failed to refresh plugin registry',
|
|
581
|
+
message: error instanceof Error ? error.message : String(error),
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Raw manifests endpoint for Studio/debug consumers
|
|
587
|
+
server.get(`${basePath}/plugins/registry`, async (_request, reply) => {
|
|
588
|
+
try {
|
|
589
|
+
const snapshot = await restDomainOperationMetrics.observeOperation('plugin.registry.snapshot', async () =>
|
|
590
|
+
registry.snapshot(),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
// Get build timestamps and validate manifests
|
|
594
|
+
const manifestsWithTimestamps = await Promise.all(
|
|
595
|
+
snapshot.manifests.map(async (entry) => {
|
|
596
|
+
let buildTimestamp: string | undefined;
|
|
597
|
+
try {
|
|
598
|
+
const distPath = path.join(entry.pluginRoot, 'dist');
|
|
599
|
+
const stats = await fs.stat(distPath);
|
|
600
|
+
buildTimestamp = stats.mtime.toISOString();
|
|
601
|
+
} catch {
|
|
602
|
+
// dist/ doesn't exist or not accessible, skip
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Validate manifest structure
|
|
606
|
+
const validation = validateManifest(entry.manifest);
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
pluginId: entry.pluginId,
|
|
610
|
+
manifest: entry.manifest,
|
|
611
|
+
pluginRoot: entry.pluginRoot,
|
|
612
|
+
source: entry.source,
|
|
613
|
+
discoveredAt: snapshot.generatedAt,
|
|
614
|
+
buildTimestamp,
|
|
615
|
+
validation: {
|
|
616
|
+
valid: validation.valid,
|
|
617
|
+
errors: validation.errors,
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
})
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
reply.type('application/json');
|
|
624
|
+
return {
|
|
625
|
+
manifests: manifestsWithTimestamps,
|
|
626
|
+
apiBasePath: basePath,
|
|
627
|
+
diagnostics: {
|
|
628
|
+
total: snapshot.diagnostics?.length ?? 0,
|
|
629
|
+
items: (snapshot.diagnostics ?? []).map((diagnostic) => ({
|
|
630
|
+
severity: diagnostic.severity,
|
|
631
|
+
code: diagnostic.code,
|
|
632
|
+
reasonCode: mapRegistryDiagnosticReasonCode(diagnostic.code),
|
|
633
|
+
message: diagnostic.message,
|
|
634
|
+
pluginId: diagnostic.context?.pluginId,
|
|
635
|
+
filePath: diagnostic.context?.filePath,
|
|
636
|
+
remediation: diagnostic.remediation,
|
|
637
|
+
ts: diagnostic.ts,
|
|
638
|
+
})),
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
} catch (error) {
|
|
642
|
+
platform.logger.error(
|
|
643
|
+
'Failed to get plugin registry',
|
|
644
|
+
error instanceof Error ? error : new Error(String(error))
|
|
645
|
+
);
|
|
646
|
+
reply.code(500).send({
|
|
647
|
+
error: 'Failed to load plugin registry',
|
|
648
|
+
message: error instanceof Error ? error.message : String(error),
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Studio Registry V2 — returns plugin pages and menus for Module Federation
|
|
654
|
+
server.get(`${basePath}/studio/registry`, {
|
|
655
|
+
schema: { tags: ['Studio'], summary: 'Get Studio Registry V2 (plugin pages for Module Federation)' },
|
|
656
|
+
}, async (_request, reply) => {
|
|
657
|
+
try {
|
|
658
|
+
const snapshot = await restDomainOperationMetrics.observeOperation('plugin.studio.registry', async () =>
|
|
659
|
+
registry.snapshot(),
|
|
660
|
+
);
|
|
661
|
+
const manifests = extractSnapshotManifests(snapshot);
|
|
662
|
+
|
|
663
|
+
// Only plugins with studio V2 config
|
|
664
|
+
const studioManifests = manifests
|
|
665
|
+
.filter(entry => entry.manifest.studio?.version === 2)
|
|
666
|
+
.map(entry => ({ manifest: entry.manifest, pluginRoot: entry.pluginRoot }));
|
|
667
|
+
|
|
668
|
+
const studioRegistry = combineManifestsToRegistry(studioManifests);
|
|
669
|
+
|
|
670
|
+
reply.type('application/json');
|
|
671
|
+
return studioRegistry;
|
|
672
|
+
} catch (error) {
|
|
673
|
+
platform.logger.error('Failed to generate studio registry', error instanceof Error ? error : new Error(String(error)));
|
|
674
|
+
reply.code(500).send({
|
|
675
|
+
error: 'Failed to generate studio registry',
|
|
676
|
+
message: error instanceof Error ? error.message : String(error),
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// Widget bundle static files — serves dist/widgets/ from plugin packages
|
|
682
|
+
server.get<{
|
|
683
|
+
Params: { scope: string; name: string; '*': string };
|
|
684
|
+
}>('/plugins/@:scope/:name/widgets/*', {
|
|
685
|
+
schema: { hide: true },
|
|
686
|
+
}, async (request, reply) => {
|
|
687
|
+
const { scope, name } = request.params;
|
|
688
|
+
const filePath = request.params['*'];
|
|
689
|
+
|
|
690
|
+
if (!scope || !name || !filePath || filePath.includes('..')) {
|
|
691
|
+
return reply.code(400).send({ error: 'Invalid path' });
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const pluginId = `@${scope}/${name}`;
|
|
695
|
+
|
|
696
|
+
// Find the plugin's widgetBundleDir from registry
|
|
697
|
+
const snapshot = registry.snapshot();
|
|
698
|
+
const manifests = extractSnapshotManifests(snapshot);
|
|
699
|
+
const entry = manifests.find(e => e.manifest.id === pluginId && e.manifest.studio?.version === 2);
|
|
700
|
+
|
|
701
|
+
if (!entry) {
|
|
702
|
+
return reply.code(404).send({ error: `No widget bundle for ${pluginId}` });
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const { join, extname } = await import('node:path');
|
|
706
|
+
const { createReadStream, existsSync, statSync } = await import('node:fs');
|
|
707
|
+
|
|
708
|
+
const bundleDir = join(entry.pluginRoot, 'dist', 'widgets');
|
|
709
|
+
const fullPath = join(bundleDir, filePath);
|
|
710
|
+
|
|
711
|
+
if (!fullPath.startsWith(bundleDir)) {
|
|
712
|
+
return reply.code(400).send({ error: 'Invalid path' });
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (!existsSync(fullPath) || !statSync(fullPath).isFile()) {
|
|
716
|
+
return reply.code(404).send({ error: `Widget file not found: ${pluginId}/widgets/${filePath}` });
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const MIME: Record<string, string> = {
|
|
720
|
+
'.js': 'application/javascript', '.mjs': 'application/javascript',
|
|
721
|
+
'.css': 'text/css', '.json': 'application/json', '.map': 'application/json',
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const ext = extname(filePath);
|
|
725
|
+
const isEntry = filePath === 'remoteEntry.js';
|
|
726
|
+
|
|
727
|
+
return reply
|
|
728
|
+
.code(200)
|
|
729
|
+
.header('Content-Type', MIME[ext] ?? 'application/octet-stream')
|
|
730
|
+
.header('Content-Length', statSync(fullPath).size)
|
|
731
|
+
.header('Cache-Control', isEntry ? 'public, max-age=10, must-revalidate' : 'public, max-age=31536000, immutable')
|
|
732
|
+
.send(createReadStream(fullPath));
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// Plugin registry health endpoint
|
|
736
|
+
server.get(`${basePath}/plugins/health`, async (_request, reply) => {
|
|
737
|
+
try {
|
|
738
|
+
const snapshot = await restDomainOperationMetrics.observeOperation('plugin.registry.health', async () =>
|
|
739
|
+
registry.snapshot(),
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
// Collect validation errors from all manifests
|
|
743
|
+
const validationIssues: Array<{ pluginId: string; errors: string[] }> = [];
|
|
744
|
+
for (const entry of snapshot.manifests) {
|
|
745
|
+
const validation = validateManifest(entry.manifest);
|
|
746
|
+
if (!validation.valid) {
|
|
747
|
+
validationIssues.push({
|
|
748
|
+
pluginId: entry.pluginId,
|
|
749
|
+
errors: validation.errors,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Registry resolution errors (plugins that failed to load)
|
|
755
|
+
const registryErrors = (snapshot as any).errors ?? [];
|
|
756
|
+
const discoveryDiagnostics = snapshot.diagnostics ?? [];
|
|
757
|
+
const discoveryErrorCount = discoveryDiagnostics.filter((diagnostic) => diagnostic.severity === 'error').length;
|
|
758
|
+
|
|
759
|
+
// Determine why registry is partial
|
|
760
|
+
const partialReasons: string[] = [];
|
|
761
|
+
if (snapshot.corrupted) {
|
|
762
|
+
partialReasons.push('snapshot_corrupted');
|
|
763
|
+
}
|
|
764
|
+
if (registryErrors.length > 0) {
|
|
765
|
+
partialReasons.push('registry_errors');
|
|
766
|
+
}
|
|
767
|
+
if (discoveryErrorCount > 0) {
|
|
768
|
+
partialReasons.push('discovery_diagnostics');
|
|
769
|
+
}
|
|
770
|
+
// If partial but no clear reason, it's likely initialization
|
|
771
|
+
if (snapshot.partial && partialReasons.length === 0) {
|
|
772
|
+
partialReasons.push('initialization_incomplete');
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
reply.type('application/json');
|
|
776
|
+
return {
|
|
777
|
+
healthy: !snapshot.partial && !snapshot.stale && validationIssues.length === 0 && registryErrors.length === 0 && discoveryErrorCount === 0,
|
|
778
|
+
snapshot: {
|
|
779
|
+
partial: snapshot.partial,
|
|
780
|
+
stale: snapshot.stale,
|
|
781
|
+
corrupted: snapshot.corrupted,
|
|
782
|
+
rev: snapshot.rev,
|
|
783
|
+
generatedAt: snapshot.generatedAt,
|
|
784
|
+
totalManifests: snapshot.manifests.length,
|
|
785
|
+
partialReasons,
|
|
786
|
+
},
|
|
787
|
+
registryErrors: {
|
|
788
|
+
total: registryErrors.length,
|
|
789
|
+
items: registryErrors.map((err: any) => ({
|
|
790
|
+
pluginPath: err.pluginPath,
|
|
791
|
+
pluginId: err.pluginId,
|
|
792
|
+
error: err.error,
|
|
793
|
+
code: err.code,
|
|
794
|
+
})),
|
|
795
|
+
},
|
|
796
|
+
diagnostics: {
|
|
797
|
+
total: discoveryDiagnostics.length,
|
|
798
|
+
errors: discoveryErrorCount,
|
|
799
|
+
warnings: discoveryDiagnostics.filter((diagnostic) => diagnostic.severity === 'warning').length,
|
|
800
|
+
items: discoveryDiagnostics.map((diagnostic) => ({
|
|
801
|
+
severity: diagnostic.severity,
|
|
802
|
+
code: diagnostic.code,
|
|
803
|
+
reasonCode: mapRegistryDiagnosticReasonCode(diagnostic.code),
|
|
804
|
+
message: diagnostic.message,
|
|
805
|
+
pluginId: diagnostic.context?.pluginId,
|
|
806
|
+
filePath: diagnostic.context?.filePath,
|
|
807
|
+
remediation: diagnostic.remediation,
|
|
808
|
+
ts: diagnostic.ts,
|
|
809
|
+
})),
|
|
810
|
+
},
|
|
811
|
+
validation: {
|
|
812
|
+
totalIssues: validationIssues.length,
|
|
813
|
+
issues: validationIssues,
|
|
814
|
+
},
|
|
815
|
+
message:
|
|
816
|
+
registryErrors.length > 0
|
|
817
|
+
? `Registry is partial - ${registryErrors.length} plugin(s) failed to load. Check registryErrors.items for details.`
|
|
818
|
+
: discoveryErrorCount > 0
|
|
819
|
+
? `Registry has discovery diagnostics - ${discoveryErrorCount} blocking issue(s). Check diagnostics.items for details.`
|
|
820
|
+
: snapshot.partial && partialReasons.includes('initialization_incomplete')
|
|
821
|
+
? 'Registry is partial - initialization incomplete. Try refreshing or wait for the snapshot rebuild to complete.'
|
|
822
|
+
: snapshot.partial
|
|
823
|
+
? `Registry is partial - reasons: ${partialReasons.join(', ')}`
|
|
824
|
+
: snapshot.stale
|
|
825
|
+
? 'Registry is stale - plugin snapshot may be outdated.'
|
|
826
|
+
: validationIssues.length > 0
|
|
827
|
+
? `${validationIssues.length} plugin(s) have validation errors.`
|
|
828
|
+
: 'All plugins are healthy.',
|
|
829
|
+
};
|
|
830
|
+
} catch (error) {
|
|
831
|
+
platform.logger.error('Failed to get plugin health', error instanceof Error ? error : new Error(String(error)));
|
|
832
|
+
reply.code(500).send({
|
|
833
|
+
error: 'Failed to get plugin health',
|
|
834
|
+
message: error instanceof Error ? error.message : String(error),
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
// AI Assistant endpoint: ask questions about a plugin
|
|
840
|
+
server.post(`${basePath}/plugins/:pluginId/ask`, async (request, reply) => {
|
|
841
|
+
try {
|
|
842
|
+
const { pluginId } = request.params as { pluginId: string };
|
|
843
|
+
const { question } = request.body as { question: string };
|
|
844
|
+
|
|
845
|
+
if (!question || typeof question !== 'string') {
|
|
846
|
+
reply.code(400).send({
|
|
847
|
+
error: 'Bad request',
|
|
848
|
+
message: 'Question is required and must be a string',
|
|
849
|
+
});
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Get plugin manifest
|
|
854
|
+
const snapshot = await restDomainOperationMetrics.observeOperation('plugin.registry.snapshot', async () =>
|
|
855
|
+
registry.snapshot(),
|
|
856
|
+
);
|
|
857
|
+
const pluginEntry = snapshot.manifests.find((entry) => entry.pluginId === pluginId);
|
|
858
|
+
|
|
859
|
+
if (!pluginEntry) {
|
|
860
|
+
reply.code(404).send({
|
|
861
|
+
error: 'Plugin not found',
|
|
862
|
+
message: `Plugin ${pluginId} not found in registry`,
|
|
863
|
+
});
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Build prompt for LLM
|
|
868
|
+
const systemPrompt = `You are a helpful assistant that explains KB Labs plugins based on their manifest.
|
|
869
|
+
Provide clear, concise answers about the plugin's capabilities, API endpoints, permissions, and usage.
|
|
870
|
+
Be specific and reference the actual values from the manifest.`;
|
|
871
|
+
|
|
872
|
+
const userPrompt = `Plugin Manifest:
|
|
873
|
+
${JSON.stringify(pluginEntry.manifest, null, 2)}
|
|
874
|
+
|
|
875
|
+
User Question: ${question}
|
|
876
|
+
|
|
877
|
+
Please answer the question based on the plugin manifest above.`;
|
|
878
|
+
|
|
879
|
+
// Call LLM
|
|
880
|
+
const response = await restDomainOperationMetrics.observeOperation('plugin.assistant.ask', () =>
|
|
881
|
+
platform.llm.complete(userPrompt, {
|
|
882
|
+
systemPrompt,
|
|
883
|
+
temperature: 0.3,
|
|
884
|
+
maxTokens: 1000,
|
|
885
|
+
}),
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
reply.type('application/json');
|
|
889
|
+
return {
|
|
890
|
+
answer: response.content,
|
|
891
|
+
usage: response.usage,
|
|
892
|
+
};
|
|
893
|
+
} catch (error) {
|
|
894
|
+
platform.logger.error('Failed to get AI answer about plugin', error instanceof Error ? error : new Error(String(error)));
|
|
895
|
+
reply.code(500).send({
|
|
896
|
+
error: 'Failed to get AI answer',
|
|
897
|
+
message: error instanceof Error ? error.message : String(error),
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Shutdown execution backend gracefully.
|
|
905
|
+
* NOTE: ExecutionBackend is now owned by platform, shutdown via platform.shutdown()
|
|
906
|
+
*/
|
|
907
|
+
export async function shutdownExecutionBackend(): Promise<void> {
|
|
908
|
+
// ExecutionBackend lifecycle is now managed by platform
|
|
909
|
+
// Call platform.shutdown() instead to shutdown all services including executionBackend
|
|
910
|
+
await platform.shutdown();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function summarizeValidationErrors(errors: string[]): string {
|
|
914
|
+
if (errors.length === 0) {
|
|
915
|
+
return 'rest_validation_failed';
|
|
916
|
+
}
|
|
917
|
+
const first = errors[0] || 'rest_validation_failed';
|
|
918
|
+
const prefix = first.split(':')[0]?.trim() ?? 'rest_validation_failed';
|
|
919
|
+
return `rest_validation_failed ${prefix}`.trim();
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function shortErrorMessage(error: unknown): string {
|
|
923
|
+
if (!error) {
|
|
924
|
+
return 'unknown_error';
|
|
925
|
+
}
|
|
926
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
927
|
+
const firstLine = message.trim().split('\n')[0] ?? message.trim();
|
|
928
|
+
return firstLine.length > 120 ? `${firstLine.slice(0, 117)}...` : firstLine;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function mapDiagnosticSeverityToLogLevel(
|
|
932
|
+
severity: 'error' | 'warning' | 'info' | 'debug',
|
|
933
|
+
): DiagnosticLogLevel {
|
|
934
|
+
switch (severity) {
|
|
935
|
+
case 'error':
|
|
936
|
+
return 'error';
|
|
937
|
+
case 'warning':
|
|
938
|
+
return 'warn';
|
|
939
|
+
case 'debug':
|
|
940
|
+
return 'debug';
|
|
941
|
+
default:
|
|
942
|
+
return 'info';
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function mapRegistryDiagnosticReasonCode(code: string): DiagnosticReasonCode {
|
|
947
|
+
switch (code) {
|
|
948
|
+
case 'MANIFEST_NOT_FOUND':
|
|
949
|
+
return 'manifest_missing';
|
|
950
|
+
case 'MANIFEST_VALIDATION_ERROR':
|
|
951
|
+
case 'MANIFEST_PARSE_ERROR':
|
|
952
|
+
return 'manifest_invalid';
|
|
953
|
+
case 'MANIFEST_LOAD_TIMEOUT':
|
|
954
|
+
return 'manifest_load_timeout';
|
|
955
|
+
case 'INTEGRITY_MISMATCH':
|
|
956
|
+
return 'integrity_mismatch';
|
|
957
|
+
default:
|
|
958
|
+
return 'plugin_discovery_failed';
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function emitRegistrySnapshotDiagnostics(snapshot: RegistrySnapshot): void {
|
|
963
|
+
for (const diagnostic of snapshot.diagnostics ?? []) {
|
|
964
|
+
logDiagnosticEvent(platform.logger, {
|
|
965
|
+
domain: 'registry',
|
|
966
|
+
event: 'plugin.discovery.diagnostic',
|
|
967
|
+
level: mapDiagnosticSeverityToLogLevel(diagnostic.severity),
|
|
968
|
+
reasonCode: mapRegistryDiagnosticReasonCode(diagnostic.code),
|
|
969
|
+
message: diagnostic.message,
|
|
970
|
+
outcome: diagnostic.severity === 'error' ? 'failed' : 'skipped',
|
|
971
|
+
pluginId: diagnostic.context?.pluginId,
|
|
972
|
+
serviceId: 'rest',
|
|
973
|
+
manifestPath: diagnostic.context?.filePath,
|
|
974
|
+
discoveryCode: diagnostic.code,
|
|
975
|
+
remediation: diagnostic.remediation,
|
|
976
|
+
evidence: {
|
|
977
|
+
severity: diagnostic.severity,
|
|
978
|
+
entityKind: diagnostic.context?.entityKind,
|
|
979
|
+
entityId: diagnostic.context?.entityId,
|
|
980
|
+
ts: diagnostic.ts,
|
|
981
|
+
},
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function inferRouteValidationReasonCode(errors: string[]): DiagnosticReasonCode {
|
|
987
|
+
if (errors.some((error) => error.includes('Handler file not found'))) {
|
|
988
|
+
return 'handler_not_found';
|
|
989
|
+
}
|
|
990
|
+
return 'route_validation_failed';
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function resolveRouteValidationLogLevel(
|
|
994
|
+
errors: string[],
|
|
995
|
+
totalRoutes: number,
|
|
996
|
+
): DiagnosticLogLevel {
|
|
997
|
+
return errors.length >= totalRoutes ? 'error' : 'warn';
|
|
998
|
+
}
|