@wascartech/devbot 0.0.0-stage → 2.1.1
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 +150 -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 +438 -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 +158 -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,187 @@
|
|
|
1
|
+
// Read a single file out of a run's workspace volume for outbound attachment
|
|
2
|
+
// upload (work item #49). The MCP server runs on the host and the agent's files
|
|
3
|
+
// live in the per-work-item podman volume, so the only way to reach them is a
|
|
4
|
+
// short-lived container — the same pattern as peer-sync.ts.
|
|
5
|
+
//
|
|
6
|
+
// The file is base64-encoded on stdout (text-safe, bounded by the size cap the
|
|
7
|
+
// shell enforces before emitting anything), then decoded host-side. Path
|
|
8
|
+
// validation happens in the shell (realpath under /workspace) so symlink and
|
|
9
|
+
// traversal escapes are caught where the filesystem actually lives.
|
|
10
|
+
import { config } from "../config.js";
|
|
11
|
+
import { log } from "../logger.js";
|
|
12
|
+
import { redact } from "../redact.js";
|
|
13
|
+
import { podman } from "../podman.js";
|
|
14
|
+
import { volumeName, logDriverArgs, volumeSelinuxArgs } from "../git.js";
|
|
15
|
+
import { mimeForFilename } from "../attachments.js";
|
|
16
|
+
import { PROJECT_ROOT, allowShellCasePatterns, denyShellCasePatterns, resolveProjectPath, } from "../path-sandbox.js";
|
|
17
|
+
// Fixed staging dir for agent-produced throwaway files. Git-excluded at run
|
|
18
|
+
// start (see ensureGitExcludes) so it can never be committed by `git add -A`.
|
|
19
|
+
export const OUT_DIR = ".devbot-out";
|
|
20
|
+
export const OUT_DIR_ABS = `/workspace/${OUT_DIR}`;
|
|
21
|
+
// Subdirectory of the staging dir where downloaded work-item attachments land.
|
|
22
|
+
// Inside OUT_DIR so it inherits the git exclude and never lands in the diff.
|
|
23
|
+
export const INCOMING_DIR = `${OUT_DIR}/incoming`;
|
|
24
|
+
export const INCOMING_DIR_ABS = `/workspace/${INCOMING_DIR}`;
|
|
25
|
+
// Prompt block appended to every run. Tells the agent both attachment tools
|
|
26
|
+
// exist, where attachment files live, and that the staging dir is git-excluded.
|
|
27
|
+
export function attachmentPromptSection() {
|
|
28
|
+
return [
|
|
29
|
+
"## Attachments",
|
|
30
|
+
"",
|
|
31
|
+
`Attachments in the work item or discussion are **not** pre-read for you; their links appear inline. To inspect one, call \`download_work_item_attachment\` with the attachment's markdown link (or bare URL) exactly as it appears. It saves the file under \`${INCOMING_DIR}/\` and returns the local path — read it with your own tools. Image/PDF OCR is available on the downloaded file (\`tesseract\`, \`pdftotext\`, \`pdftoppm\` are installed).`,
|
|
32
|
+
"",
|
|
33
|
+
"## Sharing files with the user",
|
|
34
|
+
"",
|
|
35
|
+
`You can send screenshots, test logs, command output, or any generated artifact to the work item with the \`upload_work_item_attachment\` tool. It uploads the file and posts it as a comment (with an optional \`caption\`).`,
|
|
36
|
+
"",
|
|
37
|
+
`- Write throwaway output (screenshots, logs, reports) to \`${OUT_DIR}/\` at the repository root, then pass its path to the tool (e.g. \`path: "${OUT_DIR}/result.log"\`).`,
|
|
38
|
+
`- \`${OUT_DIR}/\` is git-excluded, so anything there (including downloaded attachments in \`${INCOMING_DIR}/\`) is never committed. Keep files outside it only when you deliberately want them in the diff.`,
|
|
39
|
+
"- Prefer sharing a screenshot or a short log extract over dumping large output into your reply.",
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
// Exit codes the read script uses to classify failures host-side.
|
|
43
|
+
export const EXIT_NOT_FOUND = 3;
|
|
44
|
+
export const EXIT_OVERSIZE = 4;
|
|
45
|
+
export const EXIT_UNSAFE_PATH = 5;
|
|
46
|
+
// The write script uses this when the target already exists and overwrite is off.
|
|
47
|
+
export const EXIT_EXISTS = 6;
|
|
48
|
+
/** Raised when a staging write would overwrite an existing file. Callers
|
|
49
|
+
* suffix the filename and retry. */
|
|
50
|
+
export class ExistsError extends Error {
|
|
51
|
+
constructor(path) {
|
|
52
|
+
super(`already exists: ${path}`);
|
|
53
|
+
this.name = "ExistsError";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function shq(s) {
|
|
57
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
58
|
+
}
|
|
59
|
+
/** Resolve a tool-supplied path to an absolute path inside /workspace, rejecting
|
|
60
|
+
* traversal, escapes, and denied segments (`.xdg-*`, `.git`, `.ssh`, `.env`).
|
|
61
|
+
* Pure; no IO (the shell re-checks the real path). Delegates to the shared
|
|
62
|
+
* policy in src/path-sandbox.ts so the rule set cannot drift. */
|
|
63
|
+
export function resolveVolumePath(p) {
|
|
64
|
+
return resolveProjectPath(p);
|
|
65
|
+
}
|
|
66
|
+
// `case` patterns applied to the resolved realpath, so a symlink pointing at a
|
|
67
|
+
// denied path is caught even though the lexical check upstream only sees the
|
|
68
|
+
// symlink's own spelling. Allow patterns come first (`.env.example` also
|
|
69
|
+
// matches the `.env.*` deny pattern).
|
|
70
|
+
function deniedRealpathCase() {
|
|
71
|
+
const allow = allowShellCasePatterns().join("|");
|
|
72
|
+
const deny = denyShellCasePatterns().join("|");
|
|
73
|
+
return (`case "$rp" in ${allow}) ;; ` +
|
|
74
|
+
`${deny}) echo "path is restricted: $rp" >&2; exit ${EXIT_UNSAFE_PATH};; ` +
|
|
75
|
+
`esac`);
|
|
76
|
+
}
|
|
77
|
+
// Shell script: validate + base64. Distinct exit codes let the host classify the
|
|
78
|
+
// failure. `realpath -e` fails when the path (or its parent) is missing; the
|
|
79
|
+
// resolved path must remain under /workspace (catches symlink escapes) and must
|
|
80
|
+
// not land on a denied hidden/system path.
|
|
81
|
+
export function readFileScript(absPath, maxBytes) {
|
|
82
|
+
return [
|
|
83
|
+
"cd /workspace",
|
|
84
|
+
`p=${shq(absPath)}`,
|
|
85
|
+
`[ -e "$p" ] || { echo "file not found: $p" >&2; exit ${EXIT_NOT_FOUND}; }`,
|
|
86
|
+
`[ -f "$p" ] || { echo "not a regular file: $p" >&2; exit ${EXIT_NOT_FOUND}; }`,
|
|
87
|
+
`rp=$(realpath -e -- "$p" 2>/dev/null) || { echo "file not found: $p" >&2; exit ${EXIT_NOT_FOUND}; }`,
|
|
88
|
+
`case "$rp" in ${PROJECT_ROOT}/*) ;; *) echo "path escapes ${PROJECT_ROOT}: $rp" >&2; exit ${EXIT_UNSAFE_PATH};; esac`,
|
|
89
|
+
deniedRealpathCase(),
|
|
90
|
+
`sz=$(stat -c %s -- "$rp")`,
|
|
91
|
+
`[ "$sz" -le ${maxBytes} ] || { echo "file is larger than ${maxBytes} bytes ($sz)" >&2; exit ${EXIT_OVERSIZE}; }`,
|
|
92
|
+
`base64 -w0 -- "$rp"`,
|
|
93
|
+
].join(" && ");
|
|
94
|
+
}
|
|
95
|
+
/** Read one file from the work-item volume. Throws a typed, user-readable error
|
|
96
|
+
* for a missing/oversized/escaping path or a container failure. */
|
|
97
|
+
export async function readFileFromVolume(workItemId, path, maxBytes, builderImage = config.BUILDER_IMAGE) {
|
|
98
|
+
const abs = resolveVolumePath(path);
|
|
99
|
+
const vol = volumeName(workItemId);
|
|
100
|
+
const r = await podman([
|
|
101
|
+
"run", "--rm", ...logDriverArgs(), "--name", `devbot-wi-${workItemId}-attachread`,
|
|
102
|
+
// Share the agent run container's SELinux level: mounting with :Z alone
|
|
103
|
+
// would relabel the volume to this helper's random pair and revoke the
|
|
104
|
+
// running agent's access.
|
|
105
|
+
...volumeSelinuxArgs(workItemId),
|
|
106
|
+
"-v", `${vol}:/workspace:Z`,
|
|
107
|
+
builderImage,
|
|
108
|
+
"sh", "-c", readFileScript(abs, maxBytes),
|
|
109
|
+
], { timeoutMs: 60_000 });
|
|
110
|
+
if (r.code === EXIT_NOT_FOUND)
|
|
111
|
+
throw new Error(`attachment not found: ${abs}`);
|
|
112
|
+
if (r.code === EXIT_OVERSIZE)
|
|
113
|
+
throw new Error(`attachment is larger than ${maxBytes} bytes`);
|
|
114
|
+
if (r.code === EXIT_UNSAFE_PATH) {
|
|
115
|
+
// The shell distinguishes "escapes /workspace" (traversal/absolute) from
|
|
116
|
+
// "path is restricted" (a symlink resolving onto .env/.git/.xdg-*); surface
|
|
117
|
+
// its reason so the caller sees which guard fired.
|
|
118
|
+
const reason = redact(r.stderr.trim().split("\n").pop() ?? "");
|
|
119
|
+
throw new Error(`attachment path rejected: ${reason || `escapes /workspace: ${abs}`}`);
|
|
120
|
+
}
|
|
121
|
+
if (r.code !== 0) {
|
|
122
|
+
const msg = redact(r.stderr.trim().slice(-300));
|
|
123
|
+
log.warn("attachment read failed", { workItemId, code: r.code });
|
|
124
|
+
throw new Error(`failed to read attachment (exit ${r.code})${msg ? `: ${msg}` : ""}`);
|
|
125
|
+
}
|
|
126
|
+
const bytes = Buffer.from(r.stdout.trim(), "base64");
|
|
127
|
+
const filename = abs.split("/").pop() ?? "attachment";
|
|
128
|
+
return { bytes, filename, contentType: mimeForFilename(filename) };
|
|
129
|
+
}
|
|
130
|
+
// Shell script: decode base64 from stdin and write it under the staging dir.
|
|
131
|
+
// The caller has already sanitized `relPath` (no traversal). Defense in depth:
|
|
132
|
+
// resolve the staging dir itself and require it to be exactly INCOMING_DIR_ABS
|
|
133
|
+
// (so a symlinked `.devbot-out/incoming` is refused before any bytes are
|
|
134
|
+
// written), refuse to overwrite (incl. through a dangling symlink), and
|
|
135
|
+
// re-check that the written path resolves inside the staging dir.
|
|
136
|
+
export function writeFileScript(relPath) {
|
|
137
|
+
return [
|
|
138
|
+
"umask 002",
|
|
139
|
+
"cd /workspace",
|
|
140
|
+
`mkdir -p ${INCOMING_DIR} || { echo "staging dir unavailable" >&2; exit ${EXIT_UNSAFE_PATH}; }`,
|
|
141
|
+
`dir=$(realpath -e -- ${INCOMING_DIR})`,
|
|
142
|
+
`[ "$dir" = ${INCOMING_DIR_ABS} ] || { echo "staging dir resolves elsewhere: $dir" >&2; exit ${EXIT_UNSAFE_PATH}; }`,
|
|
143
|
+
// Best-effort group-write on the staging dirs: the bash tool runs as the
|
|
144
|
+
// unprivileged `agent` UID (gid devbot), which cannot create files in a
|
|
145
|
+
// devbot-owned 0755 dir — so OCR output written beside a downloaded
|
|
146
|
+
// attachment (e.g. `tesseract in.png out`) would fail EACCES. A chmod
|
|
147
|
+
// EPERM (custom builder image) leaves the previous behavior.
|
|
148
|
+
`{ chmod 0775 ${OUT_DIR} ${INCOMING_DIR} 2>/dev/null || true; }`,
|
|
149
|
+
`{ [ ! -e ${shq(relPath)} ] && [ ! -L ${shq(relPath)} ]; } || { echo "already exists: ${shq(relPath)}" >&2; exit ${EXIT_EXISTS}; }`,
|
|
150
|
+
`base64 -d > ${shq(relPath)}`,
|
|
151
|
+
`rp=$(realpath -e -- ${shq(relPath)})`,
|
|
152
|
+
`case "$rp" in ${INCOMING_DIR_ABS}/*) ;; *) echo "wrote outside staging dir: $rp" >&2; exit ${EXIT_UNSAFE_PATH};; esac`,
|
|
153
|
+
].join(" && ");
|
|
154
|
+
}
|
|
155
|
+
/** Write bytes into the work-item volume under the git-excluded staging dir.
|
|
156
|
+
* `relPath` must be a path inside `OUT_DIR` (the caller sanitizes the
|
|
157
|
+
* filename); this mirrors `readFileFromVolume` in reverse — a short-lived
|
|
158
|
+
* container with the workspace volume. The bytes travel on stdin (base64), not
|
|
159
|
+
* in argv, so a large attachment never hits the OS argument-size limit. Throws
|
|
160
|
+
* a typed error on an escaping path or container failure. */
|
|
161
|
+
export async function writeFileToVolume(workItemId, relPath, bytes, builderImage = config.BUILDER_IMAGE) {
|
|
162
|
+
const abs = resolveVolumePath(relPath);
|
|
163
|
+
if (abs !== INCOMING_DIR_ABS && !abs.startsWith(`${INCOMING_DIR_ABS}/`)) {
|
|
164
|
+
throw new Error(`attachment staging path must be inside ${INCOMING_DIR_ABS}: ${abs}`);
|
|
165
|
+
}
|
|
166
|
+
const vol = volumeName(workItemId);
|
|
167
|
+
const r = await podman([
|
|
168
|
+
"run", "--rm", "-i", ...logDriverArgs(), "--name", `devbot-wi-${workItemId}-attachdl`,
|
|
169
|
+
...volumeSelinuxArgs(workItemId),
|
|
170
|
+
"-v", `${vol}:/workspace:Z`,
|
|
171
|
+
builderImage,
|
|
172
|
+
"sh", "-c", writeFileScript(abs),
|
|
173
|
+
], { timeoutMs: 60_000, input: bytes.toString("base64") });
|
|
174
|
+
if (r.code === EXIT_EXISTS) {
|
|
175
|
+
throw new ExistsError(abs);
|
|
176
|
+
}
|
|
177
|
+
if (r.code === EXIT_UNSAFE_PATH) {
|
|
178
|
+
const reason = redact(r.stderr.trim().split("\n").pop() ?? "");
|
|
179
|
+
throw new Error(`attachment path rejected: ${reason || `escapes ${INCOMING_DIR_ABS}: ${abs}`}`);
|
|
180
|
+
}
|
|
181
|
+
if (r.code !== 0) {
|
|
182
|
+
const msg = redact(r.stderr.trim().slice(-300));
|
|
183
|
+
log.warn("attachment write failed", { workItemId, code: r.code });
|
|
184
|
+
throw new Error(`failed to write attachment (exit ${r.code})${msg ? `: ${msg}` : ""}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=volume-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"volume-files.js","sourceRoot":"","sources":["../../src/mcp/volume-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
import { log } from "./logger.js";
|
|
3
|
+
import { encrypt, decryptOrPlaintext } from "./crypto-util.js";
|
|
4
|
+
import { McpAuthEntrySchema } from "./types/mcp-config.js";
|
|
5
|
+
// Per-project MCP OAuth entries — one row per (project_id, server name).
|
|
6
|
+
// Stores the opencode `mcp-auth.json` entry for a server, scoped to the
|
|
7
|
+
// project so revoking a project drops its tokens. Same DB file (devbot.db,
|
|
8
|
+
// 0600). Secrets (accessToken / clientSecret) live here in plaintext, same
|
|
9
|
+
// posture as project_tokens.signing_token and mcp_thread_tokens.
|
|
10
|
+
export function initMcpAuthTables() {
|
|
11
|
+
db.exec(`
|
|
12
|
+
CREATE TABLE IF NOT EXISTS project_mcp_auth (
|
|
13
|
+
project_id INTEGER NOT NULL,
|
|
14
|
+
name TEXT NOT NULL,
|
|
15
|
+
entry TEXT NOT NULL,
|
|
16
|
+
created_at INTEGER NOT NULL,
|
|
17
|
+
updated_at INTEGER NOT NULL,
|
|
18
|
+
PRIMARY KEY (project_id, name)
|
|
19
|
+
)
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
export function getMcpAuthEntry(projectId, name) {
|
|
23
|
+
return getMcpAuth(projectId, name);
|
|
24
|
+
}
|
|
25
|
+
function getMcpAuth(projectId, name) {
|
|
26
|
+
const row = db
|
|
27
|
+
.prepare("SELECT entry FROM project_mcp_auth WHERE project_id = ? AND name = ?")
|
|
28
|
+
.get(projectId, name);
|
|
29
|
+
if (!row)
|
|
30
|
+
return null;
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(decryptOrPlaintext(row.entry));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
log.warn("mcp auth row not JSON, dropping", { projectId, name });
|
|
37
|
+
deleteMcpAuth(projectId, name);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const v = McpAuthEntrySchema.safeParse(parsed);
|
|
41
|
+
if (!v.success) {
|
|
42
|
+
log.warn("mcp auth row failed schema, dropping", { projectId, name });
|
|
43
|
+
deleteMcpAuth(projectId, name);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return v.data;
|
|
47
|
+
}
|
|
48
|
+
// Stripped shape for the browser — never exposes accessToken / clientSecret.
|
|
49
|
+
export function getMcpAuthSummary(projectId, name) {
|
|
50
|
+
const e = getMcpAuth(projectId, name);
|
|
51
|
+
if (!e)
|
|
52
|
+
return null;
|
|
53
|
+
return {
|
|
54
|
+
hasTokens: true,
|
|
55
|
+
hasRefreshToken: !!e.tokens.refreshToken,
|
|
56
|
+
expiresAt: e.tokens.expiresAt,
|
|
57
|
+
scope: e.tokens.scope,
|
|
58
|
+
hasClientInfo: !!e.clientInfo,
|
|
59
|
+
serverUrl: e.serverUrl,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function upsertMcpAuth(projectId, name, entry) {
|
|
63
|
+
const v = McpAuthEntrySchema.safeParse(entry);
|
|
64
|
+
if (!v.success) {
|
|
65
|
+
throw new Error("invalid mcp auth entry");
|
|
66
|
+
}
|
|
67
|
+
const now = Math.floor(Date.now() / 1000);
|
|
68
|
+
db.prepare(`INSERT OR REPLACE INTO project_mcp_auth
|
|
69
|
+
(project_id, name, entry, created_at, updated_at)
|
|
70
|
+
VALUES (?, ?, ?,
|
|
71
|
+
COALESCE((SELECT created_at FROM project_mcp_auth WHERE project_id = ? AND name = ?), ?),
|
|
72
|
+
?)`).run(projectId, name, encrypt(JSON.stringify(v.data)), projectId, name, now, now);
|
|
73
|
+
}
|
|
74
|
+
export function deleteMcpAuth(projectId, name) {
|
|
75
|
+
const r = db
|
|
76
|
+
.prepare("DELETE FROM project_mcp_auth WHERE project_id = ? AND name = ?")
|
|
77
|
+
.run(projectId, name);
|
|
78
|
+
return r.changes > 0;
|
|
79
|
+
}
|
|
80
|
+
export function deleteAllMcpAuth(projectId) {
|
|
81
|
+
db.prepare("DELETE FROM project_mcp_auth WHERE project_id = ?").run(projectId);
|
|
82
|
+
}
|
|
83
|
+
// Every (scope, name) pair that has a stored auth row. Used by the refresh
|
|
84
|
+
// scheduler to sweep global + all project scopes without enumerating projects.
|
|
85
|
+
export function listMcpAuthScopes() {
|
|
86
|
+
return db
|
|
87
|
+
.prepare("SELECT project_id AS scopeId, name FROM project_mcp_auth ORDER BY project_id, name")
|
|
88
|
+
.all();
|
|
89
|
+
}
|
|
90
|
+
// Build the opencode `mcp-auth.json` object for a project: a map of
|
|
91
|
+
// { [name]: entry } for every stored auth row. The runner writes this to
|
|
92
|
+
// /home/devbot/.xdg/data/opencode/mcp-auth.json on the per-work-item xdg
|
|
93
|
+
// volume (outside the cloned repo, so it can never be committed).
|
|
94
|
+
// Re-validates each row; invalid rows are skipped (never crash a run over
|
|
95
|
+
// one hand-edited DB row). `serverUrl` is force-overwritten with the matching
|
|
96
|
+
// config's url before emit — opencode's getForUrl() silently ignores tokens
|
|
97
|
+
// whose serverUrl doesn't match the config url, so this rewrite is required.
|
|
98
|
+
export function buildMcpAuthJson(projectId, configs) {
|
|
99
|
+
const rows = db
|
|
100
|
+
.prepare("SELECT name, entry FROM project_mcp_auth WHERE project_id = ?")
|
|
101
|
+
.all(projectId);
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const r of rows) {
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
parsed = JSON.parse(decryptOrPlaintext(r.entry));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
log.warn("mcp auth row not JSON, skipping", { projectId, name: r.name });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const v = McpAuthEntrySchema.safeParse(parsed);
|
|
113
|
+
if (!v.success) {
|
|
114
|
+
log.warn("mcp auth row failed schema, skipping", { projectId, name: r.name });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const cfg = configs.get(r.name);
|
|
118
|
+
const entry = { ...v.data };
|
|
119
|
+
if (cfg?.url)
|
|
120
|
+
entry.serverUrl = cfg.url;
|
|
121
|
+
out[r.name] = entry;
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
// Merge auth entries across scopes in order; later scopes win on name collision
|
|
126
|
+
// (project overrides global). Same serverUrl rewrite as buildMcpAuthJson.
|
|
127
|
+
export function buildMcpAuthJsonForScopes(scopeIds, configs) {
|
|
128
|
+
const out = {};
|
|
129
|
+
for (const scope of scopeIds) {
|
|
130
|
+
Object.assign(out, buildMcpAuthJson(scope, configs));
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=mcp-auth-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-auth-store.js","sourceRoot":"","sources":["../src/mcp-auth-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
import { log } from "./logger.js";
|
|
3
|
+
import { McpServerConfigSchema } from "./types/mcp-config.js";
|
|
4
|
+
import { GLOBAL_SCOPE_ID } from "./types/global-settings.js";
|
|
5
|
+
// Per-project external MCP server configs. Same DB file as project_tokens and
|
|
6
|
+
// mcp_thread_tokens (devbot.db, 0600). Keyed by (project_id, name); the config
|
|
7
|
+
// JSON is validated by McpServerConfigSchema on write and re-validated on read
|
|
8
|
+
// (the runner is the trust boundary for what opencode actually receives).
|
|
9
|
+
export function initMcpConfigTables() {
|
|
10
|
+
db.exec(`
|
|
11
|
+
CREATE TABLE IF NOT EXISTS project_mcp_servers (
|
|
12
|
+
project_id INTEGER NOT NULL,
|
|
13
|
+
name TEXT NOT NULL,
|
|
14
|
+
config TEXT NOT NULL,
|
|
15
|
+
created_at INTEGER NOT NULL,
|
|
16
|
+
updated_at INTEGER NOT NULL,
|
|
17
|
+
PRIMARY KEY (project_id, name)
|
|
18
|
+
)
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
export function listMcpServers(projectId) {
|
|
22
|
+
const rows = db
|
|
23
|
+
.prepare("SELECT name, config FROM project_mcp_servers WHERE project_id = ? ORDER BY name")
|
|
24
|
+
.all(projectId);
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const r of rows) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(r.config);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
log.warn("mcp config row not JSON, skipping", { projectId, name: r.name });
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const v = McpServerConfigSchema.safeParse(parsed);
|
|
36
|
+
if (!v.success) {
|
|
37
|
+
log.warn("mcp config row failed schema, skipping", { projectId, name: r.name });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
out.push({ name: r.name, config: v.data });
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
export function upsertMcpServer(projectId, name, config) {
|
|
45
|
+
// Re-validate at the store boundary — the API layer validates too, but this
|
|
46
|
+
// is the trust boundary for the runner.
|
|
47
|
+
const v = McpServerConfigSchema.safeParse(config);
|
|
48
|
+
if (!v.success) {
|
|
49
|
+
throw new Error("invalid mcp server config");
|
|
50
|
+
}
|
|
51
|
+
const now = Math.floor(Date.now() / 1000);
|
|
52
|
+
// COALESCE preserves created_at across re-PUTs; updated_at always bumps.
|
|
53
|
+
db.prepare(`INSERT OR REPLACE INTO project_mcp_servers
|
|
54
|
+
(project_id, name, config, created_at, updated_at)
|
|
55
|
+
VALUES (?, ?, ?,
|
|
56
|
+
COALESCE((SELECT created_at FROM project_mcp_servers WHERE project_id = ? AND name = ?), ?),
|
|
57
|
+
?)`).run(projectId, name, JSON.stringify(v.data), projectId, name, now, now);
|
|
58
|
+
}
|
|
59
|
+
export function deleteMcpServer(projectId, name) {
|
|
60
|
+
const r = db
|
|
61
|
+
.prepare("DELETE FROM project_mcp_servers WHERE project_id = ? AND name = ?")
|
|
62
|
+
.run(projectId, name);
|
|
63
|
+
return r.changes > 0;
|
|
64
|
+
}
|
|
65
|
+
export function deleteAllMcpServers(projectId) {
|
|
66
|
+
db.prepare("DELETE FROM project_mcp_servers WHERE project_id = ?").run(projectId);
|
|
67
|
+
}
|
|
68
|
+
// ---- Global scope (project_id = GLOBAL_SCOPE_ID) ---------------------------
|
|
69
|
+
// Global MCP servers reuse the per-project table with the sentinel scope id.
|
|
70
|
+
// The runner merges global → project (project wins on name collision).
|
|
71
|
+
export function listGlobalMcpServers() {
|
|
72
|
+
return listMcpServers(GLOBAL_SCOPE_ID);
|
|
73
|
+
}
|
|
74
|
+
export function upsertGlobalMcpServer(name, config) {
|
|
75
|
+
upsertMcpServer(GLOBAL_SCOPE_ID, name, config);
|
|
76
|
+
}
|
|
77
|
+
export function deleteGlobalMcpServer(name) {
|
|
78
|
+
return deleteMcpServer(GLOBAL_SCOPE_ID, name);
|
|
79
|
+
}
|
|
80
|
+
export function deleteAllGlobalMcpServers() {
|
|
81
|
+
deleteAllMcpServers(GLOBAL_SCOPE_ID);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=mcp-config-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-config-store.js","sourceRoot":"","sources":["../src/mcp-config-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAwB,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,0EAA0E;AAE1E,MAAM,UAAU,mBAAmB;IACjC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;GASP,CAAC,CAAC;AACL,CAAC;AAOD,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,iFAAiF,CAAC;SAC1F,GAAG,CAAC,SAAS,CAA4C,CAAC;IAC7D,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,CAAC,IAAI,CAAC,mCAAmC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3E,SAAS;QACX,CAAC;QACD,MAAM,CAAC,GAAG,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YACf,GAAG,CAAC,IAAI,CAAC,wCAAwC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAChF,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,IAAY,EAAE,MAAuB;IACtF,4EAA4E;IAC5E,wCAAwC;IACxC,MAAM,CAAC,GAAG,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,yEAAyE;IACzE,EAAE,CAAC,OAAO,CACR;;;;UAIM,CACP,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,IAAY;IAC7D,MAAM,CAAC,GAAG,EAAE;SACT,OAAO,CAAC,mEAAmE,CAAC;SAC5E,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACxB,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACpF,CAAC;AAED,+EAA+E;AAC/E,6EAA6E;AAC7E,uEAAuE;AAEvE,MAAM,UAAU,oBAAoB;IAClC,OAAO,cAAc,CAAC,eAAe,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,MAAuB;IACzE,eAAe,CAAC,eAAe,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,OAAO,eAAe,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,mBAAmB,CAAC,eAAe,CAAC,CAAC;AACvC,CAAC"}
|