@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,138 @@
|
|
|
1
|
+
import { discoverOAuthServerInfo, refreshAuthorization } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
2
|
+
import { getMcpAuthEntry, listMcpAuthScopes, upsertMcpAuth } from "../mcp-auth-store.js";
|
|
3
|
+
import { listMcpServers } from "../mcp-config-store.js";
|
|
4
|
+
import { redact } from "../redact.js";
|
|
5
|
+
import { log } from "../logger.js";
|
|
6
|
+
// Host-side OAuth token refresh for remote MCP servers.
|
|
7
|
+
//
|
|
8
|
+
// Why this exists: opencode refreshes tokens inside the run container, but its
|
|
9
|
+
// refreshed/rotated tokens are written only to the container's mcp-auth.json —
|
|
10
|
+
// which the runner overwrites from the DB before every run. A provider that
|
|
11
|
+
// rotates the refresh token therefore leaves DevBot holding a stale one and the
|
|
12
|
+
// next refresh fails silently. Refreshing here and persisting the rotation back
|
|
13
|
+
// to project_mcp_auth closes that loop.
|
|
14
|
+
//
|
|
15
|
+
// One primitive, three triggers: the periodic sweep (src/sweeps.ts), the pre-run
|
|
16
|
+
// safety net (prepareMcpRun), and the manual admin endpoint. None of them throw —
|
|
17
|
+
// a failed refresh logs (redacted) and leaves the stored row untouched.
|
|
18
|
+
const REFRESH_TIMEOUT_MS = 10_000;
|
|
19
|
+
// Serialize refreshes per (scopeId, name): a token provider that rotates on
|
|
20
|
+
// refresh must never have two concurrent refresh calls racing for the same
|
|
21
|
+
// rotating refresh token (the loser would invalidate the winner's token).
|
|
22
|
+
const inflight = new Set();
|
|
23
|
+
function message(e) {
|
|
24
|
+
return e instanceof Error ? e.message : String(e);
|
|
25
|
+
}
|
|
26
|
+
// Resolve `{env:VAR}` placeholders against the host env (the config values are
|
|
27
|
+
// authored for the container, but the host runs the refresh). Returns null when a
|
|
28
|
+
// placeholder cannot be resolved so the caller skips instead of sending a literal.
|
|
29
|
+
function resolveEnv(value) {
|
|
30
|
+
const out = value.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => process.env[name] ?? `{env:${name}}`);
|
|
31
|
+
return out.includes("{env:") ? null : out;
|
|
32
|
+
}
|
|
33
|
+
// Client credentials for the token request: pasted clientInfo wins (it is what
|
|
34
|
+
// the provider registered with), else the admin-configured oauth block. The
|
|
35
|
+
// latter may carry {env:VAR} placeholders; an unresolved placeholder means we
|
|
36
|
+
// cannot authenticate and must skip.
|
|
37
|
+
function clientInformation(entry, oauth) {
|
|
38
|
+
const info = entry.clientInfo;
|
|
39
|
+
if (info?.clientId) {
|
|
40
|
+
return { client_id: info.clientId, ...(info.clientSecret ? { client_secret: info.clientSecret } : {}) };
|
|
41
|
+
}
|
|
42
|
+
if (oauth && typeof oauth === "object" && "clientId" in oauth) {
|
|
43
|
+
const o = oauth;
|
|
44
|
+
const clientId = resolveEnv(o.clientId);
|
|
45
|
+
if (!clientId)
|
|
46
|
+
return null;
|
|
47
|
+
const clientSecret = o.clientSecret ? resolveEnv(o.clientSecret) : undefined;
|
|
48
|
+
if (o.clientSecret && !clientSecret)
|
|
49
|
+
return null;
|
|
50
|
+
return { client_id: clientId, ...(clientSecret ? { client_secret: clientSecret } : {}) };
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
// Refresh one stored entry if it has a refresh token and is due. Never throws.
|
|
55
|
+
export async function refreshMcpAuthEntry(scopeId, name, opts = {}) {
|
|
56
|
+
const key = `${scopeId}:${name}`;
|
|
57
|
+
if (inflight.has(key))
|
|
58
|
+
return { ok: false, skipped: true, reason: "refresh already in flight" };
|
|
59
|
+
inflight.add(key);
|
|
60
|
+
try {
|
|
61
|
+
const entry = getMcpAuthEntry(scopeId, name);
|
|
62
|
+
if (!entry)
|
|
63
|
+
return { ok: false, skipped: true, reason: "no stored auth entry" };
|
|
64
|
+
const refreshToken = entry.tokens.refreshToken;
|
|
65
|
+
if (!refreshToken)
|
|
66
|
+
return { ok: false, skipped: true, reason: "no refresh token" };
|
|
67
|
+
const now = opts.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
68
|
+
const horizonSec = Math.floor((opts.horizonMs ?? 0) / 1000);
|
|
69
|
+
if (!opts.force &&
|
|
70
|
+
entry.tokens.expiresAt !== undefined &&
|
|
71
|
+
entry.tokens.expiresAt > now + horizonSec) {
|
|
72
|
+
return { ok: true, skipped: true, reason: "not due", expiresAt: entry.tokens.expiresAt };
|
|
73
|
+
}
|
|
74
|
+
const cfg = listMcpServers(scopeId).find((s) => s.name === name);
|
|
75
|
+
if (!cfg || cfg.config.type !== "remote") {
|
|
76
|
+
return { ok: false, skipped: true, reason: "no remote server config" };
|
|
77
|
+
}
|
|
78
|
+
const client = clientInformation(entry, cfg.config.oauth);
|
|
79
|
+
if (!client) {
|
|
80
|
+
return { ok: false, skipped: true, reason: "no client credentials (clientInfo or oauth.clientId)" };
|
|
81
|
+
}
|
|
82
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
83
|
+
const timedFetch = (input, init) => fetchFn(input, { ...init, signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS) });
|
|
84
|
+
const serverInfo = await discoverOAuthServerInfo(cfg.config.url, { fetchFn: timedFetch });
|
|
85
|
+
const resource = serverInfo.resourceMetadata?.resource
|
|
86
|
+
? new URL(serverInfo.resourceMetadata.resource)
|
|
87
|
+
: undefined;
|
|
88
|
+
const tokens = await refreshAuthorization(serverInfo.authorizationServerUrl, {
|
|
89
|
+
...(serverInfo.authorizationServerMetadata ? { metadata: serverInfo.authorizationServerMetadata } : {}),
|
|
90
|
+
clientInformation: client,
|
|
91
|
+
refreshToken,
|
|
92
|
+
...(resource ? { resource } : {}),
|
|
93
|
+
fetchFn: timedFetch,
|
|
94
|
+
});
|
|
95
|
+
// Preserve the previous refresh token when the provider did not rotate it
|
|
96
|
+
// (the SDK guarantees this, but be explicit). Drop a stale expiresAt only when
|
|
97
|
+
// the provider returns a fresh lifetime; otherwise keep the old value.
|
|
98
|
+
const nextTokens = {
|
|
99
|
+
accessToken: tokens.access_token,
|
|
100
|
+
refreshToken: tokens.refresh_token ?? refreshToken,
|
|
101
|
+
...(tokens.expires_in !== undefined
|
|
102
|
+
? { expiresAt: now + tokens.expires_in }
|
|
103
|
+
: entry.tokens.expiresAt !== undefined
|
|
104
|
+
? { expiresAt: entry.tokens.expiresAt }
|
|
105
|
+
: {}),
|
|
106
|
+
...(tokens.scope ?? entry.tokens.scope ? { scope: tokens.scope ?? entry.tokens.scope } : {}),
|
|
107
|
+
};
|
|
108
|
+
upsertMcpAuth(scopeId, name, { ...entry, tokens: nextTokens });
|
|
109
|
+
log.info("mcp token refreshed", { scopeId, name, expiresAt: nextTokens.expiresAt });
|
|
110
|
+
return { ok: true, expiresAt: nextTokens.expiresAt };
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
const reason = redact(message(e));
|
|
114
|
+
log.warn("mcp token refresh failed", { scopeId, name, reason });
|
|
115
|
+
return { ok: false, reason };
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
inflight.delete(key);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Sweep stored auth rows, refreshing those expiring within `horizonMs`.
|
|
122
|
+
// Sequential on purpose: bounded, and refresh endpoints are frequently
|
|
123
|
+
// rate-limited. One row's failure never aborts the sweep. When `scopeIds` is
|
|
124
|
+
// given, only those scopes are swept (the pre-run path limits itself to the
|
|
125
|
+
// global + run project scopes it is about to emit); omitted = every scope.
|
|
126
|
+
export async function refreshDueMcpAuth(horizonMs, scopeIds) {
|
|
127
|
+
const allowed = scopeIds ? new Set(scopeIds) : null;
|
|
128
|
+
for (const { scopeId, name } of listMcpAuthScopes()) {
|
|
129
|
+
if (allowed && !allowed.has(scopeId))
|
|
130
|
+
continue;
|
|
131
|
+
await refreshMcpAuthEntry(scopeId, name, { horizonMs });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Test-only: clear the in-flight guard between cases.
|
|
135
|
+
export function resetRefreshInflightForTest() {
|
|
136
|
+
inflight.clear();
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=oauth-refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-refresh.js","sourceRoot":"","sources":["../../src/mcp/oauth-refresh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
import { log } from "../logger.js";
|
|
3
|
+
import { redact } from "../redact.js";
|
|
4
|
+
import { podman } from "../podman.js";
|
|
5
|
+
import { volumeName, gitExtraHeader, envArgs, logDriverArgs, volumeSelinuxArgs } from "../git.js";
|
|
6
|
+
import { PROJECT_ROOT, validateProjectRelativePath } from "../path-sandbox.js";
|
|
7
|
+
// On-demand clone/fetch of an authorized peer project into the run volume.
|
|
8
|
+
//
|
|
9
|
+
// Layout: /workspace/.devbot-peers/<path_with_namespace>/ — inside the volume
|
|
10
|
+
// but excluded from git via .git/info/exclude (local-only ignore), so the
|
|
11
|
+
// project's own `git add -A` / `git status` never see it. This is the
|
|
12
|
+
// "out of scope of git but accessible by the agent" requirement: the agent can
|
|
13
|
+
// read/grep the peer tree, but it can't be committed by mistake.
|
|
14
|
+
//
|
|
15
|
+
// The clone runs in a short-lived podman container mounting the work-item
|
|
16
|
+
// volume. The bot's forge token is injected only into that one git invocation
|
|
17
|
+
// via the same http.extraHeader used by the host-side clone/push; it is never
|
|
18
|
+
// written into the peer tree.
|
|
19
|
+
export const PEERS_DIR = ".devbot-peers";
|
|
20
|
+
export function peerPath(projectPath) {
|
|
21
|
+
// Defense in depth: the forge project path is interpolated into a shell
|
|
22
|
+
// command and a clone destination. shellQuote already blocks injection, but
|
|
23
|
+
// a `..`/absolute path could still escape the peers dir. Validate first.
|
|
24
|
+
return `${PROJECT_ROOT}/${PEERS_DIR}/${validateProjectRelativePath(projectPath)}`;
|
|
25
|
+
}
|
|
26
|
+
// Append one or more dir entries (each with a trailing slash) to the repo's
|
|
27
|
+
// local exclude file if absent. Local exclude lives at .git/info/exclude and is
|
|
28
|
+
// never committed, unlike .gitignore. Runs inside a container (the volume is
|
|
29
|
+
// only reachable there). Used for `.devbot-peers/` and the outbound-attachment
|
|
30
|
+
// staging dir `.devbot-out/`.
|
|
31
|
+
export function excludeEntriesScript(entries) {
|
|
32
|
+
const lines = ["cd /workspace", "mkdir -p .git/info"];
|
|
33
|
+
for (const e of entries) {
|
|
34
|
+
lines.push(`grep -qxF '${e}' .git/info/exclude 2>/dev/null || echo '${e}' >> .git/info/exclude`);
|
|
35
|
+
}
|
|
36
|
+
return lines.join(" && ");
|
|
37
|
+
}
|
|
38
|
+
// Back-compat single-entry helper for the peer folder.
|
|
39
|
+
export function excludeScript() {
|
|
40
|
+
return excludeEntriesScript([`${PEERS_DIR}/`]);
|
|
41
|
+
}
|
|
42
|
+
// Compose the shell command that clones or fetches one peer. Pure (given the
|
|
43
|
+
// inputs) so it can be unit-tested without podman. `extraHeader` is the
|
|
44
|
+
// token-bearing git prefix; the URL is the enrolled project's http URL.
|
|
45
|
+
export function peerSyncScript(projectPath, cloneUrl, ref, extraHeader) {
|
|
46
|
+
const dir = `${PEERS_DIR}/${validateProjectRelativePath(projectPath)}`;
|
|
47
|
+
const branchFlag = ref ? ` --branch ${shellQuote(ref)}` : "";
|
|
48
|
+
const checkout = ref ? ` && git -C ${shellQuote(dir)} checkout ${shellQuote(ref)}` : "";
|
|
49
|
+
// mkdir -p the parent so the path with namespace (group/subgroup/project) nests.
|
|
50
|
+
const parent = dir.split("/").slice(0, -1).join("/");
|
|
51
|
+
return [
|
|
52
|
+
"cd /workspace",
|
|
53
|
+
excludeScript(),
|
|
54
|
+
`mkdir -p ${shellQuote(parent)}`,
|
|
55
|
+
`if [ -d ${shellQuote(dir)}/.git ]; then ` +
|
|
56
|
+
`git -C ${shellQuote(dir)} fetch --all --prune${checkout}; ` +
|
|
57
|
+
`else ${extraHeader} clone${branchFlag} ${shellQuote(cloneUrl)} ${shellQuote(dir)}; fi`,
|
|
58
|
+
`git -C ${shellQuote(dir)} rev-parse --short HEAD`,
|
|
59
|
+
].join(" && ");
|
|
60
|
+
}
|
|
61
|
+
function shellQuote(s) {
|
|
62
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
63
|
+
}
|
|
64
|
+
// Write the git-exclude line(s) proactively at run start (when the project has
|
|
65
|
+
// any granted targets) so the peers folder is invisible to git even before the
|
|
66
|
+
// first sync_project call. Idempotent. Best-effort: a failure is logged, not
|
|
67
|
+
// fatal — sync_project writes the exclude again before cloning anyway.
|
|
68
|
+
export async function ensureGitExcludes(workItemId, entries, envVars = [], timeoutMs = 60_000, builderImage = config.BUILDER_IMAGE) {
|
|
69
|
+
const vol = volumeName(workItemId);
|
|
70
|
+
const r = await podman([
|
|
71
|
+
"run", "--rm", ...logDriverArgs(), "--name", `devbot-wi-${workItemId}-gitexclude`,
|
|
72
|
+
...volumeSelinuxArgs(workItemId),
|
|
73
|
+
"-v", `${vol}:/workspace:Z`,
|
|
74
|
+
...envArgs(envVars),
|
|
75
|
+
builderImage,
|
|
76
|
+
"sh", "-c", excludeEntriesScript(entries),
|
|
77
|
+
], { timeoutMs });
|
|
78
|
+
if (r.code !== 0) {
|
|
79
|
+
log.warn("git exclude init failed", { workItemId, entries, code: r.code });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Back-compat wrapper: ensure just the peer folder is excluded.
|
|
83
|
+
export async function ensurePeerExclude(workItemId, envVars = [], timeoutMs = 60_000, builderImage = config.BUILDER_IMAGE) {
|
|
84
|
+
return ensureGitExcludes(workItemId, [`${PEERS_DIR}/`], envVars, timeoutMs, builderImage);
|
|
85
|
+
}
|
|
86
|
+
export async function syncPeerProject(input) {
|
|
87
|
+
const vol = volumeName(input.workItemId);
|
|
88
|
+
const script = peerSyncScript(input.projectPath, input.cloneUrl, input.ref, gitExtraHeader());
|
|
89
|
+
const r = await podman([
|
|
90
|
+
"run", "--rm", ...logDriverArgs(), "--name", `devbot-wi-${input.workItemId}-peersync`,
|
|
91
|
+
// Same per-work-item SELinux level as the agent run container; :Z alone
|
|
92
|
+
// would relabel the volume to this helper's random pair mid-run.
|
|
93
|
+
...volumeSelinuxArgs(input.workItemId),
|
|
94
|
+
"-v", `${vol}:/workspace:Z`,
|
|
95
|
+
...envArgs(input.envVars ?? []),
|
|
96
|
+
input.builderImage ?? config.BUILDER_IMAGE,
|
|
97
|
+
"sh", "-c", script,
|
|
98
|
+
], { timeoutMs: input.timeoutMs ?? 5 * 60_000 });
|
|
99
|
+
const path = peerPath(input.projectPath);
|
|
100
|
+
if (r.code !== 0) {
|
|
101
|
+
const msg = redact(r.stderr.slice(-400));
|
|
102
|
+
log.warn("peer sync failed", { workItemId: input.workItemId, projectPath: input.projectPath, code: r.code });
|
|
103
|
+
return { ok: false, path, sha: "", message: `peer sync failed: ${msg}` };
|
|
104
|
+
}
|
|
105
|
+
const sha = r.stdout.trim().split("\n").pop()?.trim() ?? "";
|
|
106
|
+
log.info("peer sync ok", { workItemId: input.workItemId, projectPath: input.projectPath, ref: input.ref ?? "default" });
|
|
107
|
+
return { ok: true, path, sha, message: `synced ${input.projectPath}` };
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=peer-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-sync.js","sourceRoot":"","sources":["../../src/mcp/peer-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as issues from "./tools/issues.js";
|
|
2
|
+
import * as mergeRequests from "./tools/merge_requests.js";
|
|
3
|
+
import * as workItems from "./tools/workitems.js";
|
|
4
|
+
import * as discovery from "./tools/discovery.js";
|
|
5
|
+
import * as repository from "./tools/repository.js";
|
|
6
|
+
import * as cicd from "./tools/cicd.js";
|
|
7
|
+
import * as workTracking from "./tools/work_tracking.js";
|
|
8
|
+
import * as metadata from "./tools/metadata.js";
|
|
9
|
+
import * as peerSync from "./tools/peer_sync.js";
|
|
10
|
+
import * as attachments from "./tools/attachments.js";
|
|
11
|
+
// Flat array: adding a tool = create a file under tools/, export a Tool, add
|
|
12
|
+
// it here. tools/list and tools/call dispatch from this array.
|
|
13
|
+
export const TOOLS = [
|
|
14
|
+
// Existing, now cross-project-capable.
|
|
15
|
+
issues.listIssues,
|
|
16
|
+
issues.getIssue,
|
|
17
|
+
mergeRequests.listMergeRequests,
|
|
18
|
+
mergeRequests.getMergeRequestDiscussions,
|
|
19
|
+
workItems.searchWorkItems,
|
|
20
|
+
// Discovery.
|
|
21
|
+
discovery.listAccessibleProjects,
|
|
22
|
+
discovery.getProject,
|
|
23
|
+
// Repository.
|
|
24
|
+
repository.listRepositoryTree,
|
|
25
|
+
repository.getRepositoryFile,
|
|
26
|
+
repository.listCommits,
|
|
27
|
+
repository.getCommit,
|
|
28
|
+
repository.compareRefs,
|
|
29
|
+
repository.listBranches,
|
|
30
|
+
repository.listTags,
|
|
31
|
+
// CI/CD.
|
|
32
|
+
cicd.listPipelines,
|
|
33
|
+
cicd.getPipeline,
|
|
34
|
+
cicd.listPipelineJobs,
|
|
35
|
+
cicd.getJobLog,
|
|
36
|
+
// Work tracking.
|
|
37
|
+
workTracking.listLabels,
|
|
38
|
+
workTracking.listMilestones,
|
|
39
|
+
workTracking.listIssueNotes,
|
|
40
|
+
workTracking.getMergeRequest,
|
|
41
|
+
workTracking.listMergeRequestNotes,
|
|
42
|
+
// Repository metadata.
|
|
43
|
+
metadata.listMembers,
|
|
44
|
+
metadata.listReleases,
|
|
45
|
+
metadata.listWikiPages,
|
|
46
|
+
metadata.getWikiPage,
|
|
47
|
+
metadata.searchProject,
|
|
48
|
+
// Peer code materialization.
|
|
49
|
+
peerSync.syncProject,
|
|
50
|
+
// Attachment download (agent reads work-item files) + upload.
|
|
51
|
+
attachments.downloadWorkItemAttachment,
|
|
52
|
+
attachments.uploadWorkItemAttachment,
|
|
53
|
+
];
|
|
54
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/mcp/registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,UAAU,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,YAAY,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,WAAW,MAAM,wBAAwB,CAAC;AA0BtD,6EAA6E;AAC7E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B,uCAAuC;IACvC,MAAM,CAAC,UAAU;IACjB,MAAM,CAAC,QAAQ;IACf,aAAa,CAAC,iBAAiB;IAC/B,aAAa,CAAC,0BAA0B;IACxC,SAAS,CAAC,eAAe;IACzB,aAAa;IACb,SAAS,CAAC,sBAAsB;IAChC,SAAS,CAAC,UAAU;IACpB,cAAc;IACd,UAAU,CAAC,kBAAkB;IAC7B,UAAU,CAAC,iBAAiB;IAC5B,UAAU,CAAC,WAAW;IACtB,UAAU,CAAC,SAAS;IACpB,UAAU,CAAC,WAAW;IACtB,UAAU,CAAC,YAAY;IACvB,UAAU,CAAC,QAAQ;IACnB,SAAS;IACT,IAAI,CAAC,aAAa;IAClB,IAAI,CAAC,WAAW;IAChB,IAAI,CAAC,gBAAgB;IACrB,IAAI,CAAC,SAAS;IACd,iBAAiB;IACjB,YAAY,CAAC,UAAU;IACvB,YAAY,CAAC,cAAc;IAC3B,YAAY,CAAC,cAAc;IAC3B,YAAY,CAAC,eAAe;IAC5B,YAAY,CAAC,qBAAqB;IAClC,uBAAuB;IACvB,QAAQ,CAAC,WAAW;IACpB,QAAQ,CAAC,YAAY;IACrB,QAAQ,CAAC,aAAa;IACtB,QAAQ,CAAC,WAAW;IACpB,QAAQ,CAAC,aAAa;IACtB,6BAA6B;IAC7B,QAAQ,CAAC,WAAW;IACpB,8DAA8D;IAC9D,WAAW,CAAC,0BAA0B;IACtC,WAAW,CAAC,wBAAwB;CACrC,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { mintMcpToken, deleteMcpToken } from "./auth.js";
|
|
2
|
+
import { mergedMcpServers, effectiveMcpServers } from "./effective-mcp.js";
|
|
3
|
+
import { buildMcpAuthJsonForScopes } from "../mcp-auth-store.js";
|
|
4
|
+
import { refreshDueMcpAuth } from "./oauth-refresh.js";
|
|
5
|
+
import { buildEnvArray } from "../env-config-store.js";
|
|
6
|
+
import { resolveModelForMode, resolveVariantForMode, resolvePlugins, resolveBaseBranches, resolveBuilderImage, resolveBuildWorkflow, resolveMrDescription, resolveLspServers } from "../settings-store.js";
|
|
7
|
+
import { autoFeatureEnabled } from "../work-item-auto-store.js";
|
|
8
|
+
import { enabledProvidersForRun } from "../global-settings-store.js";
|
|
9
|
+
import { config } from "../config.js";
|
|
10
|
+
import { listAccessTargets } from "../access-grant-store.js";
|
|
11
|
+
import { resolveCustomRepos } from "../custom-repos-store.js";
|
|
12
|
+
import { GLOBAL_SCOPE_ID } from "../types/global-settings.js";
|
|
13
|
+
import { log } from "../logger.js";
|
|
14
|
+
export async function prepareMcpRun(projectId, workItemId, workItemNumber, ttlMs) {
|
|
15
|
+
const { token } = mintMcpToken({ projectId, workItemId, workItemNumber, ttlMs });
|
|
16
|
+
// MCP merge order: global first, project second (project wins on name
|
|
17
|
+
// collision). The bundled internal servers are injected later in
|
|
18
|
+
// buildOpencodeConfig and always win.
|
|
19
|
+
const mcpServers = mergedMcpServers(projectId);
|
|
20
|
+
const effective = effectiveMcpServers(projectId);
|
|
21
|
+
const configs = new Map();
|
|
22
|
+
for (const e of effective) {
|
|
23
|
+
if (e.config.type === "remote")
|
|
24
|
+
configs.set(e.name, { url: e.config.url });
|
|
25
|
+
}
|
|
26
|
+
// Observable per-run record of which external servers a run receives and
|
|
27
|
+
// where each came from, so "did the global apply?" is answerable from logs.
|
|
28
|
+
// Names/state only — never secrets or header values.
|
|
29
|
+
log.info("mcp run servers", {
|
|
30
|
+
projectId,
|
|
31
|
+
workItemId,
|
|
32
|
+
servers: effective.map((e) => ({
|
|
33
|
+
name: e.name,
|
|
34
|
+
origin: e.origin,
|
|
35
|
+
enabled: e.enabled,
|
|
36
|
+
hasAuth: configs.has(e.name),
|
|
37
|
+
overriddenByProject: e.overriddenByProject,
|
|
38
|
+
})),
|
|
39
|
+
});
|
|
40
|
+
// Pre-run OAuth refresh safety net (always on, independent of the scheduler
|
|
41
|
+
// toggle): tokens expiring within this run's TTL are renewed host-side so
|
|
42
|
+
// opencode never has to refresh in-container (and lose the rotation). Never
|
|
43
|
+
// throws — a failed refresh leaves the stored row and the run proceeds.
|
|
44
|
+
await refreshDueMcpAuth(ttlMs, [GLOBAL_SCOPE_ID, projectId]);
|
|
45
|
+
const mcpAuthJson = JSON.stringify(buildMcpAuthJsonForScopes([GLOBAL_SCOPE_ID, projectId], configs), null, 2);
|
|
46
|
+
const envVars = buildEnvArray(projectId);
|
|
47
|
+
const models = {
|
|
48
|
+
plan: { model: resolveModelForMode(projectId, "plan"), variant: resolveVariantForMode(projectId, "plan") },
|
|
49
|
+
build: { model: resolveModelForMode(projectId, "build"), variant: resolveVariantForMode(projectId, "build") },
|
|
50
|
+
};
|
|
51
|
+
const plugins = resolvePlugins(projectId);
|
|
52
|
+
const baseBranches = resolveBaseBranches(projectId);
|
|
53
|
+
const builderImage = resolveBuilderImage(projectId) || config.BUILDER_IMAGE;
|
|
54
|
+
const buildWorkflow = resolveBuildWorkflow(projectId);
|
|
55
|
+
const mrDescription = resolveMrDescription(projectId);
|
|
56
|
+
const autoPush = autoFeatureEnabled(projectId, workItemId, "autoPush");
|
|
57
|
+
const autoMr = autoFeatureEnabled(projectId, workItemId, "autoMr");
|
|
58
|
+
const autoResolveConflicts = autoFeatureEnabled(projectId, workItemId, "autoResolveConflicts");
|
|
59
|
+
const lspServers = resolveLspServers(projectId);
|
|
60
|
+
const providers = enabledProvidersForRun();
|
|
61
|
+
const peerProjects = listAccessTargets(projectId).filter((t) => t.projectId !== projectId);
|
|
62
|
+
const customRepos = {
|
|
63
|
+
skills: resolveCustomRepos(projectId, "skills"),
|
|
64
|
+
agents: resolveCustomRepos(projectId, "agents"),
|
|
65
|
+
};
|
|
66
|
+
return { mcpToken: token, mcpServers, mcpAuthJson, envVars, models, plugins, baseBranches, builderImage, buildWorkflow, mrDescription, autoPush, autoMr, autoResolveConflicts, lspServers, providers, peerProjects, customRepos };
|
|
67
|
+
}
|
|
68
|
+
export function cleanupMcpRun(token) {
|
|
69
|
+
deleteMcpToken(token);
|
|
70
|
+
}
|
|
71
|
+
// Per-project env vars for a push-only run (standalone /force-push), which has
|
|
72
|
+
// no MCP context. Kept here so runner.ts stays free of direct store imports.
|
|
73
|
+
export function projectEnvVars(projectId) {
|
|
74
|
+
return buildEnvArray(projectId);
|
|
75
|
+
}
|
|
76
|
+
// Resolved builder image for a push-only run (standalone /force-push), which has
|
|
77
|
+
// no MCP context. Kept here so runner.ts stays free of direct store imports.
|
|
78
|
+
export function projectBuilderImage(projectId) {
|
|
79
|
+
return resolveBuilderImage(projectId) || config.BUILDER_IMAGE;
|
|
80
|
+
}
|
|
81
|
+
// Resolved base-branch walk for a push/MR-only run (standalone /push, /mr),
|
|
82
|
+
// which has no MCP context and so cannot read `mcpCtx.baseBranches`. Kept here
|
|
83
|
+
// so runner.ts stays free of direct store imports.
|
|
84
|
+
export function projectBaseBranches(projectId) {
|
|
85
|
+
return resolveBaseBranches(projectId);
|
|
86
|
+
}
|
|
87
|
+
// Resolved conflict auto-fix switch for the standalone /mr path, which has no
|
|
88
|
+
// MCP context. Kept here so runner.ts stays free of direct store imports.
|
|
89
|
+
// Work-item override aware (work item #58).
|
|
90
|
+
export function projectAutoResolveConflicts(projectId, workItemId) {
|
|
91
|
+
return autoFeatureEnabled(projectId, workItemId, "autoResolveConflicts");
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=runner-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-bridge.js","sourceRoot":"","sources":["../../src/mcp/runner-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
|
+
import { hostHeaderValidation } from "@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js";
|
|
6
|
+
import { log } from "../logger.js";
|
|
7
|
+
import { verifyMcpToken } from "./auth.js";
|
|
8
|
+
import { TOOLS } from "./registry.js";
|
|
9
|
+
// ---- Bearer auth ----------------------------------------------------------
|
|
10
|
+
// Every /mcp request must carry a per-thread bearer. The token is looked up in
|
|
11
|
+
// mcp_thread_tokens; its project_id is injected into the per-session McpContext
|
|
12
|
+
// used by every tool handler. Tools never read a project_id from the client.
|
|
13
|
+
function extractBearer(req) {
|
|
14
|
+
const h = req.headers["authorization"];
|
|
15
|
+
if (typeof h !== "string")
|
|
16
|
+
return null;
|
|
17
|
+
if (!h.toLowerCase().startsWith("bearer "))
|
|
18
|
+
return null;
|
|
19
|
+
return h.slice(7).trim();
|
|
20
|
+
}
|
|
21
|
+
const sessions = new Map();
|
|
22
|
+
// ponytail: no idle-session reaper. Sessions live for the duration of an
|
|
23
|
+
// opencode run (minutes, not hours); the runner deletes the token on run
|
|
24
|
+
// end, after which auth fails and the session is inert. Add a sweep if a
|
|
25
|
+
// leaked session ever shows up in metrics.
|
|
26
|
+
const SESSION_TIMEOUT_MS = 60 * 60 * 1000;
|
|
27
|
+
function registerToolsOn(server, ctx) {
|
|
28
|
+
for (const tool of TOOLS) {
|
|
29
|
+
server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, async (args) => tool.handler(ctx, args ?? {}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function createSession(ctx) {
|
|
33
|
+
const transport = new StreamableHTTPServerTransport({
|
|
34
|
+
sessionIdGenerator: () => randomUUID(),
|
|
35
|
+
onsessioninitialized: (sessionId) => {
|
|
36
|
+
// sessionId is assigned by the transport during initialize handling
|
|
37
|
+
// (webStandardStreamableHttp.js:433), then this callback fires. Register
|
|
38
|
+
// here — NOT by reading transport.sessionId before handleRequest (that's
|
|
39
|
+
// always undefined at that point, and the old guard threw on every run).
|
|
40
|
+
if (sessions.has(sessionId))
|
|
41
|
+
return;
|
|
42
|
+
sessions.set(sessionId, { transport, server, ctx, lastSeen: Date.now() });
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
const server = new McpServer({ name: "devbot-forge", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
46
|
+
registerToolsOn(server, ctx);
|
|
47
|
+
// ponytail: cast through `as Transport` — the SDK's StreamableHTTPServerTransport
|
|
48
|
+
// declares optional callbacks as `?: () => void` (no `| undefined`) while
|
|
49
|
+
// our tsconfig has `exactOptionalPropertyTypes: true`, which makes the
|
|
50
|
+
// implementation's `(() => void) | undefined` form incompatible with the
|
|
51
|
+
// interface's stricter signature. The runtime shape is identical; this is
|
|
52
|
+
// a type-only mismatch in the SDK's own declarations. Drop the cast if the
|
|
53
|
+
// SDK ever ships `| undefined` on its Transport callbacks.
|
|
54
|
+
await server.connect(transport);
|
|
55
|
+
transport.onclose = () => {
|
|
56
|
+
const sid = transport.sessionId;
|
|
57
|
+
if (sid)
|
|
58
|
+
sessions.delete(sid);
|
|
59
|
+
};
|
|
60
|
+
// Returned before handleRequest runs; onsessioninitialized registers the
|
|
61
|
+
// session during the initialize request the caller is about to send.
|
|
62
|
+
return { transport, server };
|
|
63
|
+
}
|
|
64
|
+
// ---- Router ---------------------------------------------------------------
|
|
65
|
+
export function mcpRouter() {
|
|
66
|
+
const router = Router();
|
|
67
|
+
// Host-header gate: defense-in-depth against DNS rebinding on top of the
|
|
68
|
+
// separate port (127.0.0.1 bind) + bearer auth. Allows host.containers.internal
|
|
69
|
+
// (the default MCP_BASE_URL the container uses via --add-host) alongside the
|
|
70
|
+
// localhost variants.
|
|
71
|
+
router.use(hostHeaderValidation(["localhost", "127.0.0.1", "[::1]", "host.containers.internal"]));
|
|
72
|
+
// Bearer auth runs before session routing — every request (initialize or
|
|
73
|
+
// subsequent) must present a valid token. The McpContext is stashed on the
|
|
74
|
+
// request for the session-creation path.
|
|
75
|
+
router.use((req, res, next) => {
|
|
76
|
+
const token = extractBearer(req);
|
|
77
|
+
if (!token) {
|
|
78
|
+
log.warn("mcp auth: missing bearer");
|
|
79
|
+
res.status(401).json({ error: "unauthorized" });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const ctx = verifyMcpToken(token);
|
|
83
|
+
if (!ctx) {
|
|
84
|
+
log.warn("mcp auth: invalid or expired token");
|
|
85
|
+
res.status(401).json({ error: "unauthorized" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
req.mcpCtx = ctx;
|
|
89
|
+
next();
|
|
90
|
+
});
|
|
91
|
+
const handle = async (req, res) => {
|
|
92
|
+
try {
|
|
93
|
+
const ctx = req.mcpCtx;
|
|
94
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
95
|
+
// Existing session: validate id + token still maps to the same project.
|
|
96
|
+
if (sessionId) {
|
|
97
|
+
const state = sessions.get(sessionId);
|
|
98
|
+
if (!state) {
|
|
99
|
+
res.status(404).json({ error: "session not found" });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (state.ctx.projectId !== ctx.projectId ||
|
|
103
|
+
state.ctx.workItemId !== ctx.workItemId ||
|
|
104
|
+
state.ctx.workItemNumber !== ctx.workItemNumber) {
|
|
105
|
+
// Token swapped mid-session — reject, don't silently rebind.
|
|
106
|
+
log.warn("mcp session token mismatch", { sessionId });
|
|
107
|
+
res.status(401).json({ error: "unauthorized" });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
state.lastSeen = Date.now();
|
|
111
|
+
await state.transport.handleRequest(req, res, req.body);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// No session id: this must be an initialize. Parse the body to confirm.
|
|
115
|
+
const body = req.body;
|
|
116
|
+
const method = body?.method;
|
|
117
|
+
if (method !== "initialize") {
|
|
118
|
+
res.status(400).json({ error: "missing Mcp-Session-Id header" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const { transport } = await createSession(ctx);
|
|
122
|
+
await transport.handleRequest(req, res, req.body);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
log.warn("mcp POST handler error", { err: e instanceof Error ? e.message : String(e) });
|
|
126
|
+
if (!res.headersSent)
|
|
127
|
+
res.status(500).json({ error: "internal error" });
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
router.post("/", handle);
|
|
131
|
+
router.get("/", (req, res) => {
|
|
132
|
+
// SSE stream for server-initiated notifications. Requires an existing
|
|
133
|
+
// session (the client opens this after initialize). Stateless clients
|
|
134
|
+
// that don't use notifications get a 405.
|
|
135
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
136
|
+
const state = sessionId ? sessions.get(sessionId) : undefined;
|
|
137
|
+
if (!state) {
|
|
138
|
+
res.status(404).json({ error: "session not found" });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
state.transport.handleRequest(req, res).catch((e) => {
|
|
142
|
+
log.warn("mcp GET handler error", { err: e instanceof Error ? e.message : String(e) });
|
|
143
|
+
if (!res.headersSent)
|
|
144
|
+
res.status(500).json({ error: "internal error" });
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
router.delete("/", (req, res) => {
|
|
148
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
149
|
+
if (sessionId) {
|
|
150
|
+
const state = sessions.get(sessionId);
|
|
151
|
+
if (state) {
|
|
152
|
+
state.transport.close().catch(() => { });
|
|
153
|
+
sessions.delete(sessionId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
res.status(204).end();
|
|
157
|
+
});
|
|
158
|
+
// ponytail: one-shot idle sweep on each new session creation, not a timer.
|
|
159
|
+
// Cheap (Map iteration over a handful of live sessions) and avoids a
|
|
160
|
+
// long-lived setInterval keeping the process awake.
|
|
161
|
+
setInterval(() => {
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
for (const [id, state] of sessions) {
|
|
164
|
+
if (now - state.lastSeen > SESSION_TIMEOUT_MS) {
|
|
165
|
+
log.debug("mcp session idle timeout", { sessionId: id });
|
|
166
|
+
state.transport.close().catch(() => { });
|
|
167
|
+
sessions.delete(id);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}, 15 * 60 * 1000).unref();
|
|
171
|
+
return router;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAEnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,qEAAqE,CAAC;AAC3G,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAmB,MAAM,eAAe,CAAC;AAEvD,8EAA8E;AAC9E,+EAA+E;AAC/E,gFAAgF;AAChF,6EAA6E;AAC7E,SAAS,aAAa,CAAC,GAA+D;IACpF,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAsBD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;AAEjD,yEAAyE;AACzE,2EAA2E;AAC3E,2EAA2E;AAC3E,6CAA6C;AAC7C,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE1C,SAAS,eAAe,CAAC,MAAiB,EAAE,GAAe;IACzD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,YAAY,CACjB,IAAI,CAAC,IAAI,EACT,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,EAChE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,CAC9C,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,GAAe;IAC1C,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;QAClD,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;QACtC,oBAAoB,EAAE,CAAC,SAAS,EAAE,EAAE;YAClC,oEAAoE;YACpE,yEAAyE;YACzE,yEAAyE;YACzE,yEAAyE;YACzE,IAAI,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;gBAAE,OAAO;YACpC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC5E,CAAC;KACF,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,EAC1C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IACF,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,kFAAkF;IAClF,4EAA4E;IAC5E,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,6DAA6D;IAC7D,MAAM,MAAM,CAAC,OAAO,CAAC,SAAsB,CAAC,CAAC;IAE7C,SAAS,CAAC,OAAO,GAAG,GAAG,EAAE;QACvB,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC;QAChC,IAAI,GAAG;YAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC,CAAC;IACF,yEAAyE;IACzE,qEAAqE;IACrE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,SAAS;IACvB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,yEAAyE;IACzE,gFAAgF;IAChF,6EAA6E;IAC7E,sBAAsB;IACtB,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;IAElG,yEAAyE;IACzE,2EAA2E;IAC3E,yCAAyC;IACzC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC5B,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACrC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YAC/C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QACA,GAAW,CAAC,MAAM,GAAG,GAAG,CAAC;QAC1B,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,KAAK,EAAE,GAAQ,EAAE,GAAQ,EAAE,EAAE;QAC1C,IAAI,CAAC;YACH,MAAM,GAAG,GAAe,GAAG,CAAC,MAAM,CAAC;YACnC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAuB,CAAC;YAEtE,wEAAwE;YACxE,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACtC,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;oBACrD,OAAO;gBACT,CAAC;gBACD,IACE,KAAK,CAAC,GAAG,CAAC,SAAS,KAAK,GAAG,CAAC,SAAS;oBACrC,KAAK,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU;oBACvC,KAAK,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG,CAAC,cAAc,EAC/C,CAAC;oBACD,6DAA6D;oBAC7D,GAAG,CAAC,IAAI,CAAC,4BAA4B,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;oBACtD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;oBAChD,OAAO;gBACT,CAAC;gBACD,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC5B,MAAM,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,wEAAwE;YACxE,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;YACtB,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC;YAC5B,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;gBAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC,CAAC;gBACjE,OAAO;YACT,CAAC;YACD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;YAC/C,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CAAC,wBAAwB,EAAE,EAAE,GAAG,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACxF,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3B,sEAAsE;QACtE,sEAAsE;QACtE,0CAA0C;QAC1C,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAuB,CAAC;QACtE,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9D,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACrD,OAAO;QACT,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;YAClD,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,GAAG,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACvF,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAuB,CAAC;QACtE,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACtC,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACxC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,2EAA2E;IAC3E,uEAAuE;IACvE,sDAAsD;IACtD,WAAW,CAAC,GAAG,EAAE;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;YACnC,IAAI,GAAG,GAAG,KAAK,CAAC,QAAQ,GAAG,kBAAkB,EAAE,CAAC;gBAC9C,GAAG,CAAC,KAAK,CAAC,0BAA0B,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;gBACzD,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;IAE3B,OAAO,MAAM,CAAC;AAChB,CAAC"}
|