@wascartech/devbot 0.0.0-stage → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +638 -2
- package/dist/access-grant-store.js +99 -0
- package/dist/access-grant-store.js.map +1 -0
- package/dist/access-resolve.js +11 -0
- package/dist/access-resolve.js.map +1 -0
- package/dist/access-rule-store.js +99 -0
- package/dist/access-rule-store.js.map +1 -0
- package/dist/admin/access-routes.js +73 -0
- package/dist/admin/access-routes.js.map +1 -0
- package/dist/admin/access-rule-routes.js +107 -0
- package/dist/admin/access-rule-routes.js.map +1 -0
- package/dist/admin/custom-repos-routes.js +93 -0
- package/dist/admin/custom-repos-routes.js.map +1 -0
- package/dist/admin/env-routes.js +74 -0
- package/dist/admin/env-routes.js.map +1 -0
- package/dist/admin/global-settings-routes.js +254 -0
- package/dist/admin/global-settings-routes.js.map +1 -0
- package/dist/admin/mcp-cleanup.js +12 -0
- package/dist/admin/mcp-cleanup.js.map +1 -0
- package/dist/admin/mcp-routes.js +169 -0
- package/dist/admin/mcp-routes.js.map +1 -0
- package/dist/admin/query-params.js +80 -0
- package/dist/admin/query-params.js.map +1 -0
- package/dist/admin/routes.js +150 -0
- package/dist/admin/routes.js.map +1 -0
- package/dist/admin/run-history-routes.js +44 -0
- package/dist/admin/run-history-routes.js.map +1 -0
- package/dist/admin/run-routes.js +99 -0
- package/dist/admin/run-routes.js.map +1 -0
- package/dist/admin/settings-routes.js +119 -0
- package/dist/admin/settings-routes.js.map +1 -0
- package/dist/admin/stats-routes.js +51 -0
- package/dist/admin/stats-routes.js.map +1 -0
- package/dist/admin/task-routes.js +213 -0
- package/dist/admin/task-routes.js.map +1 -0
- package/dist/admin/test-routes.js +38 -0
- package/dist/admin/test-routes.js.map +1 -0
- package/dist/admin/volume-routes.js +82 -0
- package/dist/admin/volume-routes.js.map +1 -0
- package/dist/admin/webhook-routes.js +174 -0
- package/dist/admin/webhook-routes.js.map +1 -0
- package/dist/agents.js +48 -0
- package/dist/agents.js.map +1 -0
- package/dist/assignee-value.js +6 -0
- package/dist/assignee-value.js.map +1 -0
- package/dist/attachments.js +76 -0
- package/dist/attachments.js.map +1 -0
- package/dist/auto-commands.js +88 -0
- package/dist/auto-commands.js.map +1 -0
- package/dist/auto-features.js +32 -0
- package/dist/auto-features.js.map +1 -0
- package/dist/base-branch-store.js +40 -0
- package/dist/base-branch-store.js.map +1 -0
- package/dist/base-branches.js +9 -0
- package/dist/base-branches.js.map +1 -0
- package/dist/build-workflow.js +116 -0
- package/dist/build-workflow.js.map +1 -0
- package/dist/change-request-lifecycle.js +16 -0
- package/dist/change-request-lifecycle.js.map +1 -0
- package/dist/change-request-store.js +72 -0
- package/dist/change-request-store.js.map +1 -0
- package/dist/ci-failure.js +80 -0
- package/dist/ci-failure.js.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/conflict-fix.js +178 -0
- package/dist/conflict-fix.js.map +1 -0
- package/dist/cookie.js +70 -0
- package/dist/cookie.js.map +1 -0
- package/dist/crypto-util.js +40 -0
- package/dist/crypto-util.js.map +1 -0
- package/dist/custom-repos-store.js +90 -0
- package/dist/custom-repos-store.js.map +1 -0
- package/dist/custom-repos-sync.js +118 -0
- package/dist/custom-repos-sync.js.map +1 -0
- package/dist/db.js +12 -0
- package/dist/db.js.map +1 -0
- package/dist/devbot-scripts.js +18 -0
- package/dist/devbot-scripts.js.map +1 -0
- package/dist/devcontainer.js +141 -0
- package/dist/devcontainer.js.map +1 -0
- package/dist/env-config-store.js +132 -0
- package/dist/env-config-store.js.map +1 -0
- package/dist/error-log-store.js +193 -0
- package/dist/error-log-store.js.map +1 -0
- package/dist/git.js +605 -0
- package/dist/git.js.map +1 -0
- package/dist/global-queue.js +127 -0
- package/dist/global-queue.js.map +1 -0
- package/dist/global-settings-store.js +307 -0
- package/dist/global-settings-store.js.map +1 -0
- package/dist/help.js +41 -0
- package/dist/help.js.map +1 -0
- package/dist/http-csp.js +15 -0
- package/dist/http-csp.js.map +1 -0
- package/dist/http-util.js +40 -0
- package/dist/http-util.js.map +1 -0
- package/dist/idempotency.js +50 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/lifecycle.js +234 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/logger.js +33 -0
- package/dist/logger.js.map +1 -0
- package/dist/lsp-servers.js +81 -0
- package/dist/lsp-servers.js.map +1 -0
- package/dist/mcp/auth.js +62 -0
- package/dist/mcp/auth.js.map +1 -0
- package/dist/mcp/effective-mcp.js +42 -0
- package/dist/mcp/effective-mcp.js.map +1 -0
- package/dist/mcp/oauth-refresh.js +138 -0
- package/dist/mcp/oauth-refresh.js.map +1 -0
- package/dist/mcp/peer-sync.js +109 -0
- package/dist/mcp/peer-sync.js.map +1 -0
- package/dist/mcp/registry.js +54 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/runner-bridge.js +93 -0
- package/dist/mcp/runner-bridge.js.map +1 -0
- package/dist/mcp/server.js +173 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/_helpers.js +88 -0
- package/dist/mcp/tools/_helpers.js.map +1 -0
- package/dist/mcp/tools/attachments.js +117 -0
- package/dist/mcp/tools/attachments.js.map +1 -0
- package/dist/mcp/tools/cicd.js +105 -0
- package/dist/mcp/tools/cicd.js.map +1 -0
- package/dist/mcp/tools/discovery.js +34 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/issues.js +56 -0
- package/dist/mcp/tools/issues.js.map +1 -0
- package/dist/mcp/tools/merge_requests.js +67 -0
- package/dist/mcp/tools/merge_requests.js.map +1 -0
- package/dist/mcp/tools/metadata.js +127 -0
- package/dist/mcp/tools/metadata.js.map +1 -0
- package/dist/mcp/tools/peer_sync.js +43 -0
- package/dist/mcp/tools/peer_sync.js.map +1 -0
- package/dist/mcp/tools/repository.js +178 -0
- package/dist/mcp/tools/repository.js.map +1 -0
- package/dist/mcp/tools/work_tracking.js +125 -0
- package/dist/mcp/tools/work_tracking.js.map +1 -0
- package/dist/mcp/tools/workitems.js +30 -0
- package/dist/mcp/tools/workitems.js.map +1 -0
- package/dist/mcp/volume-files.js +187 -0
- package/dist/mcp/volume-files.js.map +1 -0
- package/dist/mcp-auth-store.js +134 -0
- package/dist/mcp-auth-store.js.map +1 -0
- package/dist/mcp-config-store.js +83 -0
- package/dist/mcp-config-store.js.map +1 -0
- package/dist/mention.js +512 -0
- package/dist/mention.js.map +1 -0
- package/dist/migrate.js +73 -0
- package/dist/migrate.js.map +1 -0
- package/dist/mr-assignee.js +71 -0
- package/dist/mr-assignee.js.map +1 -0
- package/dist/mr-content.js +90 -0
- package/dist/mr-content.js.map +1 -0
- package/dist/oauth-routes.js +54 -0
- package/dist/oauth-routes.js.map +1 -0
- package/dist/onboarding-allowlist.js +32 -0
- package/dist/onboarding-allowlist.js.map +1 -0
- package/dist/opencode.js +291 -0
- package/dist/opencode.js.map +1 -0
- package/dist/outbound-webhook-store.js +229 -0
- package/dist/outbound-webhook-store.js.map +1 -0
- package/dist/outbound-webhook.js +314 -0
- package/dist/outbound-webhook.js.map +1 -0
- package/dist/path-sandbox.js +200 -0
- package/dist/path-sandbox.js.map +1 -0
- package/dist/platform/git-auth.js +34 -0
- package/dist/platform/git-auth.js.map +1 -0
- package/dist/platform/gitealike/attachments.js +62 -0
- package/dist/platform/gitealike/attachments.js.map +1 -0
- package/dist/platform/gitealike/base.js +935 -0
- package/dist/platform/gitealike/base.js.map +1 -0
- package/dist/platform/gitealike/forgejo.js +16 -0
- package/dist/platform/gitealike/forgejo.js.map +1 -0
- package/dist/platform/gitealike/gitea.js +16 -0
- package/dist/platform/gitealike/gitea.js.map +1 -0
- package/dist/platform/gitealike/http.js +152 -0
- package/dist/platform/gitealike/http.js.map +1 -0
- package/dist/platform/gitealike/schemas.js +284 -0
- package/dist/platform/gitealike/schemas.js.map +1 -0
- package/dist/platform/github/attachments.js +52 -0
- package/dist/platform/github/attachments.js.map +1 -0
- package/dist/platform/github/http.js +153 -0
- package/dist/platform/github/http.js.map +1 -0
- package/dist/platform/github/index.js +936 -0
- package/dist/platform/github/index.js.map +1 -0
- package/dist/platform/github/schemas.js +308 -0
- package/dist/platform/github/schemas.js.map +1 -0
- package/dist/platform/github/zip.js +85 -0
- package/dist/platform/github/zip.js.map +1 -0
- package/dist/platform/gitlab/admin.js +105 -0
- package/dist/platform/gitlab/admin.js.map +1 -0
- package/dist/platform/gitlab/api.js +229 -0
- package/dist/platform/gitlab/api.js.map +1 -0
- package/dist/platform/gitlab/attachments.js +79 -0
- package/dist/platform/gitlab/attachments.js.map +1 -0
- package/dist/platform/gitlab/http.js +165 -0
- package/dist/platform/gitlab/http.js.map +1 -0
- package/dist/platform/gitlab/index.js +630 -0
- package/dist/platform/gitlab/index.js.map +1 -0
- package/dist/platform/gitlab/oauth.js +58 -0
- package/dist/platform/gitlab/oauth.js.map +1 -0
- package/dist/platform/gitlab/read.js +182 -0
- package/dist/platform/gitlab/read.js.map +1 -0
- package/dist/platform/gitlab/schemas-read.js +274 -0
- package/dist/platform/gitlab/schemas-read.js.map +1 -0
- package/dist/platform/gitlab/schemas.js +251 -0
- package/dist/platform/gitlab/schemas.js.map +1 -0
- package/dist/platform/gitlab/signature.js +52 -0
- package/dist/platform/gitlab/signature.js.map +1 -0
- package/dist/platform/index.js +38 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/port.js +26 -0
- package/dist/platform/port.js.map +1 -0
- package/dist/platform/types.js +80 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/podman.js +119 -0
- package/dist/podman.js.map +1 -0
- package/dist/project-sync.js +69 -0
- package/dist/project-sync.js.map +1 -0
- package/dist/prompt.js +47 -0
- package/dist/prompt.js.map +1 -0
- package/dist/queue-store.js +236 -0
- package/dist/queue-store.js.map +1 -0
- package/dist/queue.js +129 -0
- package/dist/queue.js.map +1 -0
- package/dist/redact.js +23 -0
- package/dist/redact.js.map +1 -0
- package/dist/run-files.js +297 -0
- package/dist/run-files.js.map +1 -0
- package/dist/run-filters.js +54 -0
- package/dist/run-filters.js.map +1 -0
- package/dist/run-history-store.js +229 -0
- package/dist/run-history-store.js.map +1 -0
- package/dist/run-log-sink.js +270 -0
- package/dist/run-log-sink.js.map +1 -0
- package/dist/run-log.js +97 -0
- package/dist/run-log.js.map +1 -0
- package/dist/run-usage.js +29 -0
- package/dist/run-usage.js.map +1 -0
- package/dist/run-view.js +112 -0
- package/dist/run-view.js.map +1 -0
- package/dist/runner.js +1094 -0
- package/dist/runner.js.map +1 -0
- package/dist/runtime-apply.js +26 -0
- package/dist/runtime-apply.js.map +1 -0
- package/dist/runtime-settings.js +71 -0
- package/dist/runtime-settings.js.map +1 -0
- package/dist/runtime-state.js +19 -0
- package/dist/runtime-state.js.map +1 -0
- package/dist/server.js +119 -0
- package/dist/server.js.map +1 -0
- package/dist/settings-store.js +320 -0
- package/dist/settings-store.js.map +1 -0
- package/dist/status.js +121 -0
- package/dist/status.js.map +1 -0
- package/dist/stop.js +56 -0
- package/dist/stop.js.map +1 -0
- package/dist/sweeps.js +87 -0
- package/dist/sweeps.js.map +1 -0
- package/dist/task-log.js +279 -0
- package/dist/task-log.js.map +1 -0
- package/dist/task-view.js +62 -0
- package/dist/task-view.js.map +1 -0
- package/dist/token-store.js +68 -0
- package/dist/token-store.js.map +1 -0
- package/dist/types/access.js +74 -0
- package/dist/types/access.js.map +1 -0
- package/dist/types/custom-repos.js +28 -0
- package/dist/types/custom-repos.js.map +1 -0
- package/dist/types/env-config.js +25 -0
- package/dist/types/env-config.js.map +1 -0
- package/dist/types/env.js +190 -0
- package/dist/types/env.js.map +1 -0
- package/dist/types/global-settings.js +178 -0
- package/dist/types/global-settings.js.map +1 -0
- package/dist/types/lsp-servers.js +145 -0
- package/dist/types/lsp-servers.js.map +1 -0
- package/dist/types/mcp-config.js +77 -0
- package/dist/types/mcp-config.js.map +1 -0
- package/dist/types/opencode.js +59 -0
- package/dist/types/opencode.js.map +1 -0
- package/dist/types/outbound-webhook.js +56 -0
- package/dist/types/outbound-webhook.js.map +1 -0
- package/dist/types/project-settings.js +114 -0
- package/dist/types/project-settings.js.map +1 -0
- package/dist/types/provider-registry.js +37 -0
- package/dist/types/provider-registry.js.map +1 -0
- package/dist/types/runtime-settings.js +78 -0
- package/dist/types/runtime-settings.js.map +1 -0
- package/dist/usage-stats.js +159 -0
- package/dist/usage-stats.js.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/dist/volume-gc.js +193 -0
- package/dist/volume-gc.js.map +1 -0
- package/dist/volume-manage.js +191 -0
- package/dist/volume-manage.js.map +1 -0
- package/dist/volume-store.js +122 -0
- package/dist/volume-store.js.map +1 -0
- package/dist/webhook.js +1106 -0
- package/dist/webhook.js.map +1 -0
- package/dist/work-item-auto-store.js +75 -0
- package/dist/work-item-auto-store.js.map +1 -0
- package/dist/work-item-branch-store.js +37 -0
- package/dist/work-item-branch-store.js.map +1 -0
- package/migrations/0001_initial.sql +59 -0
- package/migrations/0002_queue_status.sql +15 -0
- package/migrations/0003_work_item_base_branch.sql +14 -0
- package/migrations/0004_project_settings.sql +15 -0
- package/migrations/0005_global_settings.sql +34 -0
- package/migrations/0006_model_variants.sql +8 -0
- package/migrations/0007_container_error_logs.sql +21 -0
- package/migrations/0008_project_access_grants.sql +16 -0
- package/migrations/0009_drop_imported_legacy.sql +4 -0
- package/migrations/0010_container_error_log_model.sql +6 -0
- package/migrations/0011_container_error_log_title.sql +5 -0
- package/migrations/0012_pending_task_cancel.sql +6 -0
- package/migrations/0013_builder_image.sql +6 -0
- package/migrations/0014_build_workflow.sql +7 -0
- package/migrations/0015_access_rules.sql +24 -0
- package/migrations/0016_trigger_policy.sql +17 -0
- package/migrations/0017_attachment_upload.sql +8 -0
- package/migrations/0018_work_item_branch.sql +11 -0
- package/migrations/0019_auto_push_mr.sql +18 -0
- package/migrations/0020_outbound_webhooks.sql +47 -0
- package/migrations/0021_runtime_settings.sql +12 -0
- package/migrations/0022_auto_resolve_conflicts.sql +17 -0
- package/migrations/0023_ci_fail_auto_report.sql +27 -0
- package/migrations/0024_work_item_auto_override.sql +22 -0
- package/migrations/0025_run_history.sql +37 -0
- package/migrations/0026_run_history_task_seq.sql +6 -0
- package/migrations/0027_custom_repos.sql +11 -0
- package/migrations/0028_run_usage.sql +14 -0
- package/migrations/0029_drop_ocr_langs.sql +6 -0
- package/migrations/0030_mr_assignee.sql +9 -0
- package/migrations/0031_lsp_servers.sql +14 -0
- package/opencode-config/managed/devbot-agent-policy.md +61 -0
- package/opencode-config/managed/opencode.json +77 -0
- package/opencode-config/opencode.json +211 -0
- package/opencode-config/plugins/devbot-path-guard.js +224 -0
- package/package.json +157 -4
- package/public/assets/DashboardSidebarCollapse-rIuzCmVm.js +1 -0
- package/public/assets/DashboardToolbar-DFCUqZ4Z.js +1 -0
- package/public/assets/ErrorLogModal.vue_vue_type_script_setup_true_lang-BCyNIf1E.js +1 -0
- package/public/assets/ErrorLogsPage-DyYK2Rop.js +1 -0
- package/public/assets/LogSlideover.vue_vue_type_script_setup_true_lang-33xie7TE.js +1 -0
- package/public/assets/OverviewPage-CFfa0uwg.js +1 -0
- package/public/assets/PageGrid-CBROIU87.js +1 -0
- package/public/assets/PeriodRangePicker.vue_vue_type_script_setup_true_lang-DiFUNem7.js +1 -0
- package/public/assets/ProjectDetailPage-CreyvUit.js +1 -0
- package/public/assets/ProjectsPage-CFx8-elJ.js +1 -0
- package/public/assets/RunDetailSlideover.vue_vue_type_script_setup_true_lang-DopYYmCT.js +1 -0
- package/public/assets/RunsPage-CRnnXU4P.js +1 -0
- package/public/assets/Select-Bvx32Diz.js +1 -0
- package/public/assets/SelectMenu-BjMxc4VB.js +1 -0
- package/public/assets/SettingsPage-Bb6LM3wO.js +60 -0
- package/public/assets/TimeCell.vue_vue_type_script_setup_true_lang-DW_UB0Fu.js +1 -0
- package/public/assets/UsagePage-BLMDEsv-.js +332 -0
- package/public/assets/UsagePage-DodEeAjB.css +1 -0
- package/public/assets/VolumesPage-CxcLc3L2.js +1 -0
- package/public/assets/WebhookConfigPanel.vue_vue_type_script_setup_true_lang-B2V2GF6b.js +1 -0
- package/public/assets/WorkItemCell.vue_vue_type_script_setup_true_lang-BQn6vTjd.js +1 -0
- package/public/assets/index-BMbMibyb.js +4 -0
- package/public/assets/index-C5RBBrPj.js +30 -0
- package/public/assets/index-CgX29UgC.css +1 -0
- package/public/assets/usage-format-jsb31_O8.js +1 -0
- package/public/assets/useManagedProjects-8rtjRqVJ.js +1 -0
- package/public/assets/useRouteTab-DfwrcMic.js +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/public/index.html +15 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
import { log } from "./logger.js";
|
|
3
|
+
import { encrypt, decryptOrPlaintext } from "./crypto-util.js";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { WebhookEndpointSchema, } from "./types/outbound-webhook.js";
|
|
6
|
+
// Outbound webhook endpoints + their delivery queue. Same DB file as the other
|
|
7
|
+
// stores (devbot.db, 0600). Endpoints are keyed by (project_id, name); global
|
|
8
|
+
// endpoints reuse the sentinel scope id 0 (GLOBAL_SCOPE_ID), exactly like
|
|
9
|
+
// global MCP servers. The signing secret is encrypted at rest with SESSION_KEY.
|
|
10
|
+
//
|
|
11
|
+
// Deliveries are durable rows: emit() inserts one per matching endpoint, a
|
|
12
|
+
// worker claims due rows (atomic pending→delivering CAS), and settles them.
|
|
13
|
+
// A failed delivery is re-queued with a backoff until retries are exhausted.
|
|
14
|
+
const SEC = () => Math.floor(Date.now() / 1000);
|
|
15
|
+
export function initOutboundWebhookTables() {
|
|
16
|
+
// Belt-and-braces: migration 0020 is authoritative; keep the idempotent
|
|
17
|
+
// CREATE so a test DB opened without migrate() still works.
|
|
18
|
+
db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS outbound_webhooks (
|
|
20
|
+
project_id INTEGER NOT NULL,
|
|
21
|
+
name TEXT NOT NULL,
|
|
22
|
+
url TEXT NOT NULL,
|
|
23
|
+
events TEXT NOT NULL,
|
|
24
|
+
headers TEXT NOT NULL DEFAULT '{}',
|
|
25
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
26
|
+
secret TEXT NOT NULL,
|
|
27
|
+
created_at INTEGER NOT NULL,
|
|
28
|
+
updated_at INTEGER NOT NULL,
|
|
29
|
+
PRIMARY KEY (project_id, name)
|
|
30
|
+
)
|
|
31
|
+
`);
|
|
32
|
+
db.exec(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS outbound_webhook_deliveries (
|
|
34
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
35
|
+
project_id INTEGER NOT NULL,
|
|
36
|
+
webhook_name TEXT NOT NULL,
|
|
37
|
+
event TEXT NOT NULL,
|
|
38
|
+
payload TEXT NOT NULL,
|
|
39
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
40
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
next_attempt_at INTEGER NOT NULL,
|
|
42
|
+
response_status INTEGER,
|
|
43
|
+
response_body TEXT NOT NULL DEFAULT '',
|
|
44
|
+
last_error TEXT NOT NULL DEFAULT '',
|
|
45
|
+
created_at INTEGER NOT NULL,
|
|
46
|
+
updated_at INTEGER NOT NULL
|
|
47
|
+
)
|
|
48
|
+
`);
|
|
49
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_outbound_deliveries_due ON outbound_webhook_deliveries(status, next_attempt_at)");
|
|
50
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_outbound_deliveries_webhook ON outbound_webhook_deliveries(project_id, webhook_name)");
|
|
51
|
+
}
|
|
52
|
+
export function newWebhookSecret() {
|
|
53
|
+
return `whsec_${randomBytes(32).toString("base64url")}`;
|
|
54
|
+
}
|
|
55
|
+
function parseEvents(raw) {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function parseHeaders(raw) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(raw);
|
|
67
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
68
|
+
? parsed
|
|
69
|
+
: {};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function toStored(r) {
|
|
76
|
+
return {
|
|
77
|
+
projectId: r.project_id,
|
|
78
|
+
name: r.name,
|
|
79
|
+
url: r.url,
|
|
80
|
+
events: parseEvents(r.events),
|
|
81
|
+
headers: parseHeaders(r.headers),
|
|
82
|
+
enabled: r.enabled === 1,
|
|
83
|
+
secret: decryptOrPlaintext(r.secret),
|
|
84
|
+
createdAt: r.created_at,
|
|
85
|
+
updatedAt: r.updated_at,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function upsertWebhook(projectId, name, endpoint, secret) {
|
|
89
|
+
// Re-validate at the store boundary — the API layer validates too, but this
|
|
90
|
+
// is the trust boundary for the delivery worker.
|
|
91
|
+
const v = WebhookEndpointSchema.safeParse(endpoint);
|
|
92
|
+
if (!v.success)
|
|
93
|
+
throw new Error("invalid webhook endpoint");
|
|
94
|
+
const now = SEC();
|
|
95
|
+
db.prepare(`INSERT OR REPLACE INTO outbound_webhooks
|
|
96
|
+
(project_id, name, url, events, headers, enabled, secret, created_at, updated_at)
|
|
97
|
+
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
98
|
+
COALESCE((SELECT created_at FROM outbound_webhooks WHERE project_id = ? AND name = ?), ?),
|
|
99
|
+
?)`).run(projectId, name, v.data.url, JSON.stringify(v.data.events), JSON.stringify(v.data.headers), v.data.enabled ? 1 : 0, encrypt(secret), projectId, name, now, now);
|
|
100
|
+
}
|
|
101
|
+
export function getWebhook(projectId, name) {
|
|
102
|
+
const row = db
|
|
103
|
+
.prepare("SELECT * FROM outbound_webhooks WHERE project_id = ? AND name = ?")
|
|
104
|
+
.get(projectId, name);
|
|
105
|
+
return row ? toStored(row) : null;
|
|
106
|
+
}
|
|
107
|
+
export function listWebhooks(projectId) {
|
|
108
|
+
const rows = db
|
|
109
|
+
.prepare("SELECT * FROM outbound_webhooks WHERE project_id = ? ORDER BY name")
|
|
110
|
+
.all(projectId);
|
|
111
|
+
return rows.map(toStored);
|
|
112
|
+
}
|
|
113
|
+
export function deleteWebhook(projectId, name) {
|
|
114
|
+
const r = db
|
|
115
|
+
.prepare("DELETE FROM outbound_webhooks WHERE project_id = ? AND name = ?")
|
|
116
|
+
.run(projectId, name);
|
|
117
|
+
return r.changes > 0;
|
|
118
|
+
}
|
|
119
|
+
export function deleteAllOutboundWebhooksForProject(projectId) {
|
|
120
|
+
db.prepare("DELETE FROM outbound_webhooks WHERE project_id = ?").run(projectId);
|
|
121
|
+
db.prepare("DELETE FROM outbound_webhook_deliveries WHERE project_id = ?").run(projectId);
|
|
122
|
+
}
|
|
123
|
+
// Enabled endpoints in this project's scope (project + global) subscribed to
|
|
124
|
+
// `event`. Global endpoints use scope id 0, which is never a real project id.
|
|
125
|
+
export function matchingWebhooks(projectId, event) {
|
|
126
|
+
const rows = db
|
|
127
|
+
.prepare("SELECT * FROM outbound_webhooks WHERE enabled = 1 AND project_id IN (?, 0) ORDER BY project_id, name")
|
|
128
|
+
.all(projectId);
|
|
129
|
+
return rows.map(toStored).filter((w) => w.events.includes(event));
|
|
130
|
+
}
|
|
131
|
+
// ---- Delivery queue --------------------------------------------------------
|
|
132
|
+
export function enqueueDelivery(projectId, webhookName, event, payload) {
|
|
133
|
+
const now = SEC();
|
|
134
|
+
const r = db
|
|
135
|
+
.prepare(`INSERT INTO outbound_webhook_deliveries
|
|
136
|
+
(project_id, webhook_name, event, payload, status, attempts, next_attempt_at, created_at, updated_at)
|
|
137
|
+
VALUES (?, ?, ?, ?, 'pending', 0, ?, ?, ?)`)
|
|
138
|
+
.run(projectId, webhookName, event, payload, Date.now(), now, now);
|
|
139
|
+
return Number(r.lastInsertRowid);
|
|
140
|
+
}
|
|
141
|
+
function toDeliverySummary(r) {
|
|
142
|
+
return {
|
|
143
|
+
id: r.id,
|
|
144
|
+
projectId: r.project_id,
|
|
145
|
+
webhookName: r.webhook_name,
|
|
146
|
+
event: r.event,
|
|
147
|
+
status: r.status,
|
|
148
|
+
attempts: r.attempts,
|
|
149
|
+
nextAttemptAt: r.next_attempt_at,
|
|
150
|
+
responseStatus: r.response_status,
|
|
151
|
+
lastError: r.last_error,
|
|
152
|
+
createdAt: r.created_at,
|
|
153
|
+
updatedAt: r.updated_at,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function toDeliveryRow(r) {
|
|
157
|
+
return { ...toDeliverySummary(r), payload: r.payload, responseBody: r.response_body };
|
|
158
|
+
}
|
|
159
|
+
// Claim due pending rows (atomic pending→delivering CAS). Returns the claimed
|
|
160
|
+
// rows so the worker can deliver them. `nowMs` is explicit for testability.
|
|
161
|
+
export function claimDueDeliveries(limit, nowMs) {
|
|
162
|
+
const rows = db
|
|
163
|
+
.prepare(`SELECT * FROM outbound_webhook_deliveries
|
|
164
|
+
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
165
|
+
ORDER BY id ASC LIMIT ?`)
|
|
166
|
+
.all(nowMs, limit);
|
|
167
|
+
const claimed = [];
|
|
168
|
+
const upd = db.prepare("UPDATE outbound_webhook_deliveries SET status = 'delivering', updated_at = ? WHERE id = ? AND status = 'pending'");
|
|
169
|
+
for (const r of rows) {
|
|
170
|
+
if (upd.run(SEC(), r.id).changes === 0)
|
|
171
|
+
continue; // CAS lost
|
|
172
|
+
claimed.push(toDeliveryRow({ ...r, status: "delivering" }));
|
|
173
|
+
}
|
|
174
|
+
return claimed;
|
|
175
|
+
}
|
|
176
|
+
export function settleDeliverySucceeded(id, responseStatus, responseBody) {
|
|
177
|
+
db.prepare(`UPDATE outbound_webhook_deliveries
|
|
178
|
+
SET status = 'succeeded', attempts = attempts + 1, response_status = ?, response_body = ?,
|
|
179
|
+
last_error = '', updated_at = ?
|
|
180
|
+
WHERE id = ?`).run(responseStatus, responseBody.slice(0, 2000), SEC(), id);
|
|
181
|
+
}
|
|
182
|
+
// Settle a failed attempt. `retryAt` non-null re-queues as pending for that
|
|
183
|
+
// time; null marks the delivery terminally failed (retries exhausted).
|
|
184
|
+
export function settleDeliveryFailed(id, input) {
|
|
185
|
+
db.prepare(`UPDATE outbound_webhook_deliveries
|
|
186
|
+
SET status = ?, attempts = attempts + 1, response_status = ?, response_body = ?,
|
|
187
|
+
last_error = ?, next_attempt_at = ?, updated_at = ?
|
|
188
|
+
WHERE id = ?`).run(input.retryAt === null ? "failed" : "pending", input.responseStatus, input.responseBody.slice(0, 2000), input.error.slice(0, 2000), input.retryAt ?? Date.now(), SEC(), id);
|
|
189
|
+
}
|
|
190
|
+
// Crash recovery: a delivery claimed but never settled goes back to pending.
|
|
191
|
+
export function resetStuckDeliveries() {
|
|
192
|
+
const r = db
|
|
193
|
+
.prepare("UPDATE outbound_webhook_deliveries SET status = 'pending', updated_at = ? WHERE status = 'delivering'")
|
|
194
|
+
.run(SEC());
|
|
195
|
+
return Number(r.changes);
|
|
196
|
+
}
|
|
197
|
+
export function listDeliveries(projectId, webhookName, limit, offset) {
|
|
198
|
+
const rows = db
|
|
199
|
+
.prepare(`SELECT * FROM outbound_webhook_deliveries
|
|
200
|
+
WHERE project_id = ? AND webhook_name = ?
|
|
201
|
+
ORDER BY id DESC LIMIT ? OFFSET ?`)
|
|
202
|
+
.all(projectId, webhookName, limit, offset);
|
|
203
|
+
return rows.map(toDeliverySummary);
|
|
204
|
+
}
|
|
205
|
+
export function countDeliveries(projectId, webhookName) {
|
|
206
|
+
const row = db
|
|
207
|
+
.prepare("SELECT COUNT(*) AS c FROM outbound_webhook_deliveries WHERE project_id = ? AND webhook_name = ?")
|
|
208
|
+
.get(projectId, webhookName);
|
|
209
|
+
return row.c;
|
|
210
|
+
}
|
|
211
|
+
export function getDelivery(id) {
|
|
212
|
+
const row = db.prepare("SELECT * FROM outbound_webhook_deliveries WHERE id = ?").get(id);
|
|
213
|
+
return row ? toDeliveryRow(row) : null;
|
|
214
|
+
}
|
|
215
|
+
// Re-queue a terminal delivery for another attempt (manual replay from the UI).
|
|
216
|
+
export function replayDelivery(id) {
|
|
217
|
+
const r = db
|
|
218
|
+
.prepare("UPDATE outbound_webhook_deliveries SET status = 'pending', next_attempt_at = ?, updated_at = ? WHERE id = ?")
|
|
219
|
+
.run(Date.now(), SEC(), id);
|
|
220
|
+
return r.changes > 0;
|
|
221
|
+
}
|
|
222
|
+
export function purgeDeliveriesOlderThan(days) {
|
|
223
|
+
const cutoff = SEC() - days * 24 * 3600;
|
|
224
|
+
const r = db.prepare("DELETE FROM outbound_webhook_deliveries WHERE created_at < ?").run(cutoff);
|
|
225
|
+
if (r.changes > 0)
|
|
226
|
+
log.debug("outbound delivery purge", { purged: r.changes, days });
|
|
227
|
+
return Number(r.changes);
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=outbound-webhook-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-webhook-store.js","sourceRoot":"","sources":["../src/outbound-webhook-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { createHmac, randomUUID } from "node:crypto";
|
|
2
|
+
import { lookup } from "node:dns/promises";
|
|
3
|
+
import { isIP } from "node:net";
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
import { log } from "./logger.js";
|
|
6
|
+
import { matchingWebhooks, enqueueDelivery, getWebhook, claimDueDeliveries, settleDeliverySucceeded, settleDeliveryFailed, resetStuckDeliveries, purgeDeliveriesOlderThan, } from "./outbound-webhook-store.js";
|
|
7
|
+
import { getProject } from "./token-store.js";
|
|
8
|
+
import { getPlatform } from "./platform/index.js";
|
|
9
|
+
// Outbound webhook emitter + delivery worker. Forge-agnostic by construction:
|
|
10
|
+
// the only platform call is the neutral links.workItemUrl. emit* is a purely
|
|
11
|
+
// local DB write (one row per matching endpoint); the worker does all network
|
|
12
|
+
// I/O, so a webhook can never block or fail a run.
|
|
13
|
+
const SIGNATURE_HEADER = "X-DevBot-Signature-256";
|
|
14
|
+
export function signPayload(secret, body) {
|
|
15
|
+
return "sha256=" + createHmac("sha256", secret).update(body).digest("hex");
|
|
16
|
+
}
|
|
17
|
+
// The delivery id carried in the payload; used for the X-DevBot-Delivery header
|
|
18
|
+
// so a receiver can dedupe on the same value it sees in the body.
|
|
19
|
+
function deliveryIdOf(payload) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(payload);
|
|
22
|
+
return typeof parsed.delivery === "string" ? parsed.delivery : "";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// ---- SSRF guard ------------------------------------------------------------
|
|
29
|
+
// Outbound URLs are admin-configured, but a URL like http://169.254.169.254/
|
|
30
|
+
// can reach cloud metadata. Block loopback/link-local/private/CGNAT/ULA/
|
|
31
|
+
// multicast/broadcast by default; WEBHOOK_ALLOW_PRIVATE_HOSTS=true opts in to
|
|
32
|
+
// internal CI/messaging endpoints (see docs/outbound-webhooks.md).
|
|
33
|
+
function ipv4Blocked(ip) {
|
|
34
|
+
const parts = ip.split(".").map((n) => Number(n));
|
|
35
|
+
const [a, b] = parts;
|
|
36
|
+
if (a === 0 || a === 10 || a === 127)
|
|
37
|
+
return true;
|
|
38
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
39
|
+
return true; // CGNAT 100.64/10
|
|
40
|
+
if (a === 169 && b === 254)
|
|
41
|
+
return true; // link-local
|
|
42
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
43
|
+
return true; // private
|
|
44
|
+
if (a === 192 && b === 168)
|
|
45
|
+
return true; // private
|
|
46
|
+
if (a === 192 && b === 0)
|
|
47
|
+
return true; // 192.0.0.0/24 + 192.0.2.0/24
|
|
48
|
+
if (a >= 224)
|
|
49
|
+
return true; // multicast + reserved + broadcast
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
function ipv6Blocked(ip) {
|
|
53
|
+
const v = ip.toLowerCase().split("%")[0];
|
|
54
|
+
if (v === "::1" || v === "::")
|
|
55
|
+
return true;
|
|
56
|
+
if (v.startsWith("fe80"))
|
|
57
|
+
return true; // link-local
|
|
58
|
+
const first = v.split(":")[0];
|
|
59
|
+
const n = parseInt(first || "0", 16);
|
|
60
|
+
if ((n & 0xfe00) === 0xfc00)
|
|
61
|
+
return true; // ULA fc00::/7
|
|
62
|
+
if ((n & 0xff00) === 0xff00)
|
|
63
|
+
return true; // multicast ff00::/8
|
|
64
|
+
// IPv4-mapped (::ffff:a.b.c.d) — classify on the embedded v4.
|
|
65
|
+
const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(v);
|
|
66
|
+
if (mapped)
|
|
67
|
+
return ipv4Blocked(mapped[1]);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
export function isBlockedAddress(addr) {
|
|
71
|
+
const v = isIP(addr);
|
|
72
|
+
if (v === 4)
|
|
73
|
+
return ipv4Blocked(addr);
|
|
74
|
+
if (v === 6)
|
|
75
|
+
return ipv6Blocked(addr);
|
|
76
|
+
return true; // not an IP literal → treat as blocked/unknown
|
|
77
|
+
}
|
|
78
|
+
// Resolve a URL's host and reject when any resolved address is blocked (unless
|
|
79
|
+
// private hosts are explicitly allowed). Returns an error string, or null when
|
|
80
|
+
// the target is safe to call.
|
|
81
|
+
export async function checkTargetAllowed(rawUrl) {
|
|
82
|
+
let url;
|
|
83
|
+
try {
|
|
84
|
+
url = new URL(rawUrl);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return "invalid url";
|
|
88
|
+
}
|
|
89
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
90
|
+
return "url must be http(s)";
|
|
91
|
+
if (config.WEBHOOK_ALLOW_PRIVATE_HOSTS)
|
|
92
|
+
return null;
|
|
93
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
94
|
+
if (isIP(host))
|
|
95
|
+
return isBlockedAddress(host) ? "blocked address" : null;
|
|
96
|
+
let addrs;
|
|
97
|
+
try {
|
|
98
|
+
addrs = await lookup(host, { all: true, verbatim: true });
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return "dns resolution failed";
|
|
102
|
+
}
|
|
103
|
+
if (addrs.length === 0)
|
|
104
|
+
return "dns resolution failed";
|
|
105
|
+
if (addrs.some((a) => isBlockedAddress(a.address)))
|
|
106
|
+
return "blocked address";
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
export function buildRunOutcomePayload(event) {
|
|
110
|
+
return JSON.stringify(event);
|
|
111
|
+
}
|
|
112
|
+
export function buildMrPayload(event) {
|
|
113
|
+
return JSON.stringify(event);
|
|
114
|
+
}
|
|
115
|
+
function workItemUrl(projectId, workItemNumber) {
|
|
116
|
+
const projectPath = getProject(projectId)?.projectPath ?? "";
|
|
117
|
+
try {
|
|
118
|
+
return getPlatform().links.workItemUrl(projectPath, workItemNumber);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function projectPath(projectId) {
|
|
125
|
+
return getProject(projectId)?.projectPath ?? "";
|
|
126
|
+
}
|
|
127
|
+
// Resolve the concrete endpoints (project + global) an event should be sent to.
|
|
128
|
+
// Kept exported for tests and the "test delivery" route.
|
|
129
|
+
export function resolveMatchingEndpoints(projectId, event) {
|
|
130
|
+
return matchingWebhooks(projectId, event);
|
|
131
|
+
}
|
|
132
|
+
// Persist one delivery row per matching endpoint. Best-effort: a store failure
|
|
133
|
+
// is logged and never propagates into the run.
|
|
134
|
+
export function emitEvent(projectId, event, payloadFor) {
|
|
135
|
+
let count = 0;
|
|
136
|
+
try {
|
|
137
|
+
for (const endpoint of resolveMatchingEndpoints(projectId, event)) {
|
|
138
|
+
const eventPayload = payloadFor();
|
|
139
|
+
eventPayload.delivery = randomUUID();
|
|
140
|
+
enqueueDelivery(projectId, endpoint.name, event, buildRunOutcomePayload(eventPayload));
|
|
141
|
+
count++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
log.warn("outbound webhook emit failed", { projectId, event, err: e });
|
|
146
|
+
}
|
|
147
|
+
return count;
|
|
148
|
+
}
|
|
149
|
+
// A run emits `run.started` once per task lifecycle — only on the first
|
|
150
|
+
// attempt. A transient failure requeues the row with attempts++, so retries
|
|
151
|
+
// don't re-notify subscribers; a crash-replayed row keeps attempts 0 and does.
|
|
152
|
+
export function shouldEmitRunStarted(attempts) {
|
|
153
|
+
return attempts === 0;
|
|
154
|
+
}
|
|
155
|
+
// Emit `run.started` for a task that is about to execute. `run.status` is
|
|
156
|
+
// "running" with no failure detail (none exists yet).
|
|
157
|
+
export function emitRunStarted(input) {
|
|
158
|
+
emitEvent(input.projectId, "run.started", () => baseEvent(input, "run.started", "running", {}));
|
|
159
|
+
}
|
|
160
|
+
export function emitRunOutcome(input, outcome) {
|
|
161
|
+
const event = outcome.kind === "ok" ? "run.succeeded" : outcome.kind === "cancelled" ? "run.cancelled" : "run.failed";
|
|
162
|
+
const failure = outcome.failure;
|
|
163
|
+
emitEvent(input.projectId, event, () => baseEvent(input, event, outcome.kind, {
|
|
164
|
+
reason: outcome.reason ?? failure?.message ?? "",
|
|
165
|
+
phase: failure?.phase ?? "",
|
|
166
|
+
kind: failure?.kind ?? "",
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
export function emitMrEvent(input, changeRequest, created) {
|
|
170
|
+
const event = created ? "mr.created" : "mr.updated";
|
|
171
|
+
emitEvent(input.projectId, event, () => ({
|
|
172
|
+
...baseEvent(input, event, "ok", {}),
|
|
173
|
+
changeRequest: { number: changeRequest.number, url: changeRequest.webUrl, created },
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
function baseEvent(input, event, status, opts) {
|
|
177
|
+
return {
|
|
178
|
+
event,
|
|
179
|
+
delivery: "",
|
|
180
|
+
timestamp: new Date().toISOString(),
|
|
181
|
+
project: { id: input.projectId, path: projectPath(input.projectId) },
|
|
182
|
+
workItem: {
|
|
183
|
+
id: input.workItemId,
|
|
184
|
+
number: input.workItemNumber,
|
|
185
|
+
url: workItemUrl(input.projectId, input.workItemNumber),
|
|
186
|
+
},
|
|
187
|
+
run: {
|
|
188
|
+
mode: input.mode,
|
|
189
|
+
model: input.model,
|
|
190
|
+
variant: input.variant,
|
|
191
|
+
status,
|
|
192
|
+
phase: opts.phase ?? "",
|
|
193
|
+
kind: opts.kind ?? "",
|
|
194
|
+
reason: opts.reason ?? "",
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// ---- Delivery worker -------------------------------------------------------
|
|
199
|
+
async function deliverOnce(endpoint, event, delivery, payload) {
|
|
200
|
+
const blocked = await checkTargetAllowed(endpoint.url);
|
|
201
|
+
if (blocked)
|
|
202
|
+
return { ok: false, responseStatus: null, responseBody: "", error: blocked };
|
|
203
|
+
const controller = new AbortController();
|
|
204
|
+
const timer = setTimeout(() => controller.abort(), config.WEBHOOK_DELIVERY_TIMEOUT_MS);
|
|
205
|
+
try {
|
|
206
|
+
const res = await fetch(endpoint.url, {
|
|
207
|
+
method: "POST",
|
|
208
|
+
redirect: "manual",
|
|
209
|
+
signal: controller.signal,
|
|
210
|
+
headers: {
|
|
211
|
+
"Content-Type": "application/json",
|
|
212
|
+
"User-Agent": "DevBot-Webhook/1.0",
|
|
213
|
+
"X-DevBot-Event": event,
|
|
214
|
+
"X-DevBot-Delivery": delivery,
|
|
215
|
+
[SIGNATURE_HEADER]: signPayload(endpoint.secret, payload),
|
|
216
|
+
...endpoint.headers,
|
|
217
|
+
},
|
|
218
|
+
body: payload,
|
|
219
|
+
});
|
|
220
|
+
const body = await res.text().catch(() => "");
|
|
221
|
+
if (res.status >= 200 && res.status < 300) {
|
|
222
|
+
return { ok: true, responseStatus: res.status, responseBody: body, error: "" };
|
|
223
|
+
}
|
|
224
|
+
if (res.status >= 300 && res.status < 400) {
|
|
225
|
+
return { ok: false, responseStatus: res.status, responseBody: body, error: `redirect not followed (${res.status})` };
|
|
226
|
+
}
|
|
227
|
+
return { ok: false, responseStatus: res.status, responseBody: body, error: `http ${res.status}` };
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
const msg = e instanceof Error && e.name === "AbortError" ? "timeout" : e instanceof Error ? e.message : String(e);
|
|
231
|
+
return { ok: false, responseStatus: null, responseBody: "", error: msg };
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Deliver every due row once. Exported so tests can drive it without timers.
|
|
238
|
+
export async function processDueDeliveries(deps = {}) {
|
|
239
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
240
|
+
const due = claimDueDeliveries(deps.limit ?? 20, now);
|
|
241
|
+
for (const delivery of due) {
|
|
242
|
+
const endpoint = getWebhook(delivery.projectId, delivery.webhookName);
|
|
243
|
+
if (!endpoint) {
|
|
244
|
+
// Endpoint removed after enqueue — nothing to deliver.
|
|
245
|
+
settleDeliveryFailed(delivery.id, {
|
|
246
|
+
responseStatus: null,
|
|
247
|
+
responseBody: "",
|
|
248
|
+
error: "endpoint removed",
|
|
249
|
+
retryAt: null,
|
|
250
|
+
});
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const result = await deliverOnce(endpoint, delivery.event, deliveryIdOf(delivery.payload), delivery.payload);
|
|
254
|
+
if (result.ok) {
|
|
255
|
+
settleDeliverySucceeded(delivery.id, result.responseStatus, result.responseBody);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const willRetry = delivery.attempts + 1 < config.WEBHOOK_RETRY_MAX;
|
|
259
|
+
settleDeliveryFailed(delivery.id, {
|
|
260
|
+
responseStatus: result.responseStatus,
|
|
261
|
+
responseBody: result.responseBody,
|
|
262
|
+
error: result.error,
|
|
263
|
+
retryAt: willRetry ? Date.now() + config.WEBHOOK_RETRY_BACKOFF_MS * (delivery.attempts + 1) : null,
|
|
264
|
+
});
|
|
265
|
+
log.warn("outbound webhook delivery failed", {
|
|
266
|
+
projectId: delivery.projectId,
|
|
267
|
+
webhook: delivery.webhookName,
|
|
268
|
+
event: delivery.event,
|
|
269
|
+
status: result.responseStatus,
|
|
270
|
+
willRetry,
|
|
271
|
+
error: result.error,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return due.length;
|
|
275
|
+
}
|
|
276
|
+
let workerTimer = null;
|
|
277
|
+
export function startWebhookWorker() {
|
|
278
|
+
if (workerTimer)
|
|
279
|
+
return;
|
|
280
|
+
const stuck = resetStuckDeliveries();
|
|
281
|
+
if (stuck > 0)
|
|
282
|
+
log.info("reset stuck outbound deliveries", { count: stuck });
|
|
283
|
+
workerTimer = setInterval(() => {
|
|
284
|
+
void processDueDeliveries().catch((e) => log.warn("outbound webhook worker failed", { err: e }));
|
|
285
|
+
}, config.WEBHOOK_POLL_INTERVAL_MS);
|
|
286
|
+
workerTimer.unref();
|
|
287
|
+
log.info("outbound webhook worker scheduled", {
|
|
288
|
+
intervalMs: config.WEBHOOK_POLL_INTERVAL_MS,
|
|
289
|
+
retryMax: config.WEBHOOK_RETRY_MAX,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
export function stopWebhookWorker() {
|
|
293
|
+
if (workerTimer) {
|
|
294
|
+
clearInterval(workerTimer);
|
|
295
|
+
workerTimer = null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Test helper: deliver one endpoint synchronously (the UI "Test" button).
|
|
299
|
+
export async function testDelivery(endpoint) {
|
|
300
|
+
const deliveryId = randomUUID();
|
|
301
|
+
const payload = buildRunOutcomePayload({
|
|
302
|
+
event: endpoint.events[0] ?? "run.succeeded",
|
|
303
|
+
delivery: deliveryId,
|
|
304
|
+
timestamp: new Date().toISOString(),
|
|
305
|
+
project: { id: endpoint.projectId, path: projectPath(endpoint.projectId) },
|
|
306
|
+
workItem: { id: 0, number: 0, url: "" },
|
|
307
|
+
run: { mode: "test", model: "", variant: "", status: "test", phase: "", kind: "", reason: "test delivery" },
|
|
308
|
+
});
|
|
309
|
+
const result = await deliverOnce(endpoint, endpoint.events[0] ?? "run.succeeded", deliveryId, payload);
|
|
310
|
+
return { ok: result.ok, status: result.responseStatus, error: result.error };
|
|
311
|
+
}
|
|
312
|
+
// Re-export for the delivery-log route.
|
|
313
|
+
export { purgeDeliveriesOlderThan };
|
|
314
|
+
//# sourceMappingURL=outbound-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-webhook.js","sourceRoot":"","sources":["../src/outbound-webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,GAEzB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,mDAAmD;AAEnD,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAElD,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAY;IACtD,OAAO,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED,gFAAgF;AAChF,kEAAkE;AAClE,SAAS,YAAY,CAAC,OAAe;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA2B,CAAC;QAC7D,OAAO,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,6EAA6E;AAC7E,yEAAyE;AACzE,8EAA8E;AAC9E,mEAAmE;AAEnE,SAAS,WAAW,CAAC,EAAU;IAC7B,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAyC,CAAC;IACzD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAClD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,kBAAkB;IACrE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,aAAa;IACtD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,CAAC,UAAU;IAC5D,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,UAAU;IACnD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IACrE,IAAI,CAAC,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,mCAAmC;IAC9D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,EAAU;IAC7B,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IAC1C,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,aAAa;IACpD,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IAC/B,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,eAAe;IACzD,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,qBAAqB;IAC/D,8DAA8D;IAC9D,MAAM,MAAM,GAAG,+BAA+B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvD,IAAI,MAAM;QAAE,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC;IAC3C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACrB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC,CAAC,+CAA+C;AAC9D,CAAC;AAED,+EAA+E;AAC/E,+EAA+E;AAC/E,8BAA8B;AAC9B,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc;IACrD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,aAAa,CAAC;IACvB,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,qBAAqB,CAAC;IACxF,IAAI,MAAM,CAAC,2BAA2B;QAAE,OAAO,IAAI,CAAC;IACpD,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAClD,IAAI,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,IAAI,KAAiC,CAAC;IACtC,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,uBAAuB,CAAC;IACjC,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,uBAAuB,CAAC;IACvD,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAC7E,OAAO,IAAI,CAAC;AACd,CAAC;AAsBD,MAAM,UAAU,sBAAsB,CAAC,KAAsB;IAC3D,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAaD,SAAS,WAAW,CAAC,SAAiB,EAAE,cAAsB;IAC5D,MAAM,WAAW,GAAG,UAAU,CAAC,SAAS,CAAC,EAAE,WAAW,IAAI,EAAE,CAAC;IAC7D,IAAI,CAAC;QACH,OAAO,WAAW,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,SAAiB;IACpC,OAAO,UAAU,CAAC,SAAS,CAAC,EAAE,WAAW,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,gFAAgF;AAChF,yDAAyD;AACzD,MAAM,UAAU,wBAAwB,CAAC,SAAiB,EAAE,KAAoB;IAC9E,OAAO,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED,+EAA+E;AAC/E,+CAA+C;AAC/C,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,KAAoB,EAAE,UAAiC;IAClG,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,CAAC;QACH,KAAK,MAAM,QAAQ,IAAI,wBAAwB,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC;YAClE,MAAM,YAAY,GAAG,UAAU,EAAE,CAAC;YAClC,YAAY,CAAC,QAAQ,GAAG,UAAU,EAAE,CAAC;YACrC,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,sBAAsB,CAAC,YAAY,CAAC,CAAC,CAAC;YACvF,KAAK,EAAE,CAAC;QACV,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,IAAI,CAAC,8BAA8B,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAU,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,4EAA4E;AAC5E,+EAA+E;AAC/E,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,OAAO,QAAQ,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,0EAA0E;AAC1E,sDAAsD;AACtD,MAAM,UAAU,cAAc,CAAC,KAAmB;IAChD,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,KAAmB,EACnB,OAAsG;IAEtG,MAAM,KAAK,GACT,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC;IAC1G,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAChC,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CACrC,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE;QACpC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,EAAE,OAAO,IAAI,EAAE;QAChD,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE;QAC3B,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,EAAE;KAC1B,CAAC,CACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,KAAmB,EACnB,aAAiD,EACjD,OAAgB;IAEhB,MAAM,KAAK,GAAkB,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;IACnE,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QACvC,GAAG,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACpC,aAAa,EAAE,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE;KACpF,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,SAAS,CAChB,KAAmB,EACnB,KAAoB,EACpB,MAAc,EACd,IAAwD;IAExD,OAAO;QACL,KAAK;QACL,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,OAAO,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;QACpE,QAAQ,EAAE;YACR,EAAE,EAAE,KAAK,CAAC,UAAU;YACpB,MAAM,EAAE,KAAK,CAAC,cAAc;YAC5B,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,cAAc,CAAC;SACxD;QACD,GAAG,EAAE;YACH,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,MAAM;YACN,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;YACvB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE;SAC1B;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E,KAAK,UAAU,WAAW,CAAC,QAAuB,EAAE,KAAa,EAAE,QAAgB,EAAE,OAAe;IAMlG,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAE1F,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,2BAA2B,CAAC,CAAC;IACvF,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE;YACpC,MAAM,EAAE,MAAM;YACd,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,YAAY,EAAE,oBAAoB;gBAClC,gBAAgB,EAAE,KAAK;gBACvB,mBAAmB,EAAE,QAAQ;gBAC7B,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;gBACzD,GAAG,QAAQ,CAAC,OAAO;aACpB;YACD,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YAC1C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QACjF,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YAC1C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,0BAA0B,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;QACvH,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;IACpG,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnH,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAC3E,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAOD,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAA0B,EAAE;IACrE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/C,MAAM,GAAG,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IACtD,KAAK,MAAM,QAAQ,IAAI,GAAG,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,uDAAuD;YACvD,oBAAoB,CAAC,QAAQ,CAAC,EAAE,EAAE;gBAChC,cAAc,EAAE,IAAI;gBACpB,YAAY,EAAE,EAAE;gBAChB,KAAK,EAAE,kBAAkB;gBACzB,OAAO,EAAE,IAAI;aACd,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC7G,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,uBAAuB,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;YACjF,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,iBAAiB,CAAC;QACnE,oBAAoB,CAAC,QAAQ,CAAC,EAAE,EAAE;YAChC,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,wBAAwB,GAAG,CAAC,QAAQ,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;SACnG,CAAC,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,kCAAkC,EAAE;YAC3C,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,OAAO,EAAE,QAAQ,CAAC,WAAW;YAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,MAAM,EAAE,MAAM,CAAC,cAAc;YAC7B,SAAS;YACT,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,IAAI,WAAW,GAA0C,IAAI,CAAC;AAE9D,MAAM,UAAU,kBAAkB;IAChC,IAAI,WAAW;QAAE,OAAO;IACxB,MAAM,KAAK,GAAG,oBAAoB,EAAE,CAAC;IACrC,IAAI,KAAK,GAAG,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,iCAAiC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7E,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,KAAK,oBAAoB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,gCAAgC,EAAE,EAAE,GAAG,EAAE,CAAU,EAAE,CAAC,CAAC,CAAC;IAC5G,CAAC,EAAE,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACpC,WAAW,CAAC,KAAK,EAAE,CAAC;IACpB,GAAG,CAAC,IAAI,CAAC,mCAAmC,EAAE;QAC5C,UAAU,EAAE,MAAM,CAAC,wBAAwB;QAC3C,QAAQ,EAAE,MAAM,CAAC,iBAAiB;KACnC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,IAAI,WAAW,EAAE,CAAC;QAChB,aAAa,CAAC,WAAW,CAAC,CAAC;QAC3B,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAuB;IACxD,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,sBAAsB,CAAC;QACrC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,eAAe;QAC5C,QAAQ,EAAE,UAAU;QACpB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,OAAO,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAC1E,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;QACvC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE;KAC5G,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACvG,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AAC/E,CAAC;AAED,wCAAwC;AACxC,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
|