@desplega.ai/agent-swarm 1.113.0 → 1.115.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/README.md +2 -2
- package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
- package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
- package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
- package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
- package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
- package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
- package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
- package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
- package/dist/cli-0pygq9r1.js +429 -0
- package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
- package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
- package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
- package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
- package/dist/cli-52fg7s7x.js +269 -0
- package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
- package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
- package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
- package/dist/cli-86fdnerz.js +405 -0
- package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
- package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
- package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
- package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
- package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
- package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
- package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
- package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
- package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
- package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
- package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
- package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
- package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
- package/dist/cli-jkhwmmgg.js +136 -0
- package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
- package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
- package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
- package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
- package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
- package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
- package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
- package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
- package/dist/cli-t0vqevnm.js +393 -0
- package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
- package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
- package/dist/cli-wh91zby6.js +187222 -0
- package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
- package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
- package/dist/cli.js +32 -15
- package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
- package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
- package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
- package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
- package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
- package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
- package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
- package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
- package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
- package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
- package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
- package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
- package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
- package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
- package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
- package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
- package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
- package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
- package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
- package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
- package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
- package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
- package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
- package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
- package/dist/rbac-roles-kfesjy9y.js +28 -0
- package/dist/rbac-roles-mwcwhbxc.js +283 -0
- package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
- package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
- package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
- package/openapi.json +1327 -20
- package/package.json +3 -3
- package/src/agentmail/handlers.ts +16 -6
- package/src/agentmail/templates.ts +25 -5
- package/src/be/audit-user.ts +28 -4
- package/src/be/db-queries/oauth.ts +42 -0
- package/src/be/db.ts +314 -28
- package/src/be/identity.ts +79 -0
- package/src/be/mcp-proxy.ts +132 -0
- package/src/be/migrations/109_rbac_roles.sql +79 -0
- package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
- package/src/be/migrations/112_script_connections_graphql.sql +96 -0
- package/src/be/migrations/113_task_routing_affinity.sql +9 -0
- package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
- package/src/be/modelsdev-cache.json +135 -0
- package/src/be/oauth-credential-bindings.ts +35 -0
- package/src/be/oauth-refresh-sweep.ts +159 -0
- package/src/be/rbac-audit.ts +52 -13
- package/src/be/rbac-roles.ts +424 -0
- package/src/be/script-connections.ts +459 -40
- package/src/be/script-credential-broker.ts +29 -3
- package/src/be/scripts/typecheck.ts +51 -7
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
- package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
- package/src/be/users.ts +24 -0
- package/src/cli.tsx +17 -0
- package/src/github/handlers.ts +10 -9
- package/src/github/templates.ts +45 -9
- package/src/gitlab/handlers.ts +20 -8
- package/src/gitlab/templates.ts +18 -6
- package/src/heartbeat/heartbeat.ts +143 -21
- package/src/http/all-routes.ts +3 -0
- package/src/http/approval-requests.ts +12 -0
- package/src/http/config.ts +44 -10
- package/src/http/core.ts +30 -3
- package/src/http/favorites.ts +1 -5
- package/src/http/index.ts +25 -1
- package/src/http/mcp-oauth.ts +2 -0
- package/src/http/mcp-servers.ts +44 -3
- package/src/http/oauth-generic.ts +105 -0
- package/src/http/poll.ts +20 -3
- package/src/http/script-connection-proxy.ts +116 -0
- package/src/http/script-connections.ts +1657 -0
- package/src/http/scripts.ts +27 -7
- package/src/http/skills.ts +11 -0
- package/src/http/tasks.ts +27 -13
- package/src/http/x.ts +6 -2
- package/src/integrations/kapso/inbound.ts +23 -6
- package/src/jira/sync.ts +111 -17
- package/src/jira/templates.ts +10 -2
- package/src/mcp-client/http-client.ts +194 -0
- package/src/oauth/app-validation.ts +16 -0
- package/src/oauth/ensure-token.ts +69 -14
- package/src/oauth/mcp-wrapper.ts +15 -0
- package/src/oauth/wrapper.ts +48 -20
- package/src/prompts/session-templates.ts +3 -3
- package/src/providers/codex-models.ts +100 -30
- package/src/providers/modelsdev-reasoning.json +51 -0
- package/src/providers/pi-mono-mcp-client.ts +2 -133
- package/src/providers/reasoning-effort.ts +11 -5
- package/src/rbac/admission.ts +68 -0
- package/src/rbac/index.ts +2 -0
- package/src/rbac/legacy-policy.ts +12 -0
- package/src/rbac/permissions.ts +48 -0
- package/src/scheduler/scheduler.ts +9 -1
- package/src/scripts-runtime/api-client.ts +151 -19
- package/src/scripts-runtime/api-types.ts +38 -6
- package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
- package/src/scripts-runtime/credential-broker/types.ts +8 -0
- package/src/scripts-runtime/ctx.ts +11 -1
- package/src/scripts-runtime/eval-harness.ts +5 -1
- package/src/scripts-runtime/executors/types.ts +2 -1
- package/src/scripts-runtime/loader.ts +3 -1
- package/src/scripts-runtime/mcp-client.ts +87 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +6 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
- package/src/server-user.ts +114 -59
- package/src/server.ts +12 -0
- package/src/slack/assistant.ts +14 -6
- package/src/slack/enrich.ts +17 -0
- package/src/slack/handlers.ts +8 -22
- package/src/slack/responses.ts +91 -1
- package/src/slack/thread-buffer.ts +6 -3
- package/src/slack/watcher.ts +9 -6
- package/src/tasks/worker-follow-up.ts +100 -3
- package/src/tests/agentmail-handlers.test.ts +32 -3
- package/src/tests/approval-requests.test.ts +61 -0
- package/src/tests/audit-user.test.ts +47 -1
- package/src/tests/codex-adapter.test.ts +46 -23
- package/src/tests/credential-broker.test.ts +5 -2
- package/src/tests/delete-page-tool.test.ts +197 -0
- package/src/tests/github-handlers.test.ts +28 -2
- package/src/tests/gitlab-handlers.test.ts +125 -0
- package/src/tests/heartbeat.test.ts +48 -3
- package/src/tests/identity.test.ts +117 -0
- package/src/tests/jira-sync.test.ts +99 -1
- package/src/tests/kapso-inbound.test.ts +7 -0
- package/src/tests/mcp-tools-user.test.ts +60 -6
- package/src/tests/model-control.test.ts +8 -2
- package/src/tests/oauth-credential-bindings.test.ts +490 -0
- package/src/tests/oauth-refresh-sweep.test.ts +160 -0
- package/src/tests/pool-affinity.test.ts +401 -0
- package/src/tests/prompt-template-remaining.test.ts +2 -2
- package/src/tests/rbac-admission-e2e.test.ts +386 -0
- package/src/tests/rbac-admission.test.ts +492 -0
- package/src/tests/rbac-e2e-helpers.ts +3 -0
- package/src/tests/rbac-engine.test.ts +50 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
- package/src/tests/rbac-roles.test.ts +328 -0
- package/src/tests/reasoning-effort.test.ts +15 -2
- package/src/tests/schedule-target-type.test.ts +50 -0
- package/src/tests/scheduled-tasks.test.ts +40 -0
- package/src/tests/script-apis-mcp.test.ts +37 -13
- package/src/tests/script-connections-http.test.ts +1158 -0
- package/src/tests/script-connections-mcp.test.ts +894 -0
- package/src/tests/script-connections.test.ts +754 -6
- package/src/tests/scripts-external-api.test.ts +49 -1
- package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
- package/src/tests/scripts-runtime.test.ts +291 -0
- package/src/tests/seed-scripts.test.ts +36 -0
- package/src/tests/slack-identity-resolution.test.ts +37 -3
- package/src/tests/slack-inline-output.test.ts +65 -0
- package/src/tests/slack-thread-buffer.test.ts +28 -0
- package/src/tests/tool-annotations.test.ts +1 -0
- package/src/tests/user-token-rest-auth.test.ts +140 -1
- package/src/tests/workflow-swarm-script.test.ts +44 -0
- package/src/tests/workflow-triggers-v2.test.ts +300 -1
- package/src/tools/credential-bindings/tool.ts +277 -18
- package/src/tools/delete-page.ts +145 -0
- package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
- package/src/tools/request-human-input.ts +2 -0
- package/src/tools/resolve-user.ts +77 -27
- package/src/tools/script-apis.ts +62 -3
- package/src/tools/script-connections/tool.ts +275 -31
- package/src/tools/send-task.ts +15 -1
- package/src/tools/task-action.ts +34 -2
- package/src/tools/templates.ts +53 -1
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +2 -0
- package/src/tools/workflows/create-workflow.ts +8 -1
- package/src/tools/workflows/update-workflow.ts +11 -2
- package/src/types.ts +90 -11
- package/src/utils/scoped-resource.ts +25 -0
- package/src/workflows/executors/human-in-the-loop.ts +1 -0
- package/src/workflows/executors/swarm-script.ts +7 -1
- package/src/workflows/triggers.ts +146 -18
- package/templates/skills/swarm-scripts/content.md +33 -8
- package/dist/cli-bgef578c.js +0 -771
- package/dist/cli-c9mtm09b.js +0 -43
- package/dist/cli-ceewqajw.js +0 -1698
- package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
- package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
- package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
- package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
- package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
- package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
- package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
- package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
- package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
SUPPORTED_PROTOCOL_VERSIONS,
|
|
8
8
|
agentWithCapacity,
|
|
9
9
|
applyRating,
|
|
10
|
+
assertOAuthAppUrlsSafe,
|
|
11
|
+
assertOAuthProviderIsNotReserved,
|
|
10
12
|
assertSelectOnlyQuery,
|
|
11
|
-
can,
|
|
12
13
|
canClaim,
|
|
13
14
|
canReadMemory,
|
|
14
15
|
cancelTaskHandler,
|
|
15
16
|
cancelTaskInputSchema,
|
|
16
17
|
cancelTaskOutputSchema,
|
|
17
|
-
clearAuditSink,
|
|
18
18
|
clearJiraMetadata,
|
|
19
19
|
createEvent,
|
|
20
20
|
createEventsBatch,
|
|
@@ -25,8 +25,11 @@ import {
|
|
|
25
25
|
describeRequestRoute,
|
|
26
26
|
emitBudgetRefusalSideEffects,
|
|
27
27
|
enforceContentLengthCap,
|
|
28
|
+
enqueueAdmissionRow,
|
|
29
|
+
enqueueAuditRow,
|
|
28
30
|
executeReadOnlyQuery,
|
|
29
31
|
expandCandidatesWithGraph,
|
|
32
|
+
flushAuditBuffer,
|
|
30
33
|
getEnabledCapabilities,
|
|
31
34
|
getEventCountsFiltered,
|
|
32
35
|
getEventsFiltered,
|
|
@@ -89,18 +92,24 @@ import {
|
|
|
89
92
|
registerJiraWebhook,
|
|
90
93
|
reloadGlobalConfigsAndIntegrations,
|
|
91
94
|
resolveHttpAuditUserId,
|
|
95
|
+
resolveScopedResourceId,
|
|
96
|
+
resolveTaskAuditUserId,
|
|
92
97
|
route,
|
|
93
98
|
safeRequestUrlForLog,
|
|
94
99
|
scheduleIntegrationsReload,
|
|
100
|
+
scopedResourceScopeIdSchema,
|
|
95
101
|
sendKapsoReaction,
|
|
96
102
|
sendTaskHandler,
|
|
97
103
|
sendTaskOutputSchema,
|
|
98
|
-
setAuditSink,
|
|
99
104
|
setCorsHeaders,
|
|
100
105
|
shouldPersistAutomaticTaskMemory,
|
|
101
106
|
snapshotMetric,
|
|
102
107
|
snapshotPage,
|
|
108
|
+
startAuditGc,
|
|
109
|
+
startAuditWriter,
|
|
103
110
|
startSpan,
|
|
111
|
+
stopAuditGc,
|
|
112
|
+
stopAuditWriter,
|
|
104
113
|
storeLinks,
|
|
105
114
|
taskActionHandler,
|
|
106
115
|
taskActionInputSchema,
|
|
@@ -114,16 +123,16 @@ import {
|
|
|
114
123
|
withRemoteContext,
|
|
115
124
|
withSpanContext,
|
|
116
125
|
writeSkillsToFilesystem
|
|
117
|
-
} from "./cli-
|
|
126
|
+
} from "./cli-hrhye2rm.js";
|
|
127
|
+
import"./cli-1z7048dy.js";
|
|
118
128
|
import {
|
|
119
129
|
BROWSER_SDK_JS,
|
|
120
130
|
SWARM_UI_JS
|
|
121
131
|
} from "./cli-xm2tyqfe.js";
|
|
122
|
-
import"./cli-1z7048dy.js";
|
|
123
132
|
import {
|
|
124
133
|
REFERENCES_SOURCE_MAX_LENGTH,
|
|
125
134
|
sanitizeReferencesSource
|
|
126
|
-
} from "./cli-
|
|
135
|
+
} from "./cli-ftyf58xj.js";
|
|
127
136
|
import {
|
|
128
137
|
deriveCodexKeySuffix,
|
|
129
138
|
getValidCodexOAuth,
|
|
@@ -134,41 +143,38 @@ import {
|
|
|
134
143
|
} from "./cli-gz9sbyk3.js";
|
|
135
144
|
import {
|
|
136
145
|
reasoningCapability
|
|
137
|
-
} from "./cli-
|
|
138
|
-
import
|
|
139
|
-
acquireOAuthRefreshLock,
|
|
140
|
-
buildAuthorizationUrl,
|
|
141
|
-
calculatePKCECodeChallenge,
|
|
142
|
-
deleteOAuthTokens,
|
|
143
|
-
ensureTokenOrThrow,
|
|
144
|
-
exchangeCode,
|
|
145
|
-
generateRandomCodeVerifier,
|
|
146
|
-
generateRandomState,
|
|
147
|
-
getOAuthApp,
|
|
148
|
-
getOAuthTokens,
|
|
149
|
-
releaseOAuthRefreshLock
|
|
150
|
-
} from "./cli-bgef578c.js";
|
|
151
|
-
import"./cli-36ymmpmp.js";
|
|
146
|
+
} from "./cli-e61fs2hp.js";
|
|
147
|
+
import"./cli-3samkyap.js";
|
|
152
148
|
import {
|
|
153
149
|
normalizeModelKey
|
|
154
|
-
} from "./cli-
|
|
150
|
+
} from "./cli-tece6zgm.js";
|
|
151
|
+
import {
|
|
152
|
+
getUserGrant
|
|
153
|
+
} from "./cli-52fg7s7x.js";
|
|
154
|
+
import {
|
|
155
|
+
can,
|
|
156
|
+
clearAuditSink,
|
|
157
|
+
decideToolAdmission,
|
|
158
|
+
isRbacEnabled,
|
|
159
|
+
setAuditSink
|
|
160
|
+
} from "./cli-86fdnerz.js";
|
|
155
161
|
import {
|
|
156
|
-
SCRIPT_STDLIB_TYPES,
|
|
157
162
|
ensureAgentFsCredentialsForAgent,
|
|
158
163
|
extractArgsJsonSchema,
|
|
159
164
|
extractScriptSignature,
|
|
160
165
|
scriptSdkTypesWithGeneratedApis,
|
|
166
|
+
scriptStdlibTypesWithGeneratedApis,
|
|
161
167
|
typecheckScript
|
|
162
|
-
} from "./cli-
|
|
168
|
+
} from "./cli-12vb0xg6.js";
|
|
163
169
|
import {
|
|
164
170
|
checkHeartbeatChecklist,
|
|
165
171
|
runHeartbeatSweep,
|
|
166
172
|
stopHeartbeat
|
|
167
|
-
} from "./cli-
|
|
173
|
+
} from "./cli-beea4a3y.js";
|
|
168
174
|
import {
|
|
169
175
|
createResumeFollowUp,
|
|
170
176
|
createWorkerTaskFollowUp
|
|
171
|
-
} from "./cli-
|
|
177
|
+
} from "./cli-dcca6j8m.js";
|
|
172
178
|
import"./cli-fad8m16k.js";
|
|
173
179
|
import {
|
|
174
180
|
calculateNextRun,
|
|
@@ -176,7 +182,7 @@ import {
|
|
|
176
182
|
ensure,
|
|
177
183
|
initialize,
|
|
178
184
|
require_dist
|
|
179
|
-
} from "./cli-
|
|
185
|
+
} from "./cli-shvaxw27.js";
|
|
180
186
|
import {
|
|
181
187
|
RawLlmConfigSchema,
|
|
182
188
|
TriggerSchemaError,
|
|
@@ -187,6 +193,8 @@ import {
|
|
|
187
193
|
executeRawLlm,
|
|
188
194
|
generateEdges,
|
|
189
195
|
getExecutorRegistry,
|
|
196
|
+
getOAuthBindingTokenStatus,
|
|
197
|
+
getOAuthProviderConfig,
|
|
190
198
|
handleWebhookTrigger,
|
|
191
199
|
initWorkflows,
|
|
192
200
|
retryFailedRun,
|
|
@@ -196,31 +204,63 @@ import {
|
|
|
196
204
|
validateDefinition,
|
|
197
205
|
validateJsonSchema,
|
|
198
206
|
verifyHmacSignature
|
|
199
|
-
} from "./cli-
|
|
207
|
+
} from "./cli-q1rx7g0d.js";
|
|
200
208
|
import {
|
|
201
209
|
getApiKey
|
|
202
210
|
} from "./cli-f14fvzag.js";
|
|
203
211
|
import {
|
|
212
|
+
CredentialBindingSchema,
|
|
213
|
+
applyMcpOAuthRefresh,
|
|
214
|
+
assertUrlSafe,
|
|
215
|
+
buildAuthorizeUrl,
|
|
216
|
+
callMcpServerTool,
|
|
217
|
+
computeExpiresAt,
|
|
218
|
+
consumeMcpOAuthPending,
|
|
204
219
|
createScriptApi,
|
|
220
|
+
deleteMcpOAuthToken,
|
|
205
221
|
deleteScript,
|
|
206
222
|
deleteScriptApi,
|
|
223
|
+
discoverAuthorizationServerMetadata,
|
|
224
|
+
discoverProtectedResourceMetadata,
|
|
225
|
+
ensureMcpToken,
|
|
226
|
+
exchangeCodeForTokens,
|
|
227
|
+
gcMcpOAuthPending,
|
|
228
|
+
getMcpOAuthToken,
|
|
207
229
|
getScript,
|
|
208
230
|
getScriptApiById,
|
|
209
231
|
getScriptApiConnectionDescriptors,
|
|
210
232
|
getScriptApiSecret,
|
|
211
233
|
getScriptApiTypes,
|
|
212
234
|
getScriptById,
|
|
235
|
+
getScriptConnectionById,
|
|
236
|
+
getScriptMcpConnectionDescriptors,
|
|
237
|
+
getScriptMcpTypes,
|
|
238
|
+
insertMcpOAuthPending,
|
|
239
|
+
listRelationalCredentialBindings,
|
|
213
240
|
listScriptApisForScript,
|
|
241
|
+
listScriptConnections,
|
|
214
242
|
listScriptVersions,
|
|
215
243
|
listScripts,
|
|
244
|
+
placeholderForConfigKey,
|
|
245
|
+
publicEndpointSsrfOptions,
|
|
216
246
|
recordScriptApiUsage,
|
|
247
|
+
refreshMcpToken,
|
|
248
|
+
refreshScriptConnection,
|
|
249
|
+
registerClient,
|
|
250
|
+
revokeMcpToken,
|
|
217
251
|
rotateScriptApiSecret,
|
|
252
|
+
setMcpServerAuthMethod,
|
|
253
|
+
setScriptConnectionEnabled,
|
|
218
254
|
updateScriptApi,
|
|
219
|
-
|
|
220
|
-
|
|
255
|
+
upsertCredentialBinding,
|
|
256
|
+
upsertMcpOAuthToken,
|
|
257
|
+
upsertScriptByName,
|
|
258
|
+
upsertScriptConnection
|
|
259
|
+
} from "./cli-r9f6ngrf.js";
|
|
260
|
+
import"./cli-jkhwmmgg.js";
|
|
221
261
|
import {
|
|
222
262
|
searchScripts
|
|
223
|
-
} from "./cli-
|
|
263
|
+
} from "./cli-cmw9rzy5.js";
|
|
224
264
|
import {
|
|
225
265
|
CANDIDATE_SET_MULTIPLIER,
|
|
226
266
|
getEmbeddingProvider,
|
|
@@ -228,22 +268,33 @@ import {
|
|
|
228
268
|
init_constants,
|
|
229
269
|
init_reranker,
|
|
230
270
|
rerank
|
|
231
|
-
} from "./cli-
|
|
271
|
+
} from "./cli-7xje5amz.js";
|
|
232
272
|
import"./cli-q25ejbyn.js";
|
|
233
|
-
import
|
|
273
|
+
import {
|
|
274
|
+
acquireOAuthRefreshLock,
|
|
275
|
+
buildAuthorizationUrl,
|
|
276
|
+
deleteOAuthTokens,
|
|
277
|
+
ensureTokenOrThrow,
|
|
278
|
+
exchangeCode,
|
|
279
|
+
forceRefreshTokenOrThrow,
|
|
280
|
+
getOAuthApp,
|
|
281
|
+
getOAuthTokens,
|
|
282
|
+
releaseOAuthRefreshLock,
|
|
283
|
+
upsertOAuthApp
|
|
284
|
+
} from "./cli-t0vqevnm.js";
|
|
285
|
+
import"./cli-0pygq9r1.js";
|
|
286
|
+
import"./cli-tffvrh6j.js";
|
|
234
287
|
import {
|
|
235
288
|
getSlackApp,
|
|
236
289
|
startSlackApp,
|
|
237
290
|
stopSlackApp
|
|
238
|
-
} from "./cli-
|
|
291
|
+
} from "./cli-pet3aev6.js";
|
|
239
292
|
import"./cli-rkndnn90.js";
|
|
240
293
|
import"./cli-b0p7rfnd.js";
|
|
241
|
-
import {
|
|
242
|
-
recordUnmappedIdentity
|
|
243
|
-
} from "./cli-c9mtm09b.js";
|
|
244
294
|
import {
|
|
245
295
|
findOrCreateUserByEmail,
|
|
246
296
|
findUserByExternalId,
|
|
297
|
+
findUserById,
|
|
247
298
|
fingerprintApiKey,
|
|
248
299
|
getUserIdentities,
|
|
249
300
|
linkIdentity,
|
|
@@ -251,18 +302,21 @@ import {
|
|
|
251
302
|
listUserTokens,
|
|
252
303
|
mintToken,
|
|
253
304
|
recordIdentityEvent,
|
|
305
|
+
recordUnmappedIdentity,
|
|
306
|
+
renderIdentity,
|
|
307
|
+
resolveIdentity,
|
|
254
308
|
resolveUserByToken,
|
|
255
309
|
revokeToken,
|
|
256
310
|
unlinkIdentity
|
|
257
|
-
} from "./cli-
|
|
258
|
-
import"./cli-
|
|
311
|
+
} from "./cli-6aq8xhvm.js";
|
|
312
|
+
import"./cli-ma04faw5.js";
|
|
259
313
|
import"./cli-wspgs9bt.js";
|
|
260
314
|
import {
|
|
261
315
|
agentContextKey,
|
|
262
316
|
createTaskWithSiblingAwareness,
|
|
263
317
|
gitlabContextKey,
|
|
264
318
|
pageContextKey
|
|
265
|
-
} from "./cli-
|
|
319
|
+
} from "./cli-k3y4z3h5.js";
|
|
266
320
|
import {
|
|
267
321
|
getAppUrl,
|
|
268
322
|
getConfiguredAppUrls,
|
|
@@ -357,7 +411,6 @@ import {
|
|
|
357
411
|
createTaskExtended,
|
|
358
412
|
createUser,
|
|
359
413
|
createWorkflow,
|
|
360
|
-
decryptSecret,
|
|
361
414
|
deleteActiveSession,
|
|
362
415
|
deleteActiveSessionById,
|
|
363
416
|
deleteBudget,
|
|
@@ -376,7 +429,6 @@ import {
|
|
|
376
429
|
deleteTaskAttachment,
|
|
377
430
|
deleteUser,
|
|
378
431
|
deleteWorkflow,
|
|
379
|
-
encryptSecret,
|
|
380
432
|
failTask,
|
|
381
433
|
findTaskByVcs,
|
|
382
434
|
getActivePricingRow,
|
|
@@ -406,7 +458,6 @@ import {
|
|
|
406
458
|
getCurrentRequestAuth,
|
|
407
459
|
getDashboardCostSummary,
|
|
408
460
|
getDb,
|
|
409
|
-
getEncryptionKey,
|
|
410
461
|
getInboxSummary,
|
|
411
462
|
getInstanceActivity,
|
|
412
463
|
getKeyCostSummary,
|
|
@@ -464,7 +515,7 @@ import {
|
|
|
464
515
|
getTaskStats,
|
|
465
516
|
getTasksCount,
|
|
466
517
|
getTemplateDefinition,
|
|
467
|
-
|
|
518
|
+
getUnassignedTaskIdsForAgent,
|
|
468
519
|
getUserById,
|
|
469
520
|
getWorkflow,
|
|
470
521
|
getWorkflowRun,
|
|
@@ -477,7 +528,6 @@ import {
|
|
|
477
528
|
incrKv,
|
|
478
529
|
incrementPageViewCount,
|
|
479
530
|
initTelemetry,
|
|
480
|
-
init_crypto,
|
|
481
531
|
init_date_utils,
|
|
482
532
|
init_db,
|
|
483
533
|
init_registry,
|
|
@@ -516,7 +566,7 @@ import {
|
|
|
516
566
|
listWorkflows,
|
|
517
567
|
markKeyRateLimited,
|
|
518
568
|
maskSecrets,
|
|
519
|
-
|
|
569
|
+
normalizeDate,
|
|
520
570
|
pauseTask,
|
|
521
571
|
recordBudgetRefusalNotification,
|
|
522
572
|
recordInlineScriptRun,
|
|
@@ -574,22 +624,22 @@ import {
|
|
|
574
624
|
upsertSwarmConfig,
|
|
575
625
|
validateConfigValue,
|
|
576
626
|
withFavoriteFlags
|
|
577
|
-
} from "./cli-
|
|
627
|
+
} from "./cli-15y7qhzp.js";
|
|
578
628
|
import"./cli-z2zcxes1.js";
|
|
579
629
|
import {
|
|
580
630
|
init_zod
|
|
581
|
-
} from "./cli-
|
|
631
|
+
} from "./cli-bw80ck94.js";
|
|
582
632
|
import {
|
|
583
633
|
array,
|
|
584
634
|
exports_external,
|
|
585
635
|
number,
|
|
586
636
|
object,
|
|
587
637
|
string
|
|
588
|
-
} from "./cli-
|
|
638
|
+
} from "./cli-xz9aq0rf.js";
|
|
589
639
|
import {
|
|
590
640
|
init_package,
|
|
591
641
|
package_default
|
|
592
|
-
} from "./cli-
|
|
642
|
+
} from "./cli-b23axgz4.js";
|
|
593
643
|
import {
|
|
594
644
|
init_secret_scrubber,
|
|
595
645
|
registerVolatileSecret,
|
|
@@ -603,18 +653,18 @@ import {
|
|
|
603
653
|
} from "./cli-9aryf9tv.js";
|
|
604
654
|
import {
|
|
605
655
|
bedrockProviderModule
|
|
606
|
-
} from "./cli-
|
|
656
|
+
} from "./cli-6b4wwmm8.js";
|
|
657
|
+
import"./cli-cwy5hqbq.js";
|
|
607
658
|
import"./cli-jvv0g8bh.js";
|
|
608
659
|
import {
|
|
609
660
|
setBedrockProviderModule
|
|
610
661
|
} from "./cli-xw7p4wkq.js";
|
|
611
662
|
import"./cli-hv2fndhm.js";
|
|
612
|
-
import"./cli-cwy5hqbq.js";
|
|
613
663
|
import"./cli-pscrc6tw.js";
|
|
614
|
-
import"./cli-4wgngmwv.js";
|
|
615
|
-
import"./cli-vapyk8sw.js";
|
|
616
664
|
import"./cli-zyxs93y9.js";
|
|
665
|
+
import"./cli-4wgngmwv.js";
|
|
617
666
|
import"./cli-bsyka9ta.js";
|
|
667
|
+
import"./cli-vapyk8sw.js";
|
|
618
668
|
import"./cli-y2apkfe2.js";
|
|
619
669
|
import"./cli-m6pzzhqr.js";
|
|
620
670
|
import"./cli-7wmybrt5.js";
|
|
@@ -639,146 +689,6 @@ import {
|
|
|
639
689
|
} from "node:http";
|
|
640
690
|
init_db();
|
|
641
691
|
|
|
642
|
-
// src/be/rbac-audit.ts
|
|
643
|
-
init_db();
|
|
644
|
-
var FLUSH_INTERVAL_MS = 2000;
|
|
645
|
-
var FLUSH_MAX_ROWS = 200;
|
|
646
|
-
var AUDIT_GC_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
647
|
-
var DEFAULT_RETENTION_DAYS = 30;
|
|
648
|
-
var buffer = [];
|
|
649
|
-
var flushTimer = null;
|
|
650
|
-
var auditGcTimer = null;
|
|
651
|
-
var thresholdFlushScheduled = false;
|
|
652
|
-
function isAuditDisabled() {
|
|
653
|
-
return process.env.RBAC_AUDIT_DISABLED === "true";
|
|
654
|
-
}
|
|
655
|
-
function principalIdOf(principal) {
|
|
656
|
-
switch (principal.kind) {
|
|
657
|
-
case "agent":
|
|
658
|
-
return principal.agentId;
|
|
659
|
-
case "user":
|
|
660
|
-
return principal.userId;
|
|
661
|
-
case "operator":
|
|
662
|
-
return null;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
function resourceIdOf(resource) {
|
|
666
|
-
if (!resource)
|
|
667
|
-
return null;
|
|
668
|
-
switch (resource.kind) {
|
|
669
|
-
case "task":
|
|
670
|
-
return resource.taskId;
|
|
671
|
-
case "agent":
|
|
672
|
-
return resource.agentId;
|
|
673
|
-
case "kv-namespace":
|
|
674
|
-
return resource.namespace;
|
|
675
|
-
case "owned":
|
|
676
|
-
return resource.ownerAgentId ?? null;
|
|
677
|
-
case "none":
|
|
678
|
-
return null;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
function toRow(check, decision) {
|
|
682
|
-
return {
|
|
683
|
-
principalType: check.principal.kind,
|
|
684
|
-
principalId: principalIdOf(check.principal),
|
|
685
|
-
originatorUserId: null,
|
|
686
|
-
verb: check.verb,
|
|
687
|
-
resourceType: check.resource?.kind ?? null,
|
|
688
|
-
resourceId: resourceIdOf(check.resource),
|
|
689
|
-
decision: decision.allow ? "allow" : "deny",
|
|
690
|
-
reason: decision.allow ? null : decision.reason,
|
|
691
|
-
source: check.source
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
function enqueueAuditRow(check, decision) {
|
|
695
|
-
if (isAuditDisabled())
|
|
696
|
-
return;
|
|
697
|
-
try {
|
|
698
|
-
buffer.push(toRow(check, decision));
|
|
699
|
-
if (buffer.length >= FLUSH_MAX_ROWS && !thresholdFlushScheduled) {
|
|
700
|
-
thresholdFlushScheduled = true;
|
|
701
|
-
const t = setTimeout(() => {
|
|
702
|
-
thresholdFlushScheduled = false;
|
|
703
|
-
flushAuditBuffer();
|
|
704
|
-
}, 0);
|
|
705
|
-
if (typeof t.unref === "function")
|
|
706
|
-
t.unref();
|
|
707
|
-
}
|
|
708
|
-
} catch (err) {
|
|
709
|
-
console.warn("[rbac-audit] enqueue failed, dropping row:", err.message);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
function flushAuditBuffer() {
|
|
713
|
-
if (buffer.length === 0)
|
|
714
|
-
return;
|
|
715
|
-
const rows = buffer;
|
|
716
|
-
buffer = [];
|
|
717
|
-
try {
|
|
718
|
-
const db = getDb();
|
|
719
|
-
const stmt = db.prepare(`INSERT INTO permission_audit
|
|
720
|
-
(principalType, principalId, originatorUserId, verb, resourceType, resourceId, decision, reason, source)
|
|
721
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
722
|
-
const insertAll = db.transaction((batch) => {
|
|
723
|
-
for (const r of batch) {
|
|
724
|
-
stmt.run(r.principalType, r.principalId, r.originatorUserId, r.verb, r.resourceType, r.resourceId, r.decision, r.reason, r.source);
|
|
725
|
-
}
|
|
726
|
-
});
|
|
727
|
-
insertAll(rows);
|
|
728
|
-
} catch (err) {
|
|
729
|
-
console.warn(`[rbac-audit] flush failed, dropping ${rows.length} row(s):`, err.message);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
function flushIntervalMs() {
|
|
733
|
-
const v = Number(process.env.RBAC_AUDIT_FLUSH_MS);
|
|
734
|
-
return Number.isFinite(v) && v > 0 ? v : FLUSH_INTERVAL_MS;
|
|
735
|
-
}
|
|
736
|
-
function startAuditWriter(intervalMs = flushIntervalMs()) {
|
|
737
|
-
if (flushTimer)
|
|
738
|
-
return;
|
|
739
|
-
flushTimer = setInterval(() => flushAuditBuffer(), intervalMs);
|
|
740
|
-
if (typeof flushTimer?.unref === "function")
|
|
741
|
-
flushTimer.unref();
|
|
742
|
-
}
|
|
743
|
-
function stopAuditWriter() {
|
|
744
|
-
if (flushTimer) {
|
|
745
|
-
clearInterval(flushTimer);
|
|
746
|
-
flushTimer = null;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
function purgeExpiredAuditRows() {
|
|
750
|
-
try {
|
|
751
|
-
const days = Number(process.env.RBAC_AUDIT_RETENTION_DAYS) || DEFAULT_RETENTION_DAYS;
|
|
752
|
-
const result = getDb().prepare("DELETE FROM permission_audit WHERE ts < datetime('now', ?)").run(`-${days} days`);
|
|
753
|
-
return result.changes;
|
|
754
|
-
} catch (err) {
|
|
755
|
-
console.warn("[rbac-audit] retention purge failed:", err.message);
|
|
756
|
-
return 0;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function startAuditGc(intervalMs = AUDIT_GC_INTERVAL_MS) {
|
|
760
|
-
if (auditGcTimer)
|
|
761
|
-
return;
|
|
762
|
-
const purged = purgeExpiredAuditRows();
|
|
763
|
-
if (purged > 0) {
|
|
764
|
-
console.log(`[rbac-audit] Initial retention purge removed ${purged} audit row(s)`);
|
|
765
|
-
}
|
|
766
|
-
auditGcTimer = setInterval(() => {
|
|
767
|
-
const n = purgeExpiredAuditRows();
|
|
768
|
-
if (n > 0) {
|
|
769
|
-
console.log(`[rbac-audit] Retention purge removed ${n} audit row(s)`);
|
|
770
|
-
}
|
|
771
|
-
}, intervalMs);
|
|
772
|
-
if (typeof auditGcTimer?.unref === "function")
|
|
773
|
-
auditGcTimer.unref();
|
|
774
|
-
}
|
|
775
|
-
function stopAuditGc() {
|
|
776
|
-
if (auditGcTimer) {
|
|
777
|
-
clearInterval(auditGcTimer);
|
|
778
|
-
auditGcTimer = null;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
692
|
// src/gitlab/auth.ts
|
|
783
693
|
var initialized = false;
|
|
784
694
|
var webhookSecret = null;
|
|
@@ -896,7 +806,7 @@ registerTemplate({
|
|
|
896
806
|
eventType: "gitlab.merge_request.opened",
|
|
897
807
|
header: "[GitLab MR #{{mr_iid}}] {{mr_title}}",
|
|
898
808
|
defaultBody: `Repo: {{repo}}
|
|
899
|
-
Author:
|
|
809
|
+
Author: {{username}}
|
|
900
810
|
Branch: {{source_branch}} → {{target_branch}}
|
|
901
811
|
URL: {{mr_url}}
|
|
902
812
|
|
|
@@ -905,7 +815,10 @@ URL: {{mr_url}}
|
|
|
905
815
|
{ name: "mr_iid", description: "Merge request IID" },
|
|
906
816
|
{ name: "mr_title", description: "Merge request title" },
|
|
907
817
|
{ name: "repo", description: "Project path with namespace" },
|
|
908
|
-
{
|
|
818
|
+
{
|
|
819
|
+
name: "username",
|
|
820
|
+
description: "Author identity — resolved canonical name (e.g. 'Luis (gitlab:username)') or the UNKNOWN sentinel"
|
|
821
|
+
},
|
|
909
822
|
{ name: "source_branch", description: "Source branch name" },
|
|
910
823
|
{ name: "target_branch", description: "Target branch name" },
|
|
911
824
|
{ name: "mr_url", description: "Merge request URL" },
|
|
@@ -917,7 +830,7 @@ registerTemplate({
|
|
|
917
830
|
eventType: "gitlab.issue.assigned",
|
|
918
831
|
header: "[GitLab Issue #{{issue_iid}}] {{issue_title}}",
|
|
919
832
|
defaultBody: `Repo: {{repo}}
|
|
920
|
-
Author:
|
|
833
|
+
Author: {{username}}
|
|
921
834
|
URL: {{issue_url}}
|
|
922
835
|
|
|
923
836
|
{{context_section}}{{@template[common.command_suggestions.gitlab_issue]}}{{@template[common.delegation_instruction.gitlab]}}`,
|
|
@@ -925,7 +838,10 @@ URL: {{issue_url}}
|
|
|
925
838
|
{ name: "issue_iid", description: "Issue IID" },
|
|
926
839
|
{ name: "issue_title", description: "Issue title" },
|
|
927
840
|
{ name: "repo", description: "Project path with namespace" },
|
|
928
|
-
{
|
|
841
|
+
{
|
|
842
|
+
name: "username",
|
|
843
|
+
description: "Author identity — resolved canonical name (e.g. 'Luis (gitlab:username)') or the UNKNOWN sentinel"
|
|
844
|
+
},
|
|
929
845
|
{ name: "issue_url", description: "Issue URL" },
|
|
930
846
|
{ name: "context_section", description: "Context section with description or empty string" }
|
|
931
847
|
],
|
|
@@ -933,7 +849,7 @@ URL: {{issue_url}}
|
|
|
933
849
|
});
|
|
934
850
|
registerTemplate({
|
|
935
851
|
eventType: "gitlab.comment.mentioned",
|
|
936
|
-
header: "[GitLab Comment on {{entity_label}}]
|
|
852
|
+
header: "[GitLab Comment on {{entity_label}}] {{username}} mentioned bot",
|
|
937
853
|
defaultBody: `Repo: {{repo}}
|
|
938
854
|
URL: {{target_url}}
|
|
939
855
|
|
|
@@ -941,7 +857,10 @@ Comment:
|
|
|
941
857
|
{{context}}{{existing_task_note}}{{@template[common.delegation_instruction.gitlab]}}`,
|
|
942
858
|
variables: [
|
|
943
859
|
{ name: "entity_label", description: "Entity label (e.g. 'MR #1' or 'Issue #2')" },
|
|
944
|
-
{
|
|
860
|
+
{
|
|
861
|
+
name: "username",
|
|
862
|
+
description: "Comment author identity — resolved canonical name (e.g. 'Luis (gitlab:username)') or the UNKNOWN sentinel"
|
|
863
|
+
},
|
|
945
864
|
{ name: "repo", description: "Project path with namespace" },
|
|
946
865
|
{ name: "target_url", description: "Target entity URL" },
|
|
947
866
|
{ name: "context", description: "Extracted mention context from comment" },
|
|
@@ -1025,6 +944,9 @@ function resolveGitLabSender(user, sampleEventType, sampleContext) {
|
|
|
1025
944
|
incrKv(UNMAPPED_NAMESPACE, `${user.username}:count`, 1);
|
|
1026
945
|
return;
|
|
1027
946
|
}
|
|
947
|
+
function renderGitLabIdentity(username) {
|
|
948
|
+
return renderIdentity(resolveIdentity("gitlab", username));
|
|
949
|
+
}
|
|
1028
950
|
async function handleMergeRequest(event) {
|
|
1029
951
|
const { user, project, object_attributes: mr } = event;
|
|
1030
952
|
const action = mr.action;
|
|
@@ -1052,7 +974,7 @@ async function handleMergeRequest(event) {
|
|
|
1052
974
|
mr_iid: mr.iid,
|
|
1053
975
|
mr_title: mr.title,
|
|
1054
976
|
repo,
|
|
1055
|
-
username: user.username,
|
|
977
|
+
username: renderGitLabIdentity(user.username),
|
|
1056
978
|
source_branch: mr.source_branch,
|
|
1057
979
|
target_branch: mr.target_branch,
|
|
1058
980
|
mr_url: mr.url,
|
|
@@ -1135,7 +1057,7 @@ async function handleIssue2(event) {
|
|
|
1135
1057
|
issue_iid: issue.iid,
|
|
1136
1058
|
issue_title: issue.title,
|
|
1137
1059
|
repo,
|
|
1138
|
-
username: user.username,
|
|
1060
|
+
username: renderGitLabIdentity(user.username),
|
|
1139
1061
|
issue_url: issue.url,
|
|
1140
1062
|
context_section: contextSection
|
|
1141
1063
|
});
|
|
@@ -1179,7 +1101,7 @@ async function handleIssue2(event) {
|
|
|
1179
1101
|
async function handleNote(event) {
|
|
1180
1102
|
const { user, project, object_attributes: note } = event;
|
|
1181
1103
|
const repo = project.path_with_namespace;
|
|
1182
|
-
const
|
|
1104
|
+
const requestedByUserId = resolveGitLabSender(user, "note", note.note);
|
|
1183
1105
|
if (!detectMention(note.note)) {
|
|
1184
1106
|
return { created: false };
|
|
1185
1107
|
}
|
|
@@ -1214,7 +1136,7 @@ async function handleNote(event) {
|
|
|
1214
1136
|
_Note: There's an active task (${existingTask.id}) for this ${entityLabel}._` : "";
|
|
1215
1137
|
const noteResult = resolveTemplate("gitlab.comment.mentioned", {
|
|
1216
1138
|
entity_label: entityLabel,
|
|
1217
|
-
username: user.username,
|
|
1139
|
+
username: renderGitLabIdentity(user.username),
|
|
1218
1140
|
repo,
|
|
1219
1141
|
target_url: targetUrl,
|
|
1220
1142
|
context,
|
|
@@ -1233,6 +1155,7 @@ _Note: There's an active task (${existingTask.id}) for this ${entityLabel}._` :
|
|
|
1233
1155
|
vcsNumber: targetNumber,
|
|
1234
1156
|
vcsCommentId: note.id,
|
|
1235
1157
|
vcsAuthor: user.username,
|
|
1158
|
+
requestedByUserId,
|
|
1236
1159
|
vcsUrl: targetUrl,
|
|
1237
1160
|
parentTaskId: existingTask?.id,
|
|
1238
1161
|
contextKey: targetNumber ? gitlabContextKey({
|
|
@@ -1247,8 +1170,9 @@ _Note: There's an active task (${existingTask.id}) for this ${entityLabel}._` :
|
|
|
1247
1170
|
return { created: true, taskId: task.id };
|
|
1248
1171
|
}
|
|
1249
1172
|
async function handlePipeline(event) {
|
|
1250
|
-
const { project, object_attributes: pipeline } = event;
|
|
1173
|
+
const { user, project, object_attributes: pipeline } = event;
|
|
1251
1174
|
const repo = project.path_with_namespace;
|
|
1175
|
+
const requestedByUserId = resolveGitLabSender(user, "pipeline", `Pipeline #${pipeline.id}`);
|
|
1252
1176
|
if (pipeline.status !== "failed") {
|
|
1253
1177
|
return { created: false };
|
|
1254
1178
|
}
|
|
@@ -1286,6 +1210,7 @@ async function handlePipeline(event) {
|
|
|
1286
1210
|
vcsEventType: "pipeline",
|
|
1287
1211
|
vcsNumber: mrIid,
|
|
1288
1212
|
vcsAuthor: "",
|
|
1213
|
+
requestedByUserId,
|
|
1289
1214
|
vcsUrl: event.merge_request.url,
|
|
1290
1215
|
parentTaskId: existingTask.id,
|
|
1291
1216
|
contextKey: gitlabContextKey({
|
|
@@ -2539,6 +2464,7 @@ async function handleApiKeys(req, res, pathSegments, queryParams) {
|
|
|
2539
2464
|
init_zod();
|
|
2540
2465
|
init_db();
|
|
2541
2466
|
init_resolver();
|
|
2467
|
+
init_request_auth_context();
|
|
2542
2468
|
init_event_bus();
|
|
2543
2469
|
var QuestionSchema = exports_external.object({
|
|
2544
2470
|
id: exports_external.string(),
|
|
@@ -2723,6 +2649,10 @@ async function handleApprovalRequests(req, res, pathSegments, queryParams) {
|
|
|
2723
2649
|
if (!parsed)
|
|
2724
2650
|
return true;
|
|
2725
2651
|
const id = crypto.randomUUID();
|
|
2652
|
+
const auth = getRequestAuth(req);
|
|
2653
|
+
const rawCallerAgentId = req.headers["x-agent-id"];
|
|
2654
|
+
const callerAgentId = Array.isArray(rawCallerAgentId) ? rawCallerAgentId[0] : rawCallerAgentId;
|
|
2655
|
+
const createdBy = auth?.kind === "user" ? auth.userId : resolveTaskAuditUserId(parsed.body.sourceTaskId, callerAgentId) ?? undefined;
|
|
2726
2656
|
const request = createApprovalRequest({
|
|
2727
2657
|
id,
|
|
2728
2658
|
title: parsed.body.title,
|
|
@@ -2732,7 +2662,8 @@ async function handleApprovalRequests(req, res, pathSegments, queryParams) {
|
|
|
2732
2662
|
workflowRunStepId: parsed.body.workflowRunStepId,
|
|
2733
2663
|
sourceTaskId: parsed.body.sourceTaskId,
|
|
2734
2664
|
timeoutSeconds: parsed.body.timeoutSeconds,
|
|
2735
|
-
notificationChannels: parsed.body.notifications
|
|
2665
|
+
notificationChannels: parsed.body.notifications,
|
|
2666
|
+
createdBy
|
|
2736
2667
|
});
|
|
2737
2668
|
res.writeHead(201, { "Content-Type": "application/json" });
|
|
2738
2669
|
res.end(JSON.stringify({ approvalRequest: request }));
|
|
@@ -2991,6 +2922,7 @@ init_swarm_config_guard();
|
|
|
2991
2922
|
init_request_auth_context();
|
|
2992
2923
|
init_secret_scrubber();
|
|
2993
2924
|
var MAX_ENV_PRESENCE_KEYS = 200;
|
|
2925
|
+
var SECRETS_FORCE_MASK_NOTE = " (secret values masked: reading unmasked secrets requires the lead agent)";
|
|
2994
2926
|
var API_ONLY_CONFIG_KEYS = new Set(["API_AGENT_FS_API_KEY"]);
|
|
2995
2927
|
function stripApiOnlyKeys(configs) {
|
|
2996
2928
|
return configs.filter((config) => !API_ONLY_CONFIG_KEYS.has(config.key));
|
|
@@ -2999,6 +2931,23 @@ function singleHeader(req, name) {
|
|
|
2999
2931
|
const raw = req.headers[name];
|
|
3000
2932
|
return Array.isArray(raw) ? raw[0] : raw;
|
|
3001
2933
|
}
|
|
2934
|
+
function userMayReadSecrets(req) {
|
|
2935
|
+
const auth = getRequestAuth(req);
|
|
2936
|
+
if (auth?.kind === "operator")
|
|
2937
|
+
return true;
|
|
2938
|
+
if (auth?.kind !== "user")
|
|
2939
|
+
return true;
|
|
2940
|
+
if (!isRbacEnabled())
|
|
2941
|
+
return true;
|
|
2942
|
+
const grant = getUserGrant(auth.userId);
|
|
2943
|
+
return grant.grantsAll || grant.verbs.has("config.read.secrets");
|
|
2944
|
+
}
|
|
2945
|
+
function resolveSecretsRead(req, includeSecrets) {
|
|
2946
|
+
if (!includeSecrets || userMayReadSecrets(req)) {
|
|
2947
|
+
return { effectiveIncludeSecrets: includeSecrets, secretsNote: "" };
|
|
2948
|
+
}
|
|
2949
|
+
return { effectiveIncludeSecrets: false, secretsNote: SECRETS_FORCE_MASK_NOTE };
|
|
2950
|
+
}
|
|
3002
2951
|
function ensureConfigAdmin(req, res, verb) {
|
|
3003
2952
|
const auth = getRequestAuth(req);
|
|
3004
2953
|
if (auth?.kind === "operator" || auth?.kind === "user")
|
|
@@ -3128,16 +3077,20 @@ async function handleConfig(req, res, pathSegments, queryParams) {
|
|
|
3128
3077
|
if (!parsed)
|
|
3129
3078
|
return true;
|
|
3130
3079
|
const includeSecrets = parsed.query.includeSecrets === "true";
|
|
3080
|
+
const { effectiveIncludeSecrets, secretsNote } = resolveSecretsRead(req, includeSecrets);
|
|
3131
3081
|
const configs = stripApiOnlyKeys(getResolvedConfig(parsed.query.agentId || undefined, parsed.query.repoId || undefined));
|
|
3132
|
-
const result =
|
|
3133
|
-
if (
|
|
3082
|
+
const result = effectiveIncludeSecrets ? configs : maskSecrets(configs);
|
|
3083
|
+
if (effectiveIncludeSecrets) {
|
|
3134
3084
|
for (const c of result) {
|
|
3135
3085
|
if (c.isSecret && c.value) {
|
|
3136
3086
|
registerVolatileSecret(c.value, `config:${c.key}`);
|
|
3137
3087
|
}
|
|
3138
3088
|
}
|
|
3139
3089
|
}
|
|
3140
|
-
json(res, {
|
|
3090
|
+
json(res, {
|
|
3091
|
+
configs: result,
|
|
3092
|
+
...secretsNote ? { message: `Found ${result.length} config(s).${secretsNote}` } : {}
|
|
3093
|
+
});
|
|
3141
3094
|
return true;
|
|
3142
3095
|
}
|
|
3143
3096
|
if (envPresence.match(req.method, pathSegments)) {
|
|
@@ -3178,11 +3131,15 @@ async function handleConfig(req, res, pathSegments, queryParams) {
|
|
|
3178
3131
|
jsonError(res, "Config not found", 404);
|
|
3179
3132
|
return true;
|
|
3180
3133
|
}
|
|
3181
|
-
const
|
|
3182
|
-
|
|
3134
|
+
const { effectiveIncludeSecrets, secretsNote } = resolveSecretsRead(req, includeSecrets);
|
|
3135
|
+
const singleResult = effectiveIncludeSecrets ? config : maskSecrets([config])[0];
|
|
3136
|
+
if (effectiveIncludeSecrets && singleResult.isSecret && singleResult.value) {
|
|
3183
3137
|
registerVolatileSecret(singleResult.value, `config:${singleResult.key}`);
|
|
3184
3138
|
}
|
|
3185
|
-
json(res,
|
|
3139
|
+
json(res, {
|
|
3140
|
+
...singleResult,
|
|
3141
|
+
...secretsNote ? { message: `Found 1 config(s).${secretsNote}` } : {}
|
|
3142
|
+
});
|
|
3186
3143
|
return true;
|
|
3187
3144
|
}
|
|
3188
3145
|
if (listConfig.match(req.method, pathSegments)) {
|
|
@@ -3190,19 +3147,23 @@ async function handleConfig(req, res, pathSegments, queryParams) {
|
|
|
3190
3147
|
if (!parsed)
|
|
3191
3148
|
return true;
|
|
3192
3149
|
const includeSecrets = parsed.query.includeSecrets === "true";
|
|
3150
|
+
const { effectiveIncludeSecrets, secretsNote } = resolveSecretsRead(req, includeSecrets);
|
|
3193
3151
|
const configs = stripApiOnlyKeys(getSwarmConfigs({
|
|
3194
3152
|
scope: parsed.query.scope || undefined,
|
|
3195
3153
|
scopeId: parsed.query.scopeId || undefined
|
|
3196
3154
|
}));
|
|
3197
|
-
const listResult =
|
|
3198
|
-
if (
|
|
3155
|
+
const listResult = effectiveIncludeSecrets ? configs : maskSecrets(configs);
|
|
3156
|
+
if (effectiveIncludeSecrets) {
|
|
3199
3157
|
for (const c of listResult) {
|
|
3200
3158
|
if (c.isSecret && c.value) {
|
|
3201
3159
|
registerVolatileSecret(c.value, `config:${c.key}`);
|
|
3202
3160
|
}
|
|
3203
3161
|
}
|
|
3204
3162
|
}
|
|
3205
|
-
json(res, {
|
|
3163
|
+
json(res, {
|
|
3164
|
+
configs: listResult,
|
|
3165
|
+
...secretsNote ? { message: `Found ${listResult.length} config(s).${secretsNote}` } : {}
|
|
3166
|
+
});
|
|
3206
3167
|
return true;
|
|
3207
3168
|
}
|
|
3208
3169
|
if (upsertConfig.match(req.method, pathSegments)) {
|
|
@@ -3592,7 +3553,7 @@ var putFavorite = route({
|
|
|
3592
3553
|
pattern: ["api", "favorites"],
|
|
3593
3554
|
summary: "Set favorite state for an item",
|
|
3594
3555
|
tags: ["Favorites"],
|
|
3595
|
-
rbac: {
|
|
3556
|
+
rbac: { permission: "favorite.write.own" },
|
|
3596
3557
|
body: exports_external.object({
|
|
3597
3558
|
itemType: FavoriteItemTypeSchema,
|
|
3598
3559
|
itemId: exports_external.string().min(1),
|
|
@@ -4472,11 +4433,11 @@ async function readRawBody(req, maxBytes) {
|
|
|
4472
4433
|
const chunks = [];
|
|
4473
4434
|
let total = 0;
|
|
4474
4435
|
for await (const chunk of req) {
|
|
4475
|
-
const
|
|
4476
|
-
total +=
|
|
4436
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4437
|
+
total += buffer.byteLength;
|
|
4477
4438
|
if (total > maxBytes)
|
|
4478
4439
|
return BODY_TOO_LARGE;
|
|
4479
|
-
chunks.push(
|
|
4440
|
+
chunks.push(buffer);
|
|
4480
4441
|
}
|
|
4481
4442
|
return Buffer.concat(chunks);
|
|
4482
4443
|
}
|
|
@@ -6559,6 +6520,7 @@ var SDK_TOOL_NAME_MAP = {
|
|
|
6559
6520
|
mcpServer_install: "mcp-server-install",
|
|
6560
6521
|
mcpServer_uninstall: "mcp-server-uninstall",
|
|
6561
6522
|
page_create: "create_page",
|
|
6523
|
+
page_delete: "delete-page",
|
|
6562
6524
|
metric_create: "create_metric",
|
|
6563
6525
|
request_humanInput: "request-human-input"
|
|
6564
6526
|
};
|
|
@@ -6651,466 +6613,6 @@ async function handleMcpBridge(req, res, pathSegments, _queryParams, myAgentId)
|
|
|
6651
6613
|
// src/http/mcp-oauth.ts
|
|
6652
6614
|
init_zod();
|
|
6653
6615
|
init_db();
|
|
6654
|
-
|
|
6655
|
-
// src/be/db-queries/mcp-oauth.ts
|
|
6656
|
-
init_crypto();
|
|
6657
|
-
init_date_utils();
|
|
6658
|
-
init_db();
|
|
6659
|
-
function encryptOrNull(plaintext) {
|
|
6660
|
-
if (plaintext == null || plaintext === "")
|
|
6661
|
-
return null;
|
|
6662
|
-
return encryptSecret(plaintext, getEncryptionKey());
|
|
6663
|
-
}
|
|
6664
|
-
function decryptOrNull(ciphertext) {
|
|
6665
|
-
if (ciphertext == null || ciphertext === "")
|
|
6666
|
-
return null;
|
|
6667
|
-
return decryptSecret(ciphertext, getEncryptionKey());
|
|
6668
|
-
}
|
|
6669
|
-
function decryptTokenRow(row) {
|
|
6670
|
-
const key = getEncryptionKey();
|
|
6671
|
-
return {
|
|
6672
|
-
...row,
|
|
6673
|
-
accessToken: decryptSecret(row.accessToken, key),
|
|
6674
|
-
refreshToken: decryptOrNull(row.refreshToken),
|
|
6675
|
-
dcrClientSecret: decryptOrNull(row.dcrClientSecret),
|
|
6676
|
-
createdAt: normalizeDateRequired(row.createdAt),
|
|
6677
|
-
updatedAt: normalizeDateRequired(row.updatedAt)
|
|
6678
|
-
};
|
|
6679
|
-
}
|
|
6680
|
-
function getMcpOAuthToken(mcpServerId, userId = null) {
|
|
6681
|
-
const row = getDb().query(userId == null ? "SELECT * FROM mcp_oauth_tokens WHERE mcpServerId = ? AND userId IS NULL" : "SELECT * FROM mcp_oauth_tokens WHERE mcpServerId = ? AND userId = ?").get(...userId == null ? [mcpServerId] : [mcpServerId, userId]);
|
|
6682
|
-
return row ? decryptTokenRow(row) : null;
|
|
6683
|
-
}
|
|
6684
|
-
function upsertMcpOAuthToken(input) {
|
|
6685
|
-
const userId = input.userId ?? null;
|
|
6686
|
-
const encryptedAccess = encryptSecret(input.accessToken, getEncryptionKey());
|
|
6687
|
-
const encryptedRefresh = encryptOrNull(input.refreshToken);
|
|
6688
|
-
const encryptedClientSecret = encryptOrNull(input.dcrClientSecret);
|
|
6689
|
-
const existing = getMcpOAuthToken(input.mcpServerId, userId);
|
|
6690
|
-
if (!existing) {
|
|
6691
|
-
getDb().query(`INSERT INTO mcp_oauth_tokens (
|
|
6692
|
-
mcpServerId, userId,
|
|
6693
|
-
accessToken, refreshToken, tokenType, expiresAt, scope,
|
|
6694
|
-
resourceUrl, authorizationServerIssuer, authorizeUrl, tokenUrl, revocationUrl,
|
|
6695
|
-
dcrClientId, dcrClientSecret, clientSource,
|
|
6696
|
-
status, lastErrorMessage, lastRefreshedAt, connectedByUserId
|
|
6697
|
-
)
|
|
6698
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.mcpServerId, userId, encryptedAccess, encryptedRefresh, input.tokenType ?? "Bearer", input.expiresAt ?? null, input.scope ?? null, input.resourceUrl, input.authorizationServerIssuer, input.authorizeUrl, input.tokenUrl, input.revocationUrl ?? null, input.dcrClientId ?? null, encryptedClientSecret, input.clientSource, input.status ?? "connected", input.lastErrorMessage ?? null, input.lastRefreshedAt ?? null, input.connectedByUserId ?? null);
|
|
6699
|
-
return;
|
|
6700
|
-
}
|
|
6701
|
-
getDb().query(`UPDATE mcp_oauth_tokens SET
|
|
6702
|
-
accessToken = ?,
|
|
6703
|
-
refreshToken = COALESCE(?, refreshToken),
|
|
6704
|
-
tokenType = ?,
|
|
6705
|
-
expiresAt = ?,
|
|
6706
|
-
scope = COALESCE(?, scope),
|
|
6707
|
-
resourceUrl = ?,
|
|
6708
|
-
authorizationServerIssuer = ?,
|
|
6709
|
-
authorizeUrl = ?,
|
|
6710
|
-
tokenUrl = ?,
|
|
6711
|
-
revocationUrl = ?,
|
|
6712
|
-
dcrClientId = COALESCE(?, dcrClientId),
|
|
6713
|
-
dcrClientSecret = COALESCE(?, dcrClientSecret),
|
|
6714
|
-
clientSource = ?,
|
|
6715
|
-
status = ?,
|
|
6716
|
-
lastErrorMessage = ?,
|
|
6717
|
-
lastRefreshedAt = ?,
|
|
6718
|
-
connectedByUserId = COALESCE(?, connectedByUserId),
|
|
6719
|
-
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
6720
|
-
WHERE id = ?`).run(encryptedAccess, encryptedRefresh, input.tokenType ?? "Bearer", input.expiresAt ?? null, input.scope ?? null, input.resourceUrl, input.authorizationServerIssuer, input.authorizeUrl, input.tokenUrl, input.revocationUrl ?? null, input.dcrClientId ?? null, encryptedClientSecret, input.clientSource, input.status ?? "connected", input.lastErrorMessage ?? null, input.lastRefreshedAt ?? null, input.connectedByUserId ?? null, existing.id);
|
|
6721
|
-
}
|
|
6722
|
-
function applyMcpOAuthRefresh(id, data) {
|
|
6723
|
-
const key = getEncryptionKey();
|
|
6724
|
-
const encryptedAccess = encryptSecret(data.accessToken, key);
|
|
6725
|
-
const encryptedRefresh = data.refreshToken === undefined ? undefined : data.refreshToken == null ? null : encryptSecret(data.refreshToken, key);
|
|
6726
|
-
if (encryptedRefresh === undefined) {
|
|
6727
|
-
getDb().query(`UPDATE mcp_oauth_tokens
|
|
6728
|
-
SET accessToken = ?,
|
|
6729
|
-
expiresAt = COALESCE(?, expiresAt),
|
|
6730
|
-
scope = COALESCE(?, scope),
|
|
6731
|
-
status = 'connected',
|
|
6732
|
-
lastErrorMessage = NULL,
|
|
6733
|
-
lastRefreshedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
6734
|
-
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
6735
|
-
WHERE id = ?`).run(encryptedAccess, data.expiresAt ?? null, data.scope ?? null, id);
|
|
6736
|
-
} else {
|
|
6737
|
-
getDb().query(`UPDATE mcp_oauth_tokens
|
|
6738
|
-
SET accessToken = ?,
|
|
6739
|
-
refreshToken = ?,
|
|
6740
|
-
expiresAt = COALESCE(?, expiresAt),
|
|
6741
|
-
scope = COALESCE(?, scope),
|
|
6742
|
-
status = 'connected',
|
|
6743
|
-
lastErrorMessage = NULL,
|
|
6744
|
-
lastRefreshedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
6745
|
-
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
6746
|
-
WHERE id = ?`).run(encryptedAccess, encryptedRefresh, data.expiresAt ?? null, data.scope ?? null, id);
|
|
6747
|
-
}
|
|
6748
|
-
}
|
|
6749
|
-
function markMcpOAuthTokenStatus(id, status, errorMessage) {
|
|
6750
|
-
getDb().query(`UPDATE mcp_oauth_tokens
|
|
6751
|
-
SET status = ?,
|
|
6752
|
-
lastErrorMessage = ?,
|
|
6753
|
-
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
6754
|
-
WHERE id = ?`).run(status, errorMessage ?? null, id);
|
|
6755
|
-
}
|
|
6756
|
-
function deleteMcpOAuthToken(mcpServerId, userId = null) {
|
|
6757
|
-
const result = getDb().query(userId == null ? "DELETE FROM mcp_oauth_tokens WHERE mcpServerId = ? AND userId IS NULL" : "DELETE FROM mcp_oauth_tokens WHERE mcpServerId = ? AND userId = ?").run(...userId == null ? [mcpServerId] : [mcpServerId, userId]);
|
|
6758
|
-
return result.changes > 0;
|
|
6759
|
-
}
|
|
6760
|
-
function isMcpTokenExpiringSoon(token, bufferMs = 5 * 60 * 1000) {
|
|
6761
|
-
if (!token.expiresAt)
|
|
6762
|
-
return false;
|
|
6763
|
-
const expiresAt = new Date(token.expiresAt).getTime();
|
|
6764
|
-
if (Number.isNaN(expiresAt))
|
|
6765
|
-
return true;
|
|
6766
|
-
return expiresAt - Date.now() < bufferMs;
|
|
6767
|
-
}
|
|
6768
|
-
function insertMcpOAuthPending(input) {
|
|
6769
|
-
const key = getEncryptionKey();
|
|
6770
|
-
getDb().query(`INSERT INTO mcp_oauth_pending (
|
|
6771
|
-
state, mcpServerId, userId,
|
|
6772
|
-
codeVerifier, nonce,
|
|
6773
|
-
resourceUrl, authorizationServerIssuer, authorizeUrl, tokenUrl, revocationUrl,
|
|
6774
|
-
scopes, dcrClientId, dcrClientSecret, redirectUri, finalRedirect
|
|
6775
|
-
)
|
|
6776
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.state, input.mcpServerId, input.userId ?? null, encryptSecret(input.codeVerifier, key), input.nonce ?? null, input.resourceUrl, input.authorizationServerIssuer, input.authorizeUrl, input.tokenUrl, input.revocationUrl ?? null, input.scopes ?? null, input.dcrClientId ?? null, encryptOrNull(input.dcrClientSecret), input.redirectUri, input.finalRedirect ?? null);
|
|
6777
|
-
}
|
|
6778
|
-
function consumeMcpOAuthPending(state) {
|
|
6779
|
-
const row = getDb().query("SELECT * FROM mcp_oauth_pending WHERE state = ?").get(state);
|
|
6780
|
-
if (!row)
|
|
6781
|
-
return null;
|
|
6782
|
-
getDb().query("DELETE FROM mcp_oauth_pending WHERE state = ?").run(state);
|
|
6783
|
-
const key = getEncryptionKey();
|
|
6784
|
-
return {
|
|
6785
|
-
...row,
|
|
6786
|
-
codeVerifier: decryptSecret(row.codeVerifier, key),
|
|
6787
|
-
dcrClientSecret: decryptOrNull(row.dcrClientSecret)
|
|
6788
|
-
};
|
|
6789
|
-
}
|
|
6790
|
-
function gcMcpOAuthPending(olderThanMs = 10 * 60 * 1000) {
|
|
6791
|
-
const cutoff = new Date(Date.now() - olderThanMs).toISOString();
|
|
6792
|
-
const result = getDb().query("DELETE FROM mcp_oauth_pending WHERE createdAt < ?").run(cutoff);
|
|
6793
|
-
return result.changes;
|
|
6794
|
-
}
|
|
6795
|
-
function setMcpServerAuthMethod(mcpServerId, authMethod) {
|
|
6796
|
-
getDb().query("UPDATE mcp_servers SET authMethod = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?").run(authMethod, mcpServerId);
|
|
6797
|
-
}
|
|
6798
|
-
|
|
6799
|
-
// src/oauth/mcp-wrapper.ts
|
|
6800
|
-
var PRIVATE_IPV4_BLOCKS = [
|
|
6801
|
-
{ prefix: "10.", mask: 8 },
|
|
6802
|
-
{ prefix: "127.", mask: 8 },
|
|
6803
|
-
{ prefix: "169.254.", mask: 16 },
|
|
6804
|
-
{ prefix: "172.16.", mask: 12 },
|
|
6805
|
-
{ prefix: "192.168.", mask: 16 },
|
|
6806
|
-
{ prefix: "0.", mask: 8 }
|
|
6807
|
-
];
|
|
6808
|
-
function isPrivateIPv4(host) {
|
|
6809
|
-
if (host === "127.0.0.1")
|
|
6810
|
-
return true;
|
|
6811
|
-
if (host.startsWith("169.254."))
|
|
6812
|
-
return true;
|
|
6813
|
-
if (host.startsWith("10."))
|
|
6814
|
-
return true;
|
|
6815
|
-
if (host.startsWith("192.168."))
|
|
6816
|
-
return true;
|
|
6817
|
-
if (host.startsWith("0."))
|
|
6818
|
-
return true;
|
|
6819
|
-
if (host.startsWith("172.")) {
|
|
6820
|
-
const parts = host.split(".");
|
|
6821
|
-
const second = parseInt(parts[1] ?? "", 10);
|
|
6822
|
-
if (Number.isFinite(second) && second >= 16 && second <= 31)
|
|
6823
|
-
return true;
|
|
6824
|
-
}
|
|
6825
|
-
for (const block of PRIVATE_IPV4_BLOCKS) {
|
|
6826
|
-
if (block.prefix !== "172." && host.startsWith(block.prefix))
|
|
6827
|
-
return true;
|
|
6828
|
-
}
|
|
6829
|
-
return false;
|
|
6830
|
-
}
|
|
6831
|
-
function isPrivateIPv6(host) {
|
|
6832
|
-
const lower = host.toLowerCase();
|
|
6833
|
-
if (lower === "::1" || lower === "[::1]")
|
|
6834
|
-
return true;
|
|
6835
|
-
if (lower.startsWith("fe80:") || lower.startsWith("[fe80:"))
|
|
6836
|
-
return true;
|
|
6837
|
-
if (lower.startsWith("fc") || lower.startsWith("fd"))
|
|
6838
|
-
return true;
|
|
6839
|
-
if (lower.startsWith("[fc") || lower.startsWith("[fd"))
|
|
6840
|
-
return true;
|
|
6841
|
-
return false;
|
|
6842
|
-
}
|
|
6843
|
-
function assertUrlSafe(rawUrl, opts = {}) {
|
|
6844
|
-
let parsed;
|
|
6845
|
-
try {
|
|
6846
|
-
parsed = new URL(rawUrl);
|
|
6847
|
-
} catch {
|
|
6848
|
-
throw new Error(`Invalid URL: ${rawUrl}`);
|
|
6849
|
-
}
|
|
6850
|
-
const allowPrivate = opts.allowPrivateHosts === true;
|
|
6851
|
-
const allowInsecure = opts.allowInsecure === true;
|
|
6852
|
-
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
6853
|
-
throw new Error(`Refusing unsupported protocol: ${parsed.protocol}`);
|
|
6854
|
-
}
|
|
6855
|
-
if (parsed.protocol === "http:" && !allowInsecure) {
|
|
6856
|
-
throw new Error(`Refusing insecure (http://) URL in production: ${rawUrl}`);
|
|
6857
|
-
}
|
|
6858
|
-
const host = parsed.hostname;
|
|
6859
|
-
if (!host) {
|
|
6860
|
-
throw new Error(`Missing hostname: ${rawUrl}`);
|
|
6861
|
-
}
|
|
6862
|
-
if (host === "localhost" && !allowPrivate) {
|
|
6863
|
-
throw new Error(`Refusing loopback hostname: ${host}`);
|
|
6864
|
-
}
|
|
6865
|
-
if (isPrivateIPv4(host) && !allowPrivate) {
|
|
6866
|
-
throw new Error(`Refusing private IPv4 host: ${host}`);
|
|
6867
|
-
}
|
|
6868
|
-
if (isPrivateIPv6(host) && !allowPrivate) {
|
|
6869
|
-
throw new Error(`Refusing private IPv6 host: ${host}`);
|
|
6870
|
-
}
|
|
6871
|
-
return parsed;
|
|
6872
|
-
}
|
|
6873
|
-
function defaultSsrfOptions() {
|
|
6874
|
-
return {
|
|
6875
|
-
allowPrivateHosts: process.env.MCP_OAUTH_ALLOW_PRIVATE_HOSTS === "true",
|
|
6876
|
-
allowInsecure: true
|
|
6877
|
-
};
|
|
6878
|
-
}
|
|
6879
|
-
async function safeFetch(url, init) {
|
|
6880
|
-
assertUrlSafe(url, defaultSsrfOptions());
|
|
6881
|
-
return fetch(url, init);
|
|
6882
|
-
}
|
|
6883
|
-
async function discoverProtectedResourceMetadata(resourceUrl) {
|
|
6884
|
-
const base = new URL(resourceUrl);
|
|
6885
|
-
const wellKnown = new URL("/.well-known/oauth-protected-resource", base).toString();
|
|
6886
|
-
try {
|
|
6887
|
-
const res = await safeFetch(wellKnown, {
|
|
6888
|
-
headers: { Accept: "application/json" }
|
|
6889
|
-
});
|
|
6890
|
-
if (res.ok) {
|
|
6891
|
-
return await res.json();
|
|
6892
|
-
}
|
|
6893
|
-
} catch {}
|
|
6894
|
-
try {
|
|
6895
|
-
const probe = await safeFetch(resourceUrl, { method: "HEAD" });
|
|
6896
|
-
const wwwAuth = probe.headers.get("www-authenticate");
|
|
6897
|
-
if (wwwAuth) {
|
|
6898
|
-
const match = /resource_metadata="([^"]+)"/i.exec(wwwAuth);
|
|
6899
|
-
if (match) {
|
|
6900
|
-
const metaRes = await safeFetch(match[1], {
|
|
6901
|
-
headers: { Accept: "application/json" }
|
|
6902
|
-
});
|
|
6903
|
-
if (metaRes.ok) {
|
|
6904
|
-
return await metaRes.json();
|
|
6905
|
-
}
|
|
6906
|
-
}
|
|
6907
|
-
}
|
|
6908
|
-
} catch {}
|
|
6909
|
-
return null;
|
|
6910
|
-
}
|
|
6911
|
-
async function discoverAuthorizationServerMetadata(issuer) {
|
|
6912
|
-
const issuerUrl = new URL(issuer);
|
|
6913
|
-
const candidates = [
|
|
6914
|
-
new URL("/.well-known/oauth-authorization-server", issuerUrl).toString(),
|
|
6915
|
-
new URL("/.well-known/openid-configuration", issuerUrl).toString()
|
|
6916
|
-
];
|
|
6917
|
-
let lastError = null;
|
|
6918
|
-
for (const candidate of candidates) {
|
|
6919
|
-
try {
|
|
6920
|
-
const res = await safeFetch(candidate, {
|
|
6921
|
-
headers: { Accept: "application/json" }
|
|
6922
|
-
});
|
|
6923
|
-
if (res.ok) {
|
|
6924
|
-
return await res.json();
|
|
6925
|
-
}
|
|
6926
|
-
lastError = new Error(`Metadata fetch ${candidate} → ${res.status}`);
|
|
6927
|
-
} catch (err) {
|
|
6928
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
6929
|
-
}
|
|
6930
|
-
}
|
|
6931
|
-
throw lastError ?? new Error(`Authorization server metadata not found at ${issuer}`);
|
|
6932
|
-
}
|
|
6933
|
-
async function registerClient(registrationEndpoint, req) {
|
|
6934
|
-
const res = await safeFetch(registrationEndpoint, {
|
|
6935
|
-
method: "POST",
|
|
6936
|
-
headers: {
|
|
6937
|
-
"Content-Type": "application/json",
|
|
6938
|
-
Accept: "application/json"
|
|
6939
|
-
},
|
|
6940
|
-
body: JSON.stringify(req)
|
|
6941
|
-
});
|
|
6942
|
-
if (!res.ok) {
|
|
6943
|
-
const body = await res.text().catch(() => "");
|
|
6944
|
-
throw new Error(`Dynamic client registration failed (${res.status}): ${body}`);
|
|
6945
|
-
}
|
|
6946
|
-
return await res.json();
|
|
6947
|
-
}
|
|
6948
|
-
async function buildAuthorizeUrl(input) {
|
|
6949
|
-
const state = input.state ?? generateRandomState();
|
|
6950
|
-
const codeVerifier = generateRandomCodeVerifier();
|
|
6951
|
-
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
|
|
6952
|
-
const url = new URL(input.authorizeUrl);
|
|
6953
|
-
url.searchParams.set("response_type", "code");
|
|
6954
|
-
url.searchParams.set("client_id", input.clientId);
|
|
6955
|
-
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
6956
|
-
if (input.scopes.length > 0) {
|
|
6957
|
-
url.searchParams.set("scope", input.scopes.join(" "));
|
|
6958
|
-
}
|
|
6959
|
-
url.searchParams.set("state", state);
|
|
6960
|
-
url.searchParams.set("code_challenge", codeChallenge);
|
|
6961
|
-
url.searchParams.set("code_challenge_method", "S256");
|
|
6962
|
-
url.searchParams.set("resource", input.resource);
|
|
6963
|
-
if (input.extraParams) {
|
|
6964
|
-
const RESERVED = new Set([
|
|
6965
|
-
"response_type",
|
|
6966
|
-
"client_id",
|
|
6967
|
-
"redirect_uri",
|
|
6968
|
-
"scope",
|
|
6969
|
-
"state",
|
|
6970
|
-
"code_challenge",
|
|
6971
|
-
"code_challenge_method",
|
|
6972
|
-
"resource"
|
|
6973
|
-
]);
|
|
6974
|
-
for (const [k, v] of Object.entries(input.extraParams)) {
|
|
6975
|
-
if (RESERVED.has(k.toLowerCase())) {
|
|
6976
|
-
console.warn(`[mcp-oauth] extraParams key "${k}" is reserved and skipped`);
|
|
6977
|
-
continue;
|
|
6978
|
-
}
|
|
6979
|
-
url.searchParams.set(k, v);
|
|
6980
|
-
}
|
|
6981
|
-
}
|
|
6982
|
-
return { url: url.toString(), state, codeVerifier, codeChallenge };
|
|
6983
|
-
}
|
|
6984
|
-
async function exchangeCodeForTokens(input) {
|
|
6985
|
-
const body = new URLSearchParams({
|
|
6986
|
-
grant_type: "authorization_code",
|
|
6987
|
-
code: input.code,
|
|
6988
|
-
redirect_uri: input.redirectUri,
|
|
6989
|
-
client_id: input.clientId,
|
|
6990
|
-
code_verifier: input.codeVerifier,
|
|
6991
|
-
resource: input.resource
|
|
6992
|
-
});
|
|
6993
|
-
if (input.clientSecret)
|
|
6994
|
-
body.set("client_secret", input.clientSecret);
|
|
6995
|
-
const res = await safeFetch(input.tokenUrl, {
|
|
6996
|
-
method: "POST",
|
|
6997
|
-
headers: {
|
|
6998
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
6999
|
-
Accept: "application/json"
|
|
7000
|
-
},
|
|
7001
|
-
body: body.toString()
|
|
7002
|
-
});
|
|
7003
|
-
if (!res.ok) {
|
|
7004
|
-
const text = await res.text().catch(() => "");
|
|
7005
|
-
throw new Error(`Token exchange failed (${res.status}): ${text}`);
|
|
7006
|
-
}
|
|
7007
|
-
return await res.json();
|
|
7008
|
-
}
|
|
7009
|
-
async function refreshMcpToken(input) {
|
|
7010
|
-
const body = new URLSearchParams({
|
|
7011
|
-
grant_type: "refresh_token",
|
|
7012
|
-
refresh_token: input.refreshToken,
|
|
7013
|
-
client_id: input.clientId,
|
|
7014
|
-
resource: input.resource
|
|
7015
|
-
});
|
|
7016
|
-
if (input.clientSecret)
|
|
7017
|
-
body.set("client_secret", input.clientSecret);
|
|
7018
|
-
if (input.scopes && input.scopes.length > 0)
|
|
7019
|
-
body.set("scope", input.scopes.join(" "));
|
|
7020
|
-
const res = await safeFetch(input.tokenUrl, {
|
|
7021
|
-
method: "POST",
|
|
7022
|
-
headers: {
|
|
7023
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
7024
|
-
Accept: "application/json"
|
|
7025
|
-
},
|
|
7026
|
-
body: body.toString()
|
|
7027
|
-
});
|
|
7028
|
-
if (!res.ok) {
|
|
7029
|
-
const text = await res.text().catch(() => "");
|
|
7030
|
-
throw new Error(`Token refresh failed (${res.status}): ${text}`);
|
|
7031
|
-
}
|
|
7032
|
-
return await res.json();
|
|
7033
|
-
}
|
|
7034
|
-
async function revokeMcpToken(input) {
|
|
7035
|
-
const body = new URLSearchParams({
|
|
7036
|
-
token: input.token,
|
|
7037
|
-
client_id: input.clientId
|
|
7038
|
-
});
|
|
7039
|
-
if (input.tokenTypeHint)
|
|
7040
|
-
body.set("token_type_hint", input.tokenTypeHint);
|
|
7041
|
-
if (input.clientSecret)
|
|
7042
|
-
body.set("client_secret", input.clientSecret);
|
|
7043
|
-
const res = await safeFetch(input.revocationUrl, {
|
|
7044
|
-
method: "POST",
|
|
7045
|
-
headers: {
|
|
7046
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
7047
|
-
Accept: "application/json"
|
|
7048
|
-
},
|
|
7049
|
-
body: body.toString()
|
|
7050
|
-
});
|
|
7051
|
-
if (!res.ok && res.status !== 200 && res.status !== 204) {
|
|
7052
|
-
const text = await res.text().catch(() => "");
|
|
7053
|
-
throw new Error(`Token revocation failed (${res.status}): ${text}`);
|
|
7054
|
-
}
|
|
7055
|
-
}
|
|
7056
|
-
function computeExpiresAt(expiresInSeconds) {
|
|
7057
|
-
if (!expiresInSeconds || expiresInSeconds <= 0)
|
|
7058
|
-
return null;
|
|
7059
|
-
return new Date(Date.now() + expiresInSeconds * 1000).toISOString();
|
|
7060
|
-
}
|
|
7061
|
-
|
|
7062
|
-
// src/oauth/ensure-mcp-token.ts
|
|
7063
|
-
var inflight = new Map;
|
|
7064
|
-
async function ensureMcpToken(mcpServerId, opts = {}) {
|
|
7065
|
-
const userId = opts.userId ?? null;
|
|
7066
|
-
const key = `${mcpServerId}::${userId ?? "_"}`;
|
|
7067
|
-
const existing = inflight.get(key);
|
|
7068
|
-
if (existing)
|
|
7069
|
-
return existing;
|
|
7070
|
-
const work = (async () => {
|
|
7071
|
-
const token = getMcpOAuthToken(mcpServerId, userId);
|
|
7072
|
-
if (!token)
|
|
7073
|
-
return null;
|
|
7074
|
-
if (token.status === "revoked")
|
|
7075
|
-
return token;
|
|
7076
|
-
if (!isMcpTokenExpiringSoon(token, opts.bufferMs))
|
|
7077
|
-
return token;
|
|
7078
|
-
if (!token.refreshToken) {
|
|
7079
|
-
markMcpOAuthTokenStatus(token.id, "expired", "No refresh token available; reconnect required.");
|
|
7080
|
-
return { ...token, status: "expired" };
|
|
7081
|
-
}
|
|
7082
|
-
try {
|
|
7083
|
-
const refreshed = await refreshMcpToken({
|
|
7084
|
-
tokenUrl: token.tokenUrl,
|
|
7085
|
-
clientId: token.dcrClientId ?? "",
|
|
7086
|
-
clientSecret: token.dcrClientSecret ?? undefined,
|
|
7087
|
-
refreshToken: token.refreshToken,
|
|
7088
|
-
resource: token.resourceUrl,
|
|
7089
|
-
scopes: token.scope ? token.scope.split(" ").filter(Boolean) : undefined
|
|
7090
|
-
});
|
|
7091
|
-
applyMcpOAuthRefresh(token.id, {
|
|
7092
|
-
accessToken: refreshed.access_token,
|
|
7093
|
-
refreshToken: refreshed.refresh_token ?? undefined,
|
|
7094
|
-
expiresAt: computeExpiresAt(refreshed.expires_in),
|
|
7095
|
-
scope: refreshed.scope ?? null
|
|
7096
|
-
});
|
|
7097
|
-
return getMcpOAuthToken(mcpServerId, userId);
|
|
7098
|
-
} catch (err) {
|
|
7099
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
7100
|
-
markMcpOAuthTokenStatus(token.id, "error", message);
|
|
7101
|
-
console.error(`[mcp-oauth] refresh failed for ${mcpServerId}: ${message}`);
|
|
7102
|
-
return { ...token, status: "error", lastErrorMessage: message };
|
|
7103
|
-
}
|
|
7104
|
-
})();
|
|
7105
|
-
inflight.set(key, work);
|
|
7106
|
-
try {
|
|
7107
|
-
return await work;
|
|
7108
|
-
} finally {
|
|
7109
|
-
inflight.delete(key);
|
|
7110
|
-
}
|
|
7111
|
-
}
|
|
7112
|
-
|
|
7113
|
-
// src/http/mcp-oauth.ts
|
|
7114
6616
|
function ssrfOptions() {
|
|
7115
6617
|
return {
|
|
7116
6618
|
allowPrivateHosts: process.env.MCP_OAUTH_ALLOW_PRIVATE_HOSTS === "true",
|
|
@@ -7227,6 +6729,7 @@ var authorizeRoute = route({
|
|
|
7227
6729
|
userId: exports_external.string().optional(),
|
|
7228
6730
|
scopes: exports_external.string().optional()
|
|
7229
6731
|
}),
|
|
6732
|
+
rbac: { permission: "mcp-oauth.authorize.any" },
|
|
7230
6733
|
responses: {
|
|
7231
6734
|
302: { description: "Redirect to authorization server" },
|
|
7232
6735
|
400: { description: "MCP has no URL / does not require OAuth" },
|
|
@@ -7246,6 +6749,7 @@ var authorizeUrlRoute = route({
|
|
|
7246
6749
|
userId: exports_external.string().optional(),
|
|
7247
6750
|
scopes: exports_external.string().optional()
|
|
7248
6751
|
}),
|
|
6752
|
+
rbac: { permission: "mcp-oauth.authorize.any" },
|
|
7249
6753
|
responses: {
|
|
7250
6754
|
200: { description: "{ providerUrl: string }" },
|
|
7251
6755
|
400: { description: "MCP has no URL / does not require OAuth" },
|
|
@@ -7713,6 +7217,7 @@ function stopMcpOAuthPendingGc() {
|
|
|
7713
7217
|
// src/http/mcp-servers.ts
|
|
7714
7218
|
init_zod();
|
|
7715
7219
|
init_db();
|
|
7220
|
+
init_request_auth_context();
|
|
7716
7221
|
var listMcpServersRoute = route({
|
|
7717
7222
|
method: "get",
|
|
7718
7223
|
path: "/api/mcp-servers",
|
|
@@ -7767,7 +7272,8 @@ var createMcpServerRoute = route({
|
|
|
7767
7272
|
responses: {
|
|
7768
7273
|
201: { description: "MCP server created" },
|
|
7769
7274
|
400: { description: "Validation error" }
|
|
7770
|
-
}
|
|
7275
|
+
},
|
|
7276
|
+
rbac: { permission: "mcp-server.create.swarm" }
|
|
7771
7277
|
});
|
|
7772
7278
|
var updateMcpServerRoute = route({
|
|
7773
7279
|
method: "put",
|
|
@@ -7781,7 +7287,8 @@ var updateMcpServerRoute = route({
|
|
|
7781
7287
|
responses: {
|
|
7782
7288
|
200: { description: "MCP server updated" },
|
|
7783
7289
|
404: { description: "MCP server not found" }
|
|
7784
|
-
}
|
|
7290
|
+
},
|
|
7291
|
+
rbac: { permission: "mcp-server.update.any" }
|
|
7785
7292
|
});
|
|
7786
7293
|
var deleteMcpServerRoute = route({
|
|
7787
7294
|
method: "delete",
|
|
@@ -7794,7 +7301,8 @@ var deleteMcpServerRoute = route({
|
|
|
7794
7301
|
responses: {
|
|
7795
7302
|
200: { description: "MCP server deleted" },
|
|
7796
7303
|
404: { description: "MCP server not found" }
|
|
7797
|
-
}
|
|
7304
|
+
},
|
|
7305
|
+
rbac: { permission: "mcp-server.delete.any" }
|
|
7798
7306
|
});
|
|
7799
7307
|
var installMcpServerRoute = route({
|
|
7800
7308
|
method: "post",
|
|
@@ -7810,7 +7318,8 @@ var installMcpServerRoute = route({
|
|
|
7810
7318
|
responses: {
|
|
7811
7319
|
200: { description: "MCP server installed" },
|
|
7812
7320
|
404: { description: "MCP server not found" }
|
|
7813
|
-
}
|
|
7321
|
+
},
|
|
7322
|
+
rbac: { permission: "mcp-server.install.any" }
|
|
7814
7323
|
});
|
|
7815
7324
|
var uninstallMcpServerRoute = route({
|
|
7816
7325
|
method: "delete",
|
|
@@ -7822,7 +7331,8 @@ var uninstallMcpServerRoute = route({
|
|
|
7822
7331
|
params: exports_external.object({ id: exports_external.string(), agentId: exports_external.string() }),
|
|
7823
7332
|
responses: {
|
|
7824
7333
|
200: { description: "MCP server uninstalled" }
|
|
7825
|
-
}
|
|
7334
|
+
},
|
|
7335
|
+
rbac: { permission: "mcp-server.uninstall.any" }
|
|
7826
7336
|
});
|
|
7827
7337
|
var getAgentMcpServersRoute = route({
|
|
7828
7338
|
method: "get",
|
|
@@ -7839,14 +7349,39 @@ var getAgentMcpServersRoute = route({
|
|
|
7839
7349
|
200: { description: "Agent MCP servers list" }
|
|
7840
7350
|
}
|
|
7841
7351
|
});
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7352
|
+
function canResolveMcpSecretsForHttpUser(req) {
|
|
7353
|
+
const auth = getRequestAuth(req);
|
|
7354
|
+
if (auth?.kind !== "user")
|
|
7355
|
+
return true;
|
|
7356
|
+
if (!isRbacEnabled())
|
|
7357
|
+
return true;
|
|
7358
|
+
const verb = "mcp-server.read.secrets";
|
|
7359
|
+
const grant = getUserGrant(auth.userId);
|
|
7360
|
+
const decision = grant.grantsAll || grant.verbs.has(verb) ? { allow: true, verb } : {
|
|
7361
|
+
allow: false,
|
|
7362
|
+
reason: `admission: missing permission '${verb}'`,
|
|
7363
|
+
verb
|
|
7364
|
+
};
|
|
7365
|
+
enqueueAdmissionRow({
|
|
7366
|
+
userId: auth.userId,
|
|
7367
|
+
decision,
|
|
7368
|
+
method: req.method,
|
|
7369
|
+
route: getAgentMcpServersRoute.def.path
|
|
7370
|
+
});
|
|
7371
|
+
return decision.allow;
|
|
7372
|
+
}
|
|
7373
|
+
async function handleMcpServers(req, res, pathSegments, queryParams) {
|
|
7374
|
+
if (getAgentMcpServersRoute.match(req.method, pathSegments)) {
|
|
7375
|
+
const parsed = await getAgentMcpServersRoute.parse(req, res, pathSegments, queryParams);
|
|
7376
|
+
if (!parsed)
|
|
7377
|
+
return true;
|
|
7847
7378
|
const servers = getAgentMcpServers(parsed.params.id);
|
|
7848
7379
|
const resolveSecrets = parsed.query.resolveSecrets === "true";
|
|
7849
7380
|
if (resolveSecrets) {
|
|
7381
|
+
if (!canResolveMcpSecretsForHttpUser(req)) {
|
|
7382
|
+
jsonError(res, "Forbidden: admission: missing permission 'mcp-server.read.secrets'", 403);
|
|
7383
|
+
return true;
|
|
7384
|
+
}
|
|
7850
7385
|
const configs = getResolvedConfig(parsed.params.id);
|
|
7851
7386
|
const configMap = new Map(configs.map((c) => [c.key, c.value]));
|
|
7852
7387
|
const serversWithSecrets = await Promise.all(servers.map(async (server) => {
|
|
@@ -8034,12 +7569,15 @@ async function handleMcpServers(req, res, pathSegments, queryParams) {
|
|
|
8034
7569
|
const parsed = await deleteMcpServerRoute.parse(req, res, pathSegments, queryParams);
|
|
8035
7570
|
if (!parsed)
|
|
8036
7571
|
return true;
|
|
8037
|
-
const
|
|
8038
|
-
if (!deleted) {
|
|
7572
|
+
const result = deleteMcpServer(parsed.params.id);
|
|
7573
|
+
if (!result.deleted) {
|
|
8039
7574
|
jsonError(res, "MCP server not found", 404);
|
|
8040
7575
|
return true;
|
|
8041
7576
|
}
|
|
8042
|
-
json(res, {
|
|
7577
|
+
json(res, {
|
|
7578
|
+
success: true,
|
|
7579
|
+
deletedScriptConnectionCount: result.deletedScriptConnectionCount
|
|
7580
|
+
});
|
|
8043
7581
|
return true;
|
|
8044
7582
|
}
|
|
8045
7583
|
return false;
|
|
@@ -8060,6 +7598,33 @@ var userSendTaskInputSchema = object({
|
|
|
8060
7598
|
model: string().trim().min(1).optional().describe("Concrete model override interpreted by the assignee's harness/provider."),
|
|
8061
7599
|
modelTier: ModelTierSchema.optional().describe("Portable model tier: 'smol', 'regular', 'smart', or 'ultra'. Resolved by the assignee's harness/provider.")
|
|
8062
7600
|
});
|
|
7601
|
+
function permission(verb) {
|
|
7602
|
+
return { permission: verb };
|
|
7603
|
+
}
|
|
7604
|
+
async function maybeDenyUserToolAdmission(user, toolName, config) {
|
|
7605
|
+
if (!isRbacEnabled())
|
|
7606
|
+
return;
|
|
7607
|
+
const grant = getUserGrant(user.id);
|
|
7608
|
+
if (grant.grantsAll)
|
|
7609
|
+
return;
|
|
7610
|
+
const decision = decideToolAdmission({
|
|
7611
|
+
rbac: config.rbac,
|
|
7612
|
+
readOnly: config.annotations?.readOnlyHint ?? false,
|
|
7613
|
+
grant
|
|
7614
|
+
});
|
|
7615
|
+
enqueueAdmissionRow({
|
|
7616
|
+
userId: user.id,
|
|
7617
|
+
decision,
|
|
7618
|
+
source: "mcp",
|
|
7619
|
+
toolName
|
|
7620
|
+
});
|
|
7621
|
+
if (decision.allow)
|
|
7622
|
+
return;
|
|
7623
|
+
return {
|
|
7624
|
+
isError: true,
|
|
7625
|
+
content: [{ type: "text", text: `Forbidden: ${decision.reason}` }]
|
|
7626
|
+
};
|
|
7627
|
+
}
|
|
8063
7628
|
function createUserServer(user) {
|
|
8064
7629
|
const server = new McpServer({
|
|
8065
7630
|
name: `${package_default.name}-user`,
|
|
@@ -8071,44 +7636,77 @@ function createUserServer(user) {
|
|
|
8071
7636
|
}
|
|
8072
7637
|
});
|
|
8073
7638
|
const registerTool = createToolRegistrar(server);
|
|
8074
|
-
|
|
7639
|
+
const sendTaskConfig = {
|
|
8075
7640
|
title: "Send a task",
|
|
8076
7641
|
annotations: { destructiveHint: false },
|
|
7642
|
+
rbac: permission("task.create.own"),
|
|
8077
7643
|
description: "Creates an unassigned task requested by the authenticated user.",
|
|
8078
7644
|
inputSchema: userSendTaskInputSchema,
|
|
8079
7645
|
outputSchema: sendTaskOutputSchema
|
|
8080
|
-
}
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
7646
|
+
};
|
|
7647
|
+
registerTool("send-task", sendTaskConfig, async (args, info, _meta) => {
|
|
7648
|
+
const denied = await maybeDenyUserToolAdmission(user, "send-task", sendTaskConfig);
|
|
7649
|
+
if (denied)
|
|
7650
|
+
return denied;
|
|
7651
|
+
return sendTaskHandler(userCtx(user, info.sessionId), {
|
|
7652
|
+
offerMode: false,
|
|
7653
|
+
allowDuplicate: false,
|
|
7654
|
+
...args
|
|
7655
|
+
});
|
|
7656
|
+
});
|
|
7657
|
+
const getTasksConfig = {
|
|
8086
7658
|
title: "Get tasks",
|
|
8087
7659
|
description: "Returns tasks requested by the authenticated user.",
|
|
8088
7660
|
annotations: { readOnlyHint: true },
|
|
8089
7661
|
inputSchema: getTasksInputSchema,
|
|
8090
7662
|
outputSchema: getTasksOutputSchema
|
|
8091
|
-
}
|
|
8092
|
-
registerTool("get-
|
|
7663
|
+
};
|
|
7664
|
+
registerTool("get-tasks", getTasksConfig, async (args, info, _meta) => {
|
|
7665
|
+
const denied = await maybeDenyUserToolAdmission(user, "get-tasks", getTasksConfig);
|
|
7666
|
+
if (denied)
|
|
7667
|
+
return denied;
|
|
7668
|
+
return getTasksHandler(userCtx(user, info.sessionId), args);
|
|
7669
|
+
});
|
|
7670
|
+
const getTaskDetailsConfig = {
|
|
8093
7671
|
title: "Get task details",
|
|
8094
7672
|
description: "Returns detailed information about one of your tasks.",
|
|
8095
7673
|
annotations: { readOnlyHint: true },
|
|
8096
7674
|
inputSchema: getTaskDetailsInputSchema,
|
|
8097
7675
|
outputSchema: getTaskDetailsOutputSchema
|
|
8098
|
-
}
|
|
8099
|
-
registerTool("
|
|
7676
|
+
};
|
|
7677
|
+
registerTool("get-task-details", getTaskDetailsConfig, async (args, info, _meta) => {
|
|
7678
|
+
const denied = await maybeDenyUserToolAdmission(user, "get-task-details", getTaskDetailsConfig);
|
|
7679
|
+
if (denied)
|
|
7680
|
+
return denied;
|
|
7681
|
+
return getTaskDetailsHandler(userCtx(user, info.sessionId), args);
|
|
7682
|
+
});
|
|
7683
|
+
const cancelTaskConfig = {
|
|
8100
7684
|
title: "Cancel Task",
|
|
8101
7685
|
description: "Cancel one of your pending or in-progress tasks.",
|
|
8102
7686
|
annotations: { destructiveHint: true },
|
|
7687
|
+
rbac: permission("task.cancel.own"),
|
|
8103
7688
|
inputSchema: cancelTaskInputSchema,
|
|
8104
7689
|
outputSchema: cancelTaskOutputSchema
|
|
8105
|
-
}
|
|
8106
|
-
registerTool("task
|
|
7690
|
+
};
|
|
7691
|
+
registerTool("cancel-task", cancelTaskConfig, async (args, info, _meta) => {
|
|
7692
|
+
const denied = await maybeDenyUserToolAdmission(user, "cancel-task", cancelTaskConfig);
|
|
7693
|
+
if (denied)
|
|
7694
|
+
return denied;
|
|
7695
|
+
return cancelTaskHandler(userCtx(user, info.sessionId), args);
|
|
7696
|
+
});
|
|
7697
|
+
const taskActionConfig = {
|
|
8107
7698
|
title: "Task Pool Action",
|
|
8108
7699
|
description: "Move one of your tasks to or from backlog.",
|
|
7700
|
+
rbac: permission("task.action.own"),
|
|
8109
7701
|
inputSchema: taskActionInputSchema,
|
|
8110
7702
|
outputSchema: taskActionOutputSchema
|
|
8111
|
-
}
|
|
7703
|
+
};
|
|
7704
|
+
registerTool("task-action", taskActionConfig, async (args, info, _meta) => {
|
|
7705
|
+
const denied = await maybeDenyUserToolAdmission(user, "task-action", taskActionConfig);
|
|
7706
|
+
if (denied)
|
|
7707
|
+
return denied;
|
|
7708
|
+
return taskActionHandler(userCtx(user, info.sessionId), args);
|
|
7709
|
+
});
|
|
8112
7710
|
return server;
|
|
8113
7711
|
}
|
|
8114
7712
|
|
|
@@ -9731,6 +9329,82 @@ async function handleMetrics(req, res, pathSegments, queryParams, myAgentId) {
|
|
|
9731
9329
|
return false;
|
|
9732
9330
|
}
|
|
9733
9331
|
|
|
9332
|
+
// src/http/oauth-generic.ts
|
|
9333
|
+
init_zod();
|
|
9334
|
+
var DEDICATED_CALLBACK_PROVIDERS = new Set(["linear"]);
|
|
9335
|
+
var genericOAuthCallbackRoute = route({
|
|
9336
|
+
method: "get",
|
|
9337
|
+
path: "/api/oauth/{provider}/callback",
|
|
9338
|
+
pattern: ["api", "oauth", null, "callback"],
|
|
9339
|
+
operationId: "oauth_generic_callback",
|
|
9340
|
+
summary: "Generic OAuth redirect target for script credential bindings",
|
|
9341
|
+
tags: ["OAuth"],
|
|
9342
|
+
auth: { apiKey: false },
|
|
9343
|
+
params: exports_external.object({
|
|
9344
|
+
provider: exports_external.string().min(1).max(255).regex(/^[A-Za-z0-9_-]+$/)
|
|
9345
|
+
}),
|
|
9346
|
+
query: exports_external.object({
|
|
9347
|
+
code: exports_external.string().optional(),
|
|
9348
|
+
state: exports_external.string().optional(),
|
|
9349
|
+
error: exports_external.string().optional(),
|
|
9350
|
+
error_description: exports_external.string().optional()
|
|
9351
|
+
}),
|
|
9352
|
+
responses: {
|
|
9353
|
+
200: { description: "OAuth authorization completed" },
|
|
9354
|
+
400: { description: "Missing or invalid OAuth callback parameters" },
|
|
9355
|
+
404: { description: "OAuth app not configured" },
|
|
9356
|
+
409: { description: "Provider has a dedicated OAuth callback" },
|
|
9357
|
+
502: { description: "Token exchange failed" }
|
|
9358
|
+
}
|
|
9359
|
+
});
|
|
9360
|
+
function sendAuthorizedHtml(res, provider) {
|
|
9361
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
9362
|
+
res.end(`<!DOCTYPE html>
|
|
9363
|
+
<html>
|
|
9364
|
+
<head><title>OAuth Authorized</title></head>
|
|
9365
|
+
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0;">
|
|
9366
|
+
<main style="text-align: center;">
|
|
9367
|
+
<h1>${provider} authorized</h1>
|
|
9368
|
+
<p>You can close this tab.</p>
|
|
9369
|
+
</main>
|
|
9370
|
+
</body>
|
|
9371
|
+
</html>`);
|
|
9372
|
+
}
|
|
9373
|
+
async function handleGenericOAuth(req, res, pathSegments, queryParams) {
|
|
9374
|
+
if (!genericOAuthCallbackRoute.match(req.method, pathSegments))
|
|
9375
|
+
return false;
|
|
9376
|
+
const parsed = await genericOAuthCallbackRoute.parse(req, res, pathSegments, queryParams);
|
|
9377
|
+
if (!parsed)
|
|
9378
|
+
return true;
|
|
9379
|
+
const { provider } = parsed.params;
|
|
9380
|
+
if (DEDICATED_CALLBACK_PROVIDERS.has(provider)) {
|
|
9381
|
+
jsonError(res, "Provider linear uses the dedicated OAuth flow at /api/trackers/linear/authorize and /api/trackers/linear/callback.", 409);
|
|
9382
|
+
return true;
|
|
9383
|
+
}
|
|
9384
|
+
if (parsed.query.error) {
|
|
9385
|
+
jsonError(res, parsed.query.error_description ?? parsed.query.error, 400);
|
|
9386
|
+
return true;
|
|
9387
|
+
}
|
|
9388
|
+
if (!parsed.query.code || !parsed.query.state) {
|
|
9389
|
+
jsonError(res, "Missing code or state parameter", 400);
|
|
9390
|
+
return true;
|
|
9391
|
+
}
|
|
9392
|
+
const config = getOAuthProviderConfig(provider);
|
|
9393
|
+
if (!config) {
|
|
9394
|
+
jsonError(res, `OAuth app ${provider} is not configured`, 404);
|
|
9395
|
+
return true;
|
|
9396
|
+
}
|
|
9397
|
+
try {
|
|
9398
|
+
await exchangeCode(config, parsed.query.code, parsed.query.state);
|
|
9399
|
+
sendAuthorizedHtml(res, provider);
|
|
9400
|
+
} catch (err) {
|
|
9401
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9402
|
+
const status = message.includes("Invalid or expired OAuth state") ? 400 : 502;
|
|
9403
|
+
jsonError(res, status === 400 ? "Invalid or expired OAuth state" : `Token exchange failed: ${message}`, status);
|
|
9404
|
+
}
|
|
9405
|
+
return true;
|
|
9406
|
+
}
|
|
9407
|
+
|
|
9734
9408
|
// src/http/oauth-locks.ts
|
|
9735
9409
|
init_zod();
|
|
9736
9410
|
var MAX_LOCK_TTL_MS = 5 * 60 * 1000;
|
|
@@ -11186,6 +10860,7 @@ async function handlePoll(req, res, pathSegments, queryParams, myAgentId) {
|
|
|
11186
10860
|
});
|
|
11187
10861
|
const requestedByUser = pendingTask.requestedByUserId ? getUserById(pendingTask.requestedByUserId) : undefined;
|
|
11188
10862
|
const requestedByNotes = getRequesterNotes(requestedByUser?.notes);
|
|
10863
|
+
const requestedByUnknownName = !requestedByUser && pendingTask.slackUserId ? renderIdentity(resolveIdentity("slack", pendingTask.slackUserId)) : undefined;
|
|
11189
10864
|
return {
|
|
11190
10865
|
trigger: {
|
|
11191
10866
|
type: "task_assigned",
|
|
@@ -11202,6 +10877,9 @@ async function handlePoll(req, res, pathSegments, queryParams, myAgentId) {
|
|
|
11202
10877
|
role: requestedByUser.role,
|
|
11203
10878
|
notes: requestedByNotes
|
|
11204
10879
|
}
|
|
10880
|
+
},
|
|
10881
|
+
...requestedByUnknownName && {
|
|
10882
|
+
requestedBy: { name: requestedByUnknownName }
|
|
11205
10883
|
}
|
|
11206
10884
|
}
|
|
11207
10885
|
};
|
|
@@ -11220,7 +10898,7 @@ async function handlePoll(req, res, pathSegments, queryParams, myAgentId) {
|
|
|
11220
10898
|
}
|
|
11221
10899
|
if (agent.isLead) {} else {
|
|
11222
10900
|
if (hasCapacity(myAgentId)) {
|
|
11223
|
-
const unassignedIds =
|
|
10901
|
+
const unassignedIds = getUnassignedTaskIdsForAgent(myAgentId, 5);
|
|
11224
10902
|
if (unassignedIds.length > 0) {
|
|
11225
10903
|
const candidateId = unassignedIds[0];
|
|
11226
10904
|
const candidateTask = getTaskById(candidateId);
|
|
@@ -12348,182 +12026,1501 @@ async function handleSchedules(req, res, pathSegments, queryParams, myAgentId) {
|
|
|
12348
12026
|
}
|
|
12349
12027
|
return true;
|
|
12350
12028
|
}
|
|
12351
|
-
if (runScheduleNow.match(req.method, pathSegments)) {
|
|
12352
|
-
const parsed = await runScheduleNow.parse(req, res, pathSegments, queryParams);
|
|
12029
|
+
if (runScheduleNow.match(req.method, pathSegments)) {
|
|
12030
|
+
const parsed = await runScheduleNow.parse(req, res, pathSegments, queryParams);
|
|
12031
|
+
if (!parsed)
|
|
12032
|
+
return true;
|
|
12033
|
+
const schedule = getScheduledTaskById(parsed.params.id);
|
|
12034
|
+
if (!schedule) {
|
|
12035
|
+
jsonError(res, "Schedule not found", 404);
|
|
12036
|
+
return true;
|
|
12037
|
+
}
|
|
12038
|
+
if (!schedule.enabled) {
|
|
12039
|
+
jsonError(res, "Schedule is disabled", 400);
|
|
12040
|
+
return true;
|
|
12041
|
+
}
|
|
12042
|
+
try {
|
|
12043
|
+
const { workflowRunIds, task } = await dispatchScheduleTarget(schedule, ["manual-run"]);
|
|
12044
|
+
const now = new Date().toISOString();
|
|
12045
|
+
if (schedule.scheduleType === "one_time") {
|
|
12046
|
+
updateScheduledTask(schedule.id, {
|
|
12047
|
+
lastRunAt: now,
|
|
12048
|
+
nextRunAt: null,
|
|
12049
|
+
enabled: false,
|
|
12050
|
+
lastUpdatedAt: now
|
|
12051
|
+
});
|
|
12052
|
+
} else {
|
|
12053
|
+
updateScheduledTask(schedule.id, {
|
|
12054
|
+
lastRunAt: now,
|
|
12055
|
+
lastUpdatedAt: now
|
|
12056
|
+
});
|
|
12057
|
+
}
|
|
12058
|
+
const updatedSchedule = getScheduledTaskById(parsed.params.id);
|
|
12059
|
+
json(res, { schedule: updatedSchedule, workflowRunIds, task });
|
|
12060
|
+
} catch (error) {
|
|
12061
|
+
jsonError(res, error instanceof Error ? error.message : "Failed to run schedule", 500);
|
|
12062
|
+
}
|
|
12063
|
+
return true;
|
|
12064
|
+
}
|
|
12065
|
+
if (getSchedule.match(req.method, pathSegments)) {
|
|
12066
|
+
const parsed = await getSchedule.parse(req, res, pathSegments, queryParams);
|
|
12067
|
+
if (!parsed)
|
|
12068
|
+
return true;
|
|
12069
|
+
const schedule = getScheduledTaskById(parsed.params.id);
|
|
12070
|
+
if (!schedule) {
|
|
12071
|
+
jsonError(res, "Schedule not found", 404);
|
|
12072
|
+
return true;
|
|
12073
|
+
}
|
|
12074
|
+
const userId = resolveHttpAuditUserId(req, myAgentId);
|
|
12075
|
+
const [decorated] = withFavoriteFlags([schedule], { userId, itemType: "schedule" });
|
|
12076
|
+
json(res, decorated ?? schedule);
|
|
12077
|
+
return true;
|
|
12078
|
+
}
|
|
12079
|
+
if (updateSchedule.match(req.method, pathSegments) || patchSchedule.match(req.method, pathSegments)) {
|
|
12080
|
+
const routeHandle = updateSchedule.match(req.method, pathSegments) ? updateSchedule : patchSchedule;
|
|
12081
|
+
const parsed = await routeHandle.parse(req, res, pathSegments, queryParams);
|
|
12082
|
+
if (!parsed)
|
|
12083
|
+
return true;
|
|
12084
|
+
const body = parsed.body;
|
|
12085
|
+
if (parsed.body.model !== undefined || parsed.body.modelTier !== undefined) {
|
|
12086
|
+
const normalizedModel = splitLegacyModelAlias({
|
|
12087
|
+
model: parsed.body.model,
|
|
12088
|
+
modelTier: parsed.body.modelTier
|
|
12089
|
+
});
|
|
12090
|
+
if (parsed.body.model !== undefined)
|
|
12091
|
+
body.model = normalizedModel.model ?? null;
|
|
12092
|
+
if (parsed.body.modelTier !== undefined || normalizedModel.modelTier) {
|
|
12093
|
+
body.modelTier = normalizedModel.modelTier ?? null;
|
|
12094
|
+
}
|
|
12095
|
+
}
|
|
12096
|
+
const existing = getScheduledTaskById(parsed.params.id);
|
|
12097
|
+
if (!existing) {
|
|
12098
|
+
jsonError(res, "Schedule not found", 404);
|
|
12099
|
+
return true;
|
|
12100
|
+
}
|
|
12101
|
+
if (existing.scheduleType === "one_time" && !existing.enabled && existing.lastRunAt) {
|
|
12102
|
+
jsonError(res, "One-time schedule has already executed. Create a new one instead.", 400);
|
|
12103
|
+
return true;
|
|
12104
|
+
}
|
|
12105
|
+
if (existing.scheduleType !== "one_time") {
|
|
12106
|
+
const timing = mergeScheduleTiming({
|
|
12107
|
+
cronExpression: existing.cronExpression ?? null,
|
|
12108
|
+
intervalMs: existing.intervalMs ?? null
|
|
12109
|
+
}, { cronExpression: parsed.body.cronExpression, intervalMs: parsed.body.intervalMs });
|
|
12110
|
+
if (validateRecurringTiming(timing)) {
|
|
12111
|
+
jsonError(res, "At least one of intervalMs or cronExpression must be set", 400);
|
|
12112
|
+
return true;
|
|
12113
|
+
}
|
|
12114
|
+
}
|
|
12115
|
+
if (parsed.body.cronExpression) {
|
|
12116
|
+
try {
|
|
12117
|
+
import_cron_parser.CronExpressionParser.parse(parsed.body.cronExpression);
|
|
12118
|
+
} catch {
|
|
12119
|
+
jsonError(res, "Invalid cron expression", 400);
|
|
12120
|
+
return true;
|
|
12121
|
+
}
|
|
12122
|
+
}
|
|
12123
|
+
if (parsed.body.targetAgentId) {
|
|
12124
|
+
const agent = getAgentById(parsed.body.targetAgentId);
|
|
12125
|
+
if (!agent) {
|
|
12126
|
+
jsonError(res, "Target agent not found", 400);
|
|
12127
|
+
return true;
|
|
12128
|
+
}
|
|
12129
|
+
}
|
|
12130
|
+
const mergedTargetType = parsed.body.targetType ?? existing.targetType;
|
|
12131
|
+
const mergedTaskTemplate = parsed.body.taskTemplate !== undefined ? parsed.body.taskTemplate : existing.taskTemplate;
|
|
12132
|
+
const mergedWorkflowId = parsed.body.workflowId !== undefined ? parsed.body.workflowId : existing.workflowId;
|
|
12133
|
+
const mergedScriptName = parsed.body.scriptName !== undefined ? parsed.body.scriptName : existing.scriptName;
|
|
12134
|
+
if (mergedTargetType === "agent-task" && !mergedTaskTemplate) {
|
|
12135
|
+
jsonError(res, "taskTemplate is required when targetType is 'agent-task'.", 400);
|
|
12136
|
+
return true;
|
|
12137
|
+
}
|
|
12138
|
+
if (mergedTargetType === "workflow") {
|
|
12139
|
+
if (!mergedWorkflowId) {
|
|
12140
|
+
jsonError(res, "workflowId is required when targetType is 'workflow'.", 400);
|
|
12141
|
+
return true;
|
|
12142
|
+
}
|
|
12143
|
+
if (!getWorkflow(mergedWorkflowId)) {
|
|
12144
|
+
jsonError(res, `Workflow not found: ${mergedWorkflowId}`, 400);
|
|
12145
|
+
return true;
|
|
12146
|
+
}
|
|
12147
|
+
}
|
|
12148
|
+
if (mergedTargetType === "script") {
|
|
12149
|
+
if (!mergedScriptName) {
|
|
12150
|
+
jsonError(res, "scriptName is required when targetType is 'script'.", 400);
|
|
12151
|
+
return true;
|
|
12152
|
+
}
|
|
12153
|
+
if (!getScript({ name: mergedScriptName, scope: "global" })) {
|
|
12154
|
+
jsonError(res, `Script not found: ${mergedScriptName}`, 400);
|
|
12155
|
+
return true;
|
|
12156
|
+
}
|
|
12157
|
+
}
|
|
12158
|
+
if (parsed.body.name && parsed.body.name !== existing.name) {
|
|
12159
|
+
const nameConflict = getScheduledTaskByName(parsed.body.name);
|
|
12160
|
+
if (nameConflict) {
|
|
12161
|
+
jsonError(res, "Schedule with this name already exists", 409);
|
|
12162
|
+
return true;
|
|
12163
|
+
}
|
|
12164
|
+
}
|
|
12165
|
+
const newEnabled = parsed.body.enabled !== undefined ? parsed.body.enabled : existing.enabled;
|
|
12166
|
+
if (existing.scheduleType === "one_time") {
|
|
12167
|
+
if (!newEnabled) {
|
|
12168
|
+
body.nextRunAt = null;
|
|
12169
|
+
}
|
|
12170
|
+
} else {
|
|
12171
|
+
if (!newEnabled) {
|
|
12172
|
+
body.nextRunAt = null;
|
|
12173
|
+
} else if (parsed.body.cronExpression !== undefined || parsed.body.intervalMs !== undefined || parsed.body.enabled === true && !existing.enabled) {
|
|
12174
|
+
const timing = mergeScheduleTiming({
|
|
12175
|
+
cronExpression: existing.cronExpression ?? null,
|
|
12176
|
+
intervalMs: existing.intervalMs ?? null
|
|
12177
|
+
}, { cronExpression: parsed.body.cronExpression, intervalMs: parsed.body.intervalMs });
|
|
12178
|
+
const mergedTimezone = parsed.body.timezone !== undefined ? parsed.body.timezone : existing.timezone;
|
|
12179
|
+
if (timing.mergedCron || timing.mergedInterval) {
|
|
12180
|
+
body.nextRunAt = calculateNextRun({
|
|
12181
|
+
cronExpression: timing.mergedCron,
|
|
12182
|
+
intervalMs: timing.mergedInterval,
|
|
12183
|
+
timezone: mergedTimezone
|
|
12184
|
+
});
|
|
12185
|
+
}
|
|
12186
|
+
}
|
|
12187
|
+
}
|
|
12188
|
+
const updatedBy = resolveHttpAuditUserId(req, myAgentId);
|
|
12189
|
+
if (updatedBy !== null)
|
|
12190
|
+
body.updatedBy = updatedBy;
|
|
12191
|
+
const schedule = updateScheduledTask(parsed.params.id, body);
|
|
12192
|
+
json(res, schedule);
|
|
12193
|
+
return true;
|
|
12194
|
+
}
|
|
12195
|
+
if (deleteSchedule.match(req.method, pathSegments)) {
|
|
12196
|
+
const parsed = await deleteSchedule.parse(req, res, pathSegments, queryParams);
|
|
12197
|
+
if (!parsed)
|
|
12198
|
+
return true;
|
|
12199
|
+
const deleted = deleteScheduledTask(parsed.params.id);
|
|
12200
|
+
if (!deleted) {
|
|
12201
|
+
jsonError(res, "Schedule not found", 404);
|
|
12202
|
+
return true;
|
|
12203
|
+
}
|
|
12204
|
+
json(res, { success: true });
|
|
12205
|
+
return true;
|
|
12206
|
+
}
|
|
12207
|
+
return false;
|
|
12208
|
+
}
|
|
12209
|
+
|
|
12210
|
+
// src/http/script-connection-proxy.ts
|
|
12211
|
+
init_zod();
|
|
12212
|
+
init_db();
|
|
12213
|
+
var mcpCallRoute = route({
|
|
12214
|
+
method: "post",
|
|
12215
|
+
path: "/api/script-connections/{id}/mcp-call",
|
|
12216
|
+
pattern: ["api", "script-connections", null, "mcp-call"],
|
|
12217
|
+
summary: "Invoke a tool on an MCP script connection",
|
|
12218
|
+
tags: ["Script Connections"],
|
|
12219
|
+
auth: { apiKey: true, agentId: true },
|
|
12220
|
+
rbac: { permission: "script-connection.invoke" },
|
|
12221
|
+
params: exports_external.object({ id: exports_external.string().uuid() }),
|
|
12222
|
+
body: exports_external.object({
|
|
12223
|
+
tool: exports_external.string().min(1),
|
|
12224
|
+
arguments: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
12225
|
+
}),
|
|
12226
|
+
responses: {
|
|
12227
|
+
200: { description: "MCP call result" },
|
|
12228
|
+
400: { description: "Invalid MCP connection or request" },
|
|
12229
|
+
403: { description: "Not allowed to invoke this MCP connection" },
|
|
12230
|
+
404: { description: "Script connection or agent not found" }
|
|
12231
|
+
}
|
|
12232
|
+
});
|
|
12233
|
+
function connectionScopeMatches(connection, agentId, syntheticPrincipal) {
|
|
12234
|
+
if (connection.scope === "global")
|
|
12235
|
+
return true;
|
|
12236
|
+
if (connection.scope === "agent")
|
|
12237
|
+
return !syntheticPrincipal && connection.scopeId === agentId;
|
|
12238
|
+
if (connection.scope === "repo")
|
|
12239
|
+
return true;
|
|
12240
|
+
return false;
|
|
12241
|
+
}
|
|
12242
|
+
function isSyntheticScriptPrincipal(agentId) {
|
|
12243
|
+
return agentId === "schedule" || agentId === "workflow";
|
|
12244
|
+
}
|
|
12245
|
+
async function handleScriptConnectionProxy(req, res, pathSegments, queryParams, agentId) {
|
|
12246
|
+
if (!mcpCallRoute.match(req.method, pathSegments))
|
|
12247
|
+
return false;
|
|
12248
|
+
const parsed = await mcpCallRoute.parse(req, res, pathSegments, queryParams);
|
|
12249
|
+
if (!parsed)
|
|
12250
|
+
return true;
|
|
12251
|
+
if (!agentId) {
|
|
12252
|
+
jsonError(res, "X-Agent-ID required for script connection MCP proxy", 400);
|
|
12253
|
+
return true;
|
|
12254
|
+
}
|
|
12255
|
+
const syntheticPrincipal = isSyntheticScriptPrincipal(agentId);
|
|
12256
|
+
const agent = syntheticPrincipal ? null : getAgentById(agentId);
|
|
12257
|
+
if (!agent && !syntheticPrincipal) {
|
|
12258
|
+
jsonError(res, "Agent not found", 404);
|
|
12259
|
+
return true;
|
|
12260
|
+
}
|
|
12261
|
+
const decision = can({
|
|
12262
|
+
principal: { kind: "agent", agentId, isLead: agent?.isLead ?? false },
|
|
12263
|
+
verb: "script-connection.invoke",
|
|
12264
|
+
resource: { kind: "none" },
|
|
12265
|
+
source: "http"
|
|
12266
|
+
});
|
|
12267
|
+
if (!decision.allow) {
|
|
12268
|
+
jsonError(res, decision.reason, 403);
|
|
12269
|
+
return true;
|
|
12270
|
+
}
|
|
12271
|
+
const connection = getScriptConnectionById(parsed.params.id);
|
|
12272
|
+
if (!connection) {
|
|
12273
|
+
jsonError(res, "Script connection not found", 404);
|
|
12274
|
+
return true;
|
|
12275
|
+
}
|
|
12276
|
+
if (!connection.enabled) {
|
|
12277
|
+
jsonError(res, "Script connection is disabled", 403);
|
|
12278
|
+
return true;
|
|
12279
|
+
}
|
|
12280
|
+
if (connection.kind !== "mcp") {
|
|
12281
|
+
jsonError(res, "Script connection is not an MCP connection", 400);
|
|
12282
|
+
return true;
|
|
12283
|
+
}
|
|
12284
|
+
if (!connectionScopeMatches(connection, agentId, syntheticPrincipal)) {
|
|
12285
|
+
jsonError(res, "Script connection is not available to this agent", 403);
|
|
12286
|
+
return true;
|
|
12287
|
+
}
|
|
12288
|
+
if (!connection.mcpServerId) {
|
|
12289
|
+
jsonError(res, "Script MCP connection is missing mcpServerId", 400);
|
|
12290
|
+
return true;
|
|
12291
|
+
}
|
|
12292
|
+
try {
|
|
12293
|
+
const result = await callMcpServerTool(connection.mcpServerId, parsed.body.tool, parsed.body.arguments ?? {}, { agentId, timeoutMs: 30000 });
|
|
12294
|
+
json(res, result);
|
|
12295
|
+
} catch (err) {
|
|
12296
|
+
json(res, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
12297
|
+
}
|
|
12298
|
+
return true;
|
|
12299
|
+
}
|
|
12300
|
+
|
|
12301
|
+
// src/http/script-connections.ts
|
|
12302
|
+
init_zod();
|
|
12303
|
+
init_date_utils();
|
|
12304
|
+
init_db();
|
|
12305
|
+
init_request_auth_context();
|
|
12306
|
+
init_secret_scrubber();
|
|
12307
|
+
var providerSchema = exports_external.string().min(1).max(255).regex(/^[A-Za-z0-9_-]+$/);
|
|
12308
|
+
var scopeSchema = exports_external.enum(["global", "agent", "repo"]);
|
|
12309
|
+
var connectionKindSchema = exports_external.enum(["openapi", "graphql", "mcp"]);
|
|
12310
|
+
var idParamsSchema = exports_external.object({ id: exports_external.string().uuid() });
|
|
12311
|
+
var providerParamsSchema = exports_external.object({ provider: providerSchema });
|
|
12312
|
+
var listConnectionsQuerySchema = exports_external.object({
|
|
12313
|
+
kind: connectionKindSchema.optional(),
|
|
12314
|
+
scope: scopeSchema.optional(),
|
|
12315
|
+
scopeId: exports_external.string().optional()
|
|
12316
|
+
});
|
|
12317
|
+
var connectionBaseBodySchema = exports_external.object({
|
|
12318
|
+
id: exports_external.string().uuid().optional(),
|
|
12319
|
+
slug: exports_external.string().min(1).max(80),
|
|
12320
|
+
displayName: exports_external.string().max(160).optional(),
|
|
12321
|
+
scope: scopeSchema.optional(),
|
|
12322
|
+
scopeId: scopedResourceScopeIdSchema.nullable().optional(),
|
|
12323
|
+
allowedHosts: exports_external.array(exports_external.string().min(1)).optional(),
|
|
12324
|
+
credentialBindingId: exports_external.string().uuid().nullable().optional(),
|
|
12325
|
+
configKey: exports_external.string().min(1).max(255).optional(),
|
|
12326
|
+
headerTemplate: exports_external.string().min(1).optional(),
|
|
12327
|
+
queryTemplate: exports_external.string().min(1).optional(),
|
|
12328
|
+
authKind: exports_external.enum(["config", "oauth"]).optional(),
|
|
12329
|
+
oauthProvider: providerSchema.optional(),
|
|
12330
|
+
enabled: exports_external.boolean().optional()
|
|
12331
|
+
});
|
|
12332
|
+
var upsertConnectionBodySchema = exports_external.discriminatedUnion("kind", [
|
|
12333
|
+
connectionBaseBodySchema.extend({
|
|
12334
|
+
kind: exports_external.literal("openapi"),
|
|
12335
|
+
baseUrl: exports_external.string().url(),
|
|
12336
|
+
openapiSpecUrl: exports_external.string().url().optional(),
|
|
12337
|
+
openapiSpecJson: exports_external.string().optional()
|
|
12338
|
+
}),
|
|
12339
|
+
connectionBaseBodySchema.extend({
|
|
12340
|
+
kind: exports_external.literal("graphql"),
|
|
12341
|
+
baseUrl: exports_external.string().url(),
|
|
12342
|
+
allowedHosts: exports_external.array(exports_external.string().min(1)).min(1)
|
|
12343
|
+
}),
|
|
12344
|
+
connectionBaseBodySchema.extend({
|
|
12345
|
+
kind: exports_external.literal("mcp"),
|
|
12346
|
+
mcpServerId: exports_external.string().uuid()
|
|
12347
|
+
})
|
|
12348
|
+
]);
|
|
12349
|
+
var disableConnectionBodySchema = exports_external.object({ enabled: exports_external.boolean() });
|
|
12350
|
+
var credentialBindingBodySchema = exports_external.object({
|
|
12351
|
+
id: exports_external.string().uuid().optional(),
|
|
12352
|
+
configKey: exports_external.string().min(1).max(255),
|
|
12353
|
+
allowedHosts: exports_external.array(exports_external.string().min(1)).min(1),
|
|
12354
|
+
headerTemplate: exports_external.string().min(1).optional(),
|
|
12355
|
+
queryTemplate: exports_external.string().min(1).optional(),
|
|
12356
|
+
scope: scopeSchema.default("global").optional(),
|
|
12357
|
+
scopeId: scopedResourceScopeIdSchema.nullable().optional(),
|
|
12358
|
+
active: exports_external.boolean().default(true).optional(),
|
|
12359
|
+
authKind: exports_external.enum(["config", "oauth"]).default("config").optional(),
|
|
12360
|
+
oauthProvider: providerSchema.optional()
|
|
12361
|
+
});
|
|
12362
|
+
var oauthAppBodySchema = exports_external.object({
|
|
12363
|
+
provider: providerSchema,
|
|
12364
|
+
clientId: exports_external.string().min(1),
|
|
12365
|
+
clientSecret: exports_external.string().min(1).optional(),
|
|
12366
|
+
authorizeUrl: exports_external.string().url(),
|
|
12367
|
+
tokenUrl: exports_external.string().url(),
|
|
12368
|
+
scopes: exports_external.array(exports_external.string().min(1)).default([]).optional(),
|
|
12369
|
+
extraParams: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
12370
|
+
tokenAuthStyle: exports_external.enum(["body", "basic"]).optional(),
|
|
12371
|
+
tokenBodyFormat: exports_external.enum(["form", "json"]).optional()
|
|
12372
|
+
});
|
|
12373
|
+
var discoverOAuthAppBodySchema = exports_external.object({
|
|
12374
|
+
url: exports_external.string().url()
|
|
12375
|
+
});
|
|
12376
|
+
var listConnectionsRoute = route({
|
|
12377
|
+
method: "get",
|
|
12378
|
+
path: "/api/script-connections",
|
|
12379
|
+
pattern: ["api", "script-connections"],
|
|
12380
|
+
operationId: "script_connections_list",
|
|
12381
|
+
summary: "List script connections",
|
|
12382
|
+
description: "Dashboard read of OpenAPI, GraphQL, and MCP script connections with credential summaries.",
|
|
12383
|
+
tags: ["Script Connections"],
|
|
12384
|
+
query: listConnectionsQuerySchema,
|
|
12385
|
+
responses: {
|
|
12386
|
+
200: { description: "Script connections" },
|
|
12387
|
+
400: { description: "Validation error" }
|
|
12388
|
+
}
|
|
12389
|
+
});
|
|
12390
|
+
var getConnectionRoute = route({
|
|
12391
|
+
method: "get",
|
|
12392
|
+
path: "/api/script-connections/{id}",
|
|
12393
|
+
pattern: ["api", "script-connections", null],
|
|
12394
|
+
operationId: "script_connections_get",
|
|
12395
|
+
summary: "Get script connection detail",
|
|
12396
|
+
tags: ["Script Connections"],
|
|
12397
|
+
params: idParamsSchema,
|
|
12398
|
+
responses: {
|
|
12399
|
+
200: { description: "Script connection detail" },
|
|
12400
|
+
404: { description: "Script connection not found" }
|
|
12401
|
+
}
|
|
12402
|
+
});
|
|
12403
|
+
var upsertConnectionRoute = route({
|
|
12404
|
+
method: "post",
|
|
12405
|
+
path: "/api/script-connections",
|
|
12406
|
+
pattern: ["api", "script-connections"],
|
|
12407
|
+
operationId: "script_connections_upsert",
|
|
12408
|
+
summary: "Create or update a script connection",
|
|
12409
|
+
tags: ["Script Connections"],
|
|
12410
|
+
body: upsertConnectionBodySchema,
|
|
12411
|
+
responses: {
|
|
12412
|
+
200: { description: "Saved script connection" },
|
|
12413
|
+
400: { description: "Validation or generation error" },
|
|
12414
|
+
403: { description: "Only the lead agent can manage script connections" }
|
|
12415
|
+
},
|
|
12416
|
+
rbac: { permission: "script-connection.manage" }
|
|
12417
|
+
});
|
|
12418
|
+
var refreshConnectionRoute = route({
|
|
12419
|
+
method: "post",
|
|
12420
|
+
path: "/api/script-connections/{id}/refresh",
|
|
12421
|
+
pattern: ["api", "script-connections", null, "refresh"],
|
|
12422
|
+
operationId: "script_connections_refresh",
|
|
12423
|
+
summary: "Refresh a script connection",
|
|
12424
|
+
tags: ["Script Connections"],
|
|
12425
|
+
params: idParamsSchema,
|
|
12426
|
+
responses: {
|
|
12427
|
+
200: { description: "Refreshed script connection" },
|
|
12428
|
+
400: { description: "Connection cannot be refreshed" },
|
|
12429
|
+
403: { description: "Only the lead agent can manage script connections" },
|
|
12430
|
+
404: { description: "Script connection not found" }
|
|
12431
|
+
},
|
|
12432
|
+
rbac: { permission: "script-connection.manage" }
|
|
12433
|
+
});
|
|
12434
|
+
var setConnectionEnabledRoute = route({
|
|
12435
|
+
method: "post",
|
|
12436
|
+
path: "/api/script-connections/{id}/disable",
|
|
12437
|
+
pattern: ["api", "script-connections", null, "disable"],
|
|
12438
|
+
operationId: "script_connections_set_enabled",
|
|
12439
|
+
summary: "Enable or disable a script connection",
|
|
12440
|
+
tags: ["Script Connections"],
|
|
12441
|
+
params: idParamsSchema,
|
|
12442
|
+
body: disableConnectionBodySchema,
|
|
12443
|
+
responses: {
|
|
12444
|
+
200: { description: "Updated script connection" },
|
|
12445
|
+
403: { description: "Only the lead agent can manage script connections" },
|
|
12446
|
+
404: { description: "Script connection not found" }
|
|
12447
|
+
},
|
|
12448
|
+
rbac: { permission: "script-connection.manage" }
|
|
12449
|
+
});
|
|
12450
|
+
var listCredentialBindingsRoute = route({
|
|
12451
|
+
method: "get",
|
|
12452
|
+
path: "/api/credential-bindings",
|
|
12453
|
+
pattern: ["api", "credential-bindings"],
|
|
12454
|
+
operationId: "credential_bindings_list",
|
|
12455
|
+
summary: "List script credential bindings",
|
|
12456
|
+
tags: ["Script Connections"],
|
|
12457
|
+
responses: {
|
|
12458
|
+
200: { description: "Credential bindings" }
|
|
12459
|
+
}
|
|
12460
|
+
});
|
|
12461
|
+
var upsertCredentialBindingRoute = route({
|
|
12462
|
+
method: "post",
|
|
12463
|
+
path: "/api/credential-bindings",
|
|
12464
|
+
pattern: ["api", "credential-bindings"],
|
|
12465
|
+
operationId: "credential_bindings_upsert",
|
|
12466
|
+
summary: "Create or update a script credential binding",
|
|
12467
|
+
tags: ["Script Connections"],
|
|
12468
|
+
body: credentialBindingBodySchema,
|
|
12469
|
+
responses: {
|
|
12470
|
+
200: { description: "Saved credential binding" },
|
|
12471
|
+
400: { description: "Validation error" },
|
|
12472
|
+
403: { description: "Only the lead agent can manage script connections" }
|
|
12473
|
+
},
|
|
12474
|
+
rbac: { permission: "script-connection.manage" }
|
|
12475
|
+
});
|
|
12476
|
+
var listOAuthAppsRoute = route({
|
|
12477
|
+
method: "get",
|
|
12478
|
+
path: "/api/oauth-apps",
|
|
12479
|
+
pattern: ["api", "oauth-apps"],
|
|
12480
|
+
operationId: "oauth_apps_list",
|
|
12481
|
+
summary: "List OAuth apps for script credential bindings",
|
|
12482
|
+
tags: ["Script Connections"],
|
|
12483
|
+
responses: {
|
|
12484
|
+
200: { description: "OAuth apps without client secrets" }
|
|
12485
|
+
}
|
|
12486
|
+
});
|
|
12487
|
+
var upsertOAuthAppRoute = route({
|
|
12488
|
+
method: "post",
|
|
12489
|
+
path: "/api/oauth-apps",
|
|
12490
|
+
pattern: ["api", "oauth-apps"],
|
|
12491
|
+
operationId: "oauth_apps_upsert",
|
|
12492
|
+
summary: "Create or update an OAuth app for script credential bindings",
|
|
12493
|
+
tags: ["Script Connections"],
|
|
12494
|
+
body: oauthAppBodySchema,
|
|
12495
|
+
responses: {
|
|
12496
|
+
200: { description: "Saved OAuth app without client secret" },
|
|
12497
|
+
400: { description: "Validation error" },
|
|
12498
|
+
403: { description: "Only the lead agent can manage script connections" }
|
|
12499
|
+
},
|
|
12500
|
+
rbac: { permission: "script-connection.manage" }
|
|
12501
|
+
});
|
|
12502
|
+
var discoverOAuthAppRoute = route({
|
|
12503
|
+
method: "post",
|
|
12504
|
+
path: "/api/oauth-apps/discover",
|
|
12505
|
+
pattern: ["api", "oauth-apps", "discover"],
|
|
12506
|
+
operationId: "oauth_apps_discover",
|
|
12507
|
+
summary: "Discover OAuth endpoints from provider metadata",
|
|
12508
|
+
tags: ["Script Connections"],
|
|
12509
|
+
body: discoverOAuthAppBodySchema,
|
|
12510
|
+
responses: {
|
|
12511
|
+
200: { description: "Discovered OAuth metadata" },
|
|
12512
|
+
400: { description: "Discovery failed" },
|
|
12513
|
+
403: { description: "Only the lead agent can manage script connections" }
|
|
12514
|
+
},
|
|
12515
|
+
rbac: { permission: "script-connection.manage" }
|
|
12516
|
+
});
|
|
12517
|
+
var deleteOAuthAppRoute = route({
|
|
12518
|
+
method: "delete",
|
|
12519
|
+
path: "/api/oauth-apps/{provider}",
|
|
12520
|
+
pattern: ["api", "oauth-apps", null],
|
|
12521
|
+
operationId: "oauth_apps_delete",
|
|
12522
|
+
summary: "Delete an OAuth app and its tokens",
|
|
12523
|
+
tags: ["Script Connections"],
|
|
12524
|
+
params: providerParamsSchema,
|
|
12525
|
+
responses: {
|
|
12526
|
+
200: { description: "OAuth app deleted" },
|
|
12527
|
+
403: { description: "Only the lead agent can manage script connections" },
|
|
12528
|
+
404: { description: "OAuth app not found" }
|
|
12529
|
+
},
|
|
12530
|
+
rbac: { permission: "script-connection.manage" }
|
|
12531
|
+
});
|
|
12532
|
+
var authorizeUrlRoute2 = route({
|
|
12533
|
+
method: "post",
|
|
12534
|
+
path: "/api/oauth-apps/{provider}/authorize-url",
|
|
12535
|
+
pattern: ["api", "oauth-apps", null, "authorize-url"],
|
|
12536
|
+
operationId: "oauth_apps_authorize_url",
|
|
12537
|
+
summary: "Build an OAuth authorization URL",
|
|
12538
|
+
tags: ["Script Connections"],
|
|
12539
|
+
params: providerParamsSchema,
|
|
12540
|
+
responses: {
|
|
12541
|
+
200: { description: "OAuth authorization URL" },
|
|
12542
|
+
403: { description: "Only the lead agent can manage script connections" },
|
|
12543
|
+
404: { description: "OAuth app not found" }
|
|
12544
|
+
},
|
|
12545
|
+
rbac: { permission: "script-connection.manage" }
|
|
12546
|
+
});
|
|
12547
|
+
var integrationsCatalogRoute = route({
|
|
12548
|
+
method: "get",
|
|
12549
|
+
path: "/api/integrations-catalog",
|
|
12550
|
+
pattern: ["api", "integrations-catalog"],
|
|
12551
|
+
operationId: "integrations_catalog_list",
|
|
12552
|
+
summary: "Proxy integrations.sh catalog entries",
|
|
12553
|
+
tags: ["Script Connections"],
|
|
12554
|
+
responses: {
|
|
12555
|
+
200: { description: "Integrations catalog entries" },
|
|
12556
|
+
502: { description: "Catalog upstream unavailable" }
|
|
12557
|
+
}
|
|
12558
|
+
});
|
|
12559
|
+
var surfaceDomainSchema = exports_external.string().min(1).max(255).regex(/^[a-z0-9.-]+$/i);
|
|
12560
|
+
var integrationsSurfaceRoute = route({
|
|
12561
|
+
method: "get",
|
|
12562
|
+
path: "/api/integrations-catalog/{domain}/surface",
|
|
12563
|
+
pattern: ["api", "integrations-catalog", null, "surface"],
|
|
12564
|
+
operationId: "integrations_catalog_surface",
|
|
12565
|
+
summary: "Proxy integrations.sh per-domain surface details (trimmed for the Add Connection flow)",
|
|
12566
|
+
tags: ["Script Connections"],
|
|
12567
|
+
params: exports_external.object({ domain: surfaceDomainSchema }),
|
|
12568
|
+
responses: {
|
|
12569
|
+
200: { description: "Trimmed integration surface details for a domain" },
|
|
12570
|
+
404: { description: "No surface data for this domain" },
|
|
12571
|
+
502: { description: "Surface upstream unavailable" }
|
|
12572
|
+
}
|
|
12573
|
+
});
|
|
12574
|
+
var disconnectOAuthAppRoute = route({
|
|
12575
|
+
method: "delete",
|
|
12576
|
+
path: "/api/oauth-apps/{provider}/tokens",
|
|
12577
|
+
pattern: ["api", "oauth-apps", null, "tokens"],
|
|
12578
|
+
operationId: "oauth_app_disconnect",
|
|
12579
|
+
summary: "Disconnect an OAuth app: delete stored tokens (best-effort remote revocation when a revocation endpoint is known)",
|
|
12580
|
+
tags: ["Script Connections"],
|
|
12581
|
+
params: providerParamsSchema,
|
|
12582
|
+
responses: {
|
|
12583
|
+
200: { description: "Disconnect result" },
|
|
12584
|
+
403: { description: "Only the lead agent can manage script connections" },
|
|
12585
|
+
404: { description: "OAuth app not found" }
|
|
12586
|
+
},
|
|
12587
|
+
rbac: { permission: "script-connection.manage" }
|
|
12588
|
+
});
|
|
12589
|
+
var refreshOAuthAppTokensRoute = route({
|
|
12590
|
+
method: "post",
|
|
12591
|
+
path: "/api/oauth-apps/{provider}/refresh",
|
|
12592
|
+
pattern: ["api", "oauth-apps", null, "refresh"],
|
|
12593
|
+
operationId: "oauth_app_refresh_tokens",
|
|
12594
|
+
summary: "Force-refresh the stored OAuth tokens for a provider (never returns token values)",
|
|
12595
|
+
tags: ["Script Connections"],
|
|
12596
|
+
params: providerParamsSchema,
|
|
12597
|
+
responses: {
|
|
12598
|
+
200: { description: "Refresh result with token status and new expiry" },
|
|
12599
|
+
400: { description: "No stored tokens or provider does not support refresh" },
|
|
12600
|
+
403: { description: "Only the lead agent can manage script connections" },
|
|
12601
|
+
404: { description: "OAuth app not found" },
|
|
12602
|
+
502: { description: "Provider token endpoint rejected the refresh" }
|
|
12603
|
+
},
|
|
12604
|
+
rbac: { permission: "script-connection.manage" }
|
|
12605
|
+
});
|
|
12606
|
+
var DISCOVERY_TIMEOUT_MS = 1e4;
|
|
12607
|
+
var INTEGRATIONS_CATALOG_TIMEOUT_MS = 15000;
|
|
12608
|
+
var INTEGRATIONS_CATALOG_TTL_MS = 60 * 60 * 1000;
|
|
12609
|
+
var SPEC_PREVIEW_MAX_BYTES = 50 * 1024;
|
|
12610
|
+
var integrationsCatalogCache = null;
|
|
12611
|
+
var INTEGRATIONS_SURFACE_CACHE_MAX_ENTRIES = 200;
|
|
12612
|
+
var integrationsSurfaceCache = new Map;
|
|
12613
|
+
|
|
12614
|
+
class SurfaceNotFoundError extends Error {
|
|
12615
|
+
}
|
|
12616
|
+
function singleHeader4(req, name) {
|
|
12617
|
+
const raw = req.headers[name];
|
|
12618
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
12619
|
+
}
|
|
12620
|
+
function ensureConnectionAdmin(req, res, agentId) {
|
|
12621
|
+
const auth = getRequestAuth(req);
|
|
12622
|
+
if (auth?.kind === "operator" || auth?.kind === "user")
|
|
12623
|
+
return true;
|
|
12624
|
+
const callerAgentId = agentId ?? singleHeader4(req, "x-agent-id");
|
|
12625
|
+
const agent = callerAgentId ? getAgentById(callerAgentId) : undefined;
|
|
12626
|
+
const decision = can({
|
|
12627
|
+
principal: {
|
|
12628
|
+
kind: "agent",
|
|
12629
|
+
agentId: callerAgentId ?? "",
|
|
12630
|
+
isLead: agent?.isLead ?? false
|
|
12631
|
+
},
|
|
12632
|
+
verb: "script-connection.manage",
|
|
12633
|
+
resource: { kind: "none" },
|
|
12634
|
+
source: "http"
|
|
12635
|
+
});
|
|
12636
|
+
if (!decision.allow) {
|
|
12637
|
+
jsonError(res, "Only the lead can manage script connections.", 403);
|
|
12638
|
+
return false;
|
|
12639
|
+
}
|
|
12640
|
+
return true;
|
|
12641
|
+
}
|
|
12642
|
+
function tokenStatusForBinding(binding) {
|
|
12643
|
+
return binding.authKind === "oauth" && binding.oauthProvider ? getOAuthBindingTokenStatus(binding.oauthProvider) : undefined;
|
|
12644
|
+
}
|
|
12645
|
+
function decorateBinding(binding) {
|
|
12646
|
+
const tokenStatus = tokenStatusForBinding(binding);
|
|
12647
|
+
return tokenStatus ? { ...binding, tokenStatus } : binding;
|
|
12648
|
+
}
|
|
12649
|
+
function bindingSummary(binding) {
|
|
12650
|
+
if (!binding)
|
|
12651
|
+
return null;
|
|
12652
|
+
const tokenStatus = tokenStatusForBinding(binding);
|
|
12653
|
+
return {
|
|
12654
|
+
id: binding.id,
|
|
12655
|
+
configKey: binding.configKey,
|
|
12656
|
+
authKind: binding.authKind ?? "config",
|
|
12657
|
+
...binding.oauthProvider ? { oauthProvider: binding.oauthProvider } : {},
|
|
12658
|
+
...tokenStatus ? { tokenStatus } : {}
|
|
12659
|
+
};
|
|
12660
|
+
}
|
|
12661
|
+
function runtimeCounts(connection) {
|
|
12662
|
+
if (!connection.generatedRuntimeJson) {
|
|
12663
|
+
return { operationCount: 0, toolCount: 0 };
|
|
12664
|
+
}
|
|
12665
|
+
try {
|
|
12666
|
+
const runtime = JSON.parse(connection.generatedRuntimeJson);
|
|
12667
|
+
const operationCount = Array.isArray(runtime.operations) ? runtime.operations.length : connection.kind === "graphql" ? 1 : 0;
|
|
12668
|
+
const toolCount = Array.isArray(runtime.tools) ? runtime.tools.length : 0;
|
|
12669
|
+
return { operationCount, toolCount };
|
|
12670
|
+
} catch {
|
|
12671
|
+
return { operationCount: 0, toolCount: 0 };
|
|
12672
|
+
}
|
|
12673
|
+
}
|
|
12674
|
+
function parseRecord(value) {
|
|
12675
|
+
if (!value)
|
|
12676
|
+
return null;
|
|
12677
|
+
try {
|
|
12678
|
+
const parsed = JSON.parse(value);
|
|
12679
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
12680
|
+
} catch {
|
|
12681
|
+
return null;
|
|
12682
|
+
}
|
|
12683
|
+
}
|
|
12684
|
+
function connectionDetail(connection) {
|
|
12685
|
+
const decorated = decorateConnections([connection])[0];
|
|
12686
|
+
if (!decorated)
|
|
12687
|
+
throw new Error("Failed to decorate connection detail.");
|
|
12688
|
+
const runtime = parseRecord(connection.generatedRuntimeJson);
|
|
12689
|
+
const operations = Array.isArray(runtime?.operations) ? runtime.operations.filter((operation) => {
|
|
12690
|
+
return operation !== null && typeof operation === "object" && !Array.isArray(operation);
|
|
12691
|
+
}).map((operation) => ({
|
|
12692
|
+
name: String(operation.name ?? ""),
|
|
12693
|
+
method: String(operation.method ?? ""),
|
|
12694
|
+
path: String(operation.path ?? ""),
|
|
12695
|
+
...Array.isArray(operation.parameters) ? {
|
|
12696
|
+
parameters: operation.parameters.filter((param) => {
|
|
12697
|
+
return param !== null && typeof param === "object" && !Array.isArray(param);
|
|
12698
|
+
}).map((param) => ({
|
|
12699
|
+
name: String(param.name ?? ""),
|
|
12700
|
+
in: String(param.in ?? "query"),
|
|
12701
|
+
required: param.required === true,
|
|
12702
|
+
...param.schema !== undefined ? { schema: param.schema } : {}
|
|
12703
|
+
})).filter((param) => param.name)
|
|
12704
|
+
} : {},
|
|
12705
|
+
...typeof operation.hasBody === "boolean" ? { hasBody: operation.hasBody } : {},
|
|
12706
|
+
...typeof operation.successStatus === "string" ? { successStatus: operation.successStatus } : {},
|
|
12707
|
+
...operation.requestBodySchema !== undefined ? { requestBodySchema: operation.requestBodySchema } : {},
|
|
12708
|
+
...operation.responseSchema !== undefined ? { responseSchema: operation.responseSchema } : {}
|
|
12709
|
+
})).filter((operation) => operation.name && operation.method && operation.path) : [];
|
|
12710
|
+
const tools = Array.isArray(runtime?.tools) ? runtime.tools.filter((tool) => {
|
|
12711
|
+
return tool !== null && typeof tool === "object" && !Array.isArray(tool);
|
|
12712
|
+
}).map((tool) => ({
|
|
12713
|
+
name: String(tool.name ?? ""),
|
|
12714
|
+
...typeof tool.description === "string" ? { description: tool.description } : {},
|
|
12715
|
+
...tool.inputSchema !== undefined ? { inputSchema: tool.inputSchema } : {}
|
|
12716
|
+
})).filter((tool) => tool.name) : [];
|
|
12717
|
+
const detail = {
|
|
12718
|
+
...decorated,
|
|
12719
|
+
operations,
|
|
12720
|
+
tools,
|
|
12721
|
+
graphql: connection.kind === "graphql",
|
|
12722
|
+
generatedTypes: connection.generatedTypes ?? ""
|
|
12723
|
+
};
|
|
12724
|
+
if (connection.kind === "openapi" && connection.openapiSpecJson) {
|
|
12725
|
+
try {
|
|
12726
|
+
const spec = JSON.parse(connection.openapiSpecJson);
|
|
12727
|
+
const info = spec.info && typeof spec.info === "object" && !Array.isArray(spec.info) ? spec.info : {};
|
|
12728
|
+
const paths = spec.paths && typeof spec.paths === "object" && !Array.isArray(spec.paths) ? spec.paths : {};
|
|
12729
|
+
const pretty = JSON.stringify(spec, null, 2);
|
|
12730
|
+
const truncated = pretty.length > SPEC_PREVIEW_MAX_BYTES;
|
|
12731
|
+
detail.specSummary = {
|
|
12732
|
+
...typeof info.title === "string" ? { title: info.title } : {},
|
|
12733
|
+
...typeof info.version === "string" ? { version: info.version } : {},
|
|
12734
|
+
pathCount: Object.keys(paths).length
|
|
12735
|
+
};
|
|
12736
|
+
detail.specPreview = {
|
|
12737
|
+
json: truncated ? pretty.slice(0, SPEC_PREVIEW_MAX_BYTES) : pretty,
|
|
12738
|
+
truncated
|
|
12739
|
+
};
|
|
12740
|
+
} catch {
|
|
12741
|
+
detail.specPreview = {
|
|
12742
|
+
json: connection.openapiSpecJson.slice(0, SPEC_PREVIEW_MAX_BYTES),
|
|
12743
|
+
truncated: connection.openapiSpecJson.length > SPEC_PREVIEW_MAX_BYTES
|
|
12744
|
+
};
|
|
12745
|
+
}
|
|
12746
|
+
}
|
|
12747
|
+
return detail;
|
|
12748
|
+
}
|
|
12749
|
+
function decorateConnections(connections) {
|
|
12750
|
+
const bindings = new Map(listRelationalCredentialBindings({ includeInactive: true }).map((binding) => [
|
|
12751
|
+
binding.id,
|
|
12752
|
+
binding
|
|
12753
|
+
]));
|
|
12754
|
+
return connections.map((connection) => {
|
|
12755
|
+
const {
|
|
12756
|
+
openapiSpecJson: _openapiSpecJson,
|
|
12757
|
+
generatedTypes: _generatedTypes,
|
|
12758
|
+
generatedRuntimeJson: _generatedRuntimeJson,
|
|
12759
|
+
...safeConnection
|
|
12760
|
+
} = connection;
|
|
12761
|
+
return {
|
|
12762
|
+
...safeConnection,
|
|
12763
|
+
...runtimeCounts(connection),
|
|
12764
|
+
credentialBinding: bindingSummary(connection.credentialBindingId ? bindings.get(connection.credentialBindingId) : undefined)
|
|
12765
|
+
};
|
|
12766
|
+
});
|
|
12767
|
+
}
|
|
12768
|
+
function listConnections(query) {
|
|
12769
|
+
const connections = listScriptConnections({
|
|
12770
|
+
includeDisabled: true,
|
|
12771
|
+
allScopes: true,
|
|
12772
|
+
kind: query.kind
|
|
12773
|
+
}).filter((connection) => {
|
|
12774
|
+
if (query.scope && connection.scope !== query.scope)
|
|
12775
|
+
return false;
|
|
12776
|
+
if (query.scopeId && connection.scopeId !== query.scopeId)
|
|
12777
|
+
return false;
|
|
12778
|
+
return true;
|
|
12779
|
+
});
|
|
12780
|
+
return decorateConnections(connections);
|
|
12781
|
+
}
|
|
12782
|
+
function connectionScopeId(scope, scopeId, subject = "connections") {
|
|
12783
|
+
return resolveScopedResourceId(scope, scopeId, subject);
|
|
12784
|
+
}
|
|
12785
|
+
function validateCredentialTemplate(input) {
|
|
12786
|
+
if (input.requireTemplate && !input.headerTemplate && !input.queryTemplate) {
|
|
12787
|
+
throw new Error("At least one of headerTemplate or queryTemplate is required.");
|
|
12788
|
+
}
|
|
12789
|
+
const placeholder = placeholderForConfigKey(input.configKey);
|
|
12790
|
+
if (input.headerTemplate && !input.headerTemplate.includes(placeholder)) {
|
|
12791
|
+
throw new Error(`headerTemplate must include ${placeholder}.`);
|
|
12792
|
+
}
|
|
12793
|
+
if (input.queryTemplate && !input.queryTemplate.includes(placeholder)) {
|
|
12794
|
+
throw new Error(`queryTemplate must include ${placeholder}.`);
|
|
12795
|
+
}
|
|
12796
|
+
}
|
|
12797
|
+
function maybeCreateInlineBinding(data, resolvedScope, resolvedScopeId) {
|
|
12798
|
+
if (data.credentialBindingId || !data.configKey)
|
|
12799
|
+
return data.credentialBindingId ?? null;
|
|
12800
|
+
const scope = resolvedScope ?? data.scope ?? "global";
|
|
12801
|
+
const scopeId = resolvedScopeId !== undefined ? resolvedScopeId : connectionScopeId(scope, data.scopeId, "bindings");
|
|
12802
|
+
const allowedHosts = data.allowedHosts ?? ("baseUrl" in data ? [new URL(data.baseUrl).hostname] : []);
|
|
12803
|
+
const authKind = data.authKind ?? "config";
|
|
12804
|
+
const placeholder = placeholderForConfigKey(data.configKey);
|
|
12805
|
+
const headerTemplate = data.headerTemplate ?? (data.queryTemplate ? undefined : `Authorization: Bearer ${placeholder}`);
|
|
12806
|
+
validateCredentialTemplate({
|
|
12807
|
+
configKey: data.configKey,
|
|
12808
|
+
headerTemplate,
|
|
12809
|
+
queryTemplate: data.queryTemplate
|
|
12810
|
+
});
|
|
12811
|
+
if (authKind === "oauth" && !data.oauthProvider) {
|
|
12812
|
+
throw new Error("oauthProvider is required for oauth credential bindings.");
|
|
12813
|
+
}
|
|
12814
|
+
return upsertCredentialBinding({
|
|
12815
|
+
configKey: data.configKey,
|
|
12816
|
+
allowedHosts,
|
|
12817
|
+
headerTemplate,
|
|
12818
|
+
queryTemplate: data.queryTemplate,
|
|
12819
|
+
scope,
|
|
12820
|
+
scopeId,
|
|
12821
|
+
active: true,
|
|
12822
|
+
authKind,
|
|
12823
|
+
oauthProvider: data.oauthProvider ?? null
|
|
12824
|
+
}).id;
|
|
12825
|
+
}
|
|
12826
|
+
function parseMetadata(metadata) {
|
|
12827
|
+
try {
|
|
12828
|
+
const parsed = JSON.parse(metadata);
|
|
12829
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
12830
|
+
} catch {
|
|
12831
|
+
return {};
|
|
12832
|
+
}
|
|
12833
|
+
}
|
|
12834
|
+
function parseScopes(scopes) {
|
|
12835
|
+
return scopes.split(",").map((scope) => scope.trim()).filter(Boolean);
|
|
12836
|
+
}
|
|
12837
|
+
function sanitizeOAuthApp(row) {
|
|
12838
|
+
const metadata = parseMetadata(row.metadata);
|
|
12839
|
+
const extraParams = metadata.extraParams && typeof metadata.extraParams === "object" && !Array.isArray(metadata.extraParams) ? Object.fromEntries(Object.entries(metadata.extraParams).filter((entry) => typeof entry[1] === "string")) : undefined;
|
|
12840
|
+
return {
|
|
12841
|
+
id: row.id,
|
|
12842
|
+
provider: row.provider,
|
|
12843
|
+
clientId: row.clientId,
|
|
12844
|
+
authorizeUrl: row.authorizeUrl,
|
|
12845
|
+
tokenUrl: row.tokenUrl,
|
|
12846
|
+
redirectUri: row.redirectUri,
|
|
12847
|
+
scopes: parseScopes(row.scopes),
|
|
12848
|
+
extraParams,
|
|
12849
|
+
tokenAuthStyle: metadata.tokenAuthStyle === "basic" ? "basic" : "body",
|
|
12850
|
+
tokenBodyFormat: metadata.tokenBodyFormat === "json" ? "json" : "form",
|
|
12851
|
+
tokenStatus: getOAuthBindingTokenStatus(row.provider),
|
|
12852
|
+
expiresAt: row.tokenExpiresAt,
|
|
12853
|
+
lastRefreshedAt: normalizeDate(row.tokenUpdatedAt),
|
|
12854
|
+
createdAt: row.createdAt,
|
|
12855
|
+
updatedAt: row.updatedAt
|
|
12856
|
+
};
|
|
12857
|
+
}
|
|
12858
|
+
function listOAuthApps() {
|
|
12859
|
+
const rows = getDb().prepare(`SELECT a.id, a.provider, a.clientId, a.authorizeUrl, a.tokenUrl, a.redirectUri,
|
|
12860
|
+
a.scopes, a.metadata, t.expiresAt AS tokenExpiresAt,
|
|
12861
|
+
t.updatedAt AS tokenUpdatedAt, a.createdAt, a.updatedAt
|
|
12862
|
+
FROM oauth_apps a
|
|
12863
|
+
LEFT JOIN oauth_tokens t ON t.provider = a.provider
|
|
12864
|
+
ORDER BY a.provider ASC`).all();
|
|
12865
|
+
return rows.map(sanitizeOAuthApp);
|
|
12866
|
+
}
|
|
12867
|
+
async function attemptRemoteRevocation(app, accessToken) {
|
|
12868
|
+
const metadata = parseMetadata(app.metadata);
|
|
12869
|
+
const revocationUrl = typeof metadata.revocationUrl === "string" ? metadata.revocationUrl : undefined;
|
|
12870
|
+
if (!revocationUrl)
|
|
12871
|
+
return false;
|
|
12872
|
+
const body = new URLSearchParams({
|
|
12873
|
+
token: accessToken,
|
|
12874
|
+
token_type_hint: "access_token"
|
|
12875
|
+
});
|
|
12876
|
+
const headers2 = {
|
|
12877
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
12878
|
+
};
|
|
12879
|
+
if (metadata.tokenAuthStyle === "basic") {
|
|
12880
|
+
headers2.authorization = `Basic ${Buffer.from(`${app.clientId}:${app.clientSecret}`).toString("base64")}`;
|
|
12881
|
+
} else {
|
|
12882
|
+
body.set("client_id", app.clientId);
|
|
12883
|
+
body.set("client_secret", app.clientSecret);
|
|
12884
|
+
}
|
|
12885
|
+
const controller = new AbortController;
|
|
12886
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
12887
|
+
try {
|
|
12888
|
+
await fetch(revocationUrl, {
|
|
12889
|
+
method: "POST",
|
|
12890
|
+
headers: headers2,
|
|
12891
|
+
body: body.toString(),
|
|
12892
|
+
signal: controller.signal
|
|
12893
|
+
});
|
|
12894
|
+
} catch (err) {
|
|
12895
|
+
console.warn(scrubSecrets(`OAuth token revocation request failed for provider ${app.provider}: ${err instanceof Error ? err.message : String(err)}`));
|
|
12896
|
+
} finally {
|
|
12897
|
+
clearTimeout(timeout);
|
|
12898
|
+
}
|
|
12899
|
+
return true;
|
|
12900
|
+
}
|
|
12901
|
+
function genericOAuthRedirectUri(provider) {
|
|
12902
|
+
return `${getPublicMcpBaseUrl()}/api/oauth/${encodeURIComponent(provider)}/callback`;
|
|
12903
|
+
}
|
|
12904
|
+
function oauthDiscoveryUrls(inputUrl) {
|
|
12905
|
+
const parsed = new URL(inputUrl);
|
|
12906
|
+
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
12907
|
+
const base = `${parsed.origin}${pathname === "/" ? "" : pathname}`;
|
|
12908
|
+
return [
|
|
12909
|
+
`${base}/.well-known/oauth-authorization-server`,
|
|
12910
|
+
`${base}/.well-known/openid-configuration`,
|
|
12911
|
+
parsed.toString()
|
|
12912
|
+
].filter((url, index, urls) => urls.indexOf(url) === index);
|
|
12913
|
+
}
|
|
12914
|
+
async function fetchJsonMetadata(url, signal) {
|
|
12915
|
+
let current = assertUrlSafe(url, publicEndpointSsrfOptions());
|
|
12916
|
+
let response = null;
|
|
12917
|
+
for (let hop = 0;hop <= 5; hop += 1) {
|
|
12918
|
+
response = await fetch(current, {
|
|
12919
|
+
headers: { accept: "application/json" },
|
|
12920
|
+
signal,
|
|
12921
|
+
redirect: "manual"
|
|
12922
|
+
});
|
|
12923
|
+
if (response.status < 300 || response.status >= 400 || response.status === 304)
|
|
12924
|
+
break;
|
|
12925
|
+
const location = response.headers.get("location");
|
|
12926
|
+
if (!location) {
|
|
12927
|
+
throw new Error(`HTTP ${response.status} redirect missing Location header`);
|
|
12928
|
+
}
|
|
12929
|
+
current = assertUrlSafe(new URL(location, current).toString(), publicEndpointSsrfOptions());
|
|
12930
|
+
if (hop === 5)
|
|
12931
|
+
throw new Error("OAuth discovery exceeded 5 redirects.");
|
|
12932
|
+
}
|
|
12933
|
+
if (!response)
|
|
12934
|
+
throw new Error("OAuth discovery failed before receiving a response.");
|
|
12935
|
+
if (!response.ok) {
|
|
12936
|
+
throw new Error(`HTTP ${response.status}`);
|
|
12937
|
+
}
|
|
12938
|
+
const text = await response.text();
|
|
12939
|
+
try {
|
|
12940
|
+
return JSON.parse(text);
|
|
12941
|
+
} catch {
|
|
12942
|
+
throw new Error("Response was not JSON.");
|
|
12943
|
+
}
|
|
12944
|
+
}
|
|
12945
|
+
function extractOAuthDiscovery(metadata) {
|
|
12946
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
12947
|
+
return null;
|
|
12948
|
+
const record = metadata;
|
|
12949
|
+
if (typeof record.authorization_endpoint !== "string" || typeof record.token_endpoint !== "string") {
|
|
12950
|
+
return null;
|
|
12951
|
+
}
|
|
12952
|
+
const scopes = Array.isArray(record.scopes_supported) ? record.scopes_supported.filter((scope) => typeof scope === "string") : [];
|
|
12953
|
+
return {
|
|
12954
|
+
authorizeUrl: record.authorization_endpoint,
|
|
12955
|
+
tokenUrl: record.token_endpoint,
|
|
12956
|
+
scopes
|
|
12957
|
+
};
|
|
12958
|
+
}
|
|
12959
|
+
async function discoverOAuthApp(url) {
|
|
12960
|
+
const controller = new AbortController;
|
|
12961
|
+
const timeout = setTimeout(() => controller.abort(), DISCOVERY_TIMEOUT_MS);
|
|
12962
|
+
const failures = [];
|
|
12963
|
+
try {
|
|
12964
|
+
for (const candidate of oauthDiscoveryUrls(url)) {
|
|
12965
|
+
try {
|
|
12966
|
+
const metadata = await fetchJsonMetadata(candidate, controller.signal);
|
|
12967
|
+
const discovered = extractOAuthDiscovery(metadata);
|
|
12968
|
+
if (discovered) {
|
|
12969
|
+
assertOAuthAppUrlsSafe(discovered);
|
|
12970
|
+
return { ...discovered, sourceUrl: candidate };
|
|
12971
|
+
}
|
|
12972
|
+
failures.push(`${candidate}: missing authorization_endpoint or token_endpoint`);
|
|
12973
|
+
} catch (error) {
|
|
12974
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
12975
|
+
throw new Error("OAuth discovery timed out after 10s.");
|
|
12976
|
+
}
|
|
12977
|
+
if (isUrlSafetyError(error))
|
|
12978
|
+
throw error;
|
|
12979
|
+
failures.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);
|
|
12980
|
+
}
|
|
12981
|
+
}
|
|
12982
|
+
} finally {
|
|
12983
|
+
clearTimeout(timeout);
|
|
12984
|
+
}
|
|
12985
|
+
throw new Error(`OAuth discovery failed. ${failures.join(" ")}`);
|
|
12986
|
+
}
|
|
12987
|
+
function isUrlSafetyError(error) {
|
|
12988
|
+
if (!(error instanceof Error))
|
|
12989
|
+
return false;
|
|
12990
|
+
return error.message.startsWith("Refusing ") || error.message.startsWith("Invalid URL:") || error.message.startsWith("Missing hostname:");
|
|
12991
|
+
}
|
|
12992
|
+
function stringFromCatalogEntry(entry, keys) {
|
|
12993
|
+
for (const key of keys) {
|
|
12994
|
+
const value = entry[key];
|
|
12995
|
+
if (typeof value === "string")
|
|
12996
|
+
return value;
|
|
12997
|
+
}
|
|
12998
|
+
return "";
|
|
12999
|
+
}
|
|
13000
|
+
function stringArrayFromCatalogEntry(entry, key) {
|
|
13001
|
+
const value = entry[key];
|
|
13002
|
+
if (!Array.isArray(value))
|
|
13003
|
+
return [];
|
|
13004
|
+
return value.filter((item) => typeof item === "string");
|
|
13005
|
+
}
|
|
13006
|
+
function normalizeCatalogEntry(entry) {
|
|
13007
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
13008
|
+
return null;
|
|
13009
|
+
const record = entry;
|
|
13010
|
+
const kind = stringFromCatalogEntry(record, ["kind", "type"]);
|
|
13011
|
+
if (!kind || kind === "cli")
|
|
13012
|
+
return null;
|
|
13013
|
+
const slug = stringFromCatalogEntry(record, ["slug", "id", "name"]);
|
|
13014
|
+
const name = stringFromCatalogEntry(record, ["name", "title"]) || slug;
|
|
13015
|
+
const domain = stringFromCatalogEntry(record, ["domain", "hostname"]);
|
|
13016
|
+
return {
|
|
13017
|
+
id: stringFromCatalogEntry(record, ["id", "slug"]) || slug || name,
|
|
13018
|
+
kind,
|
|
13019
|
+
slug,
|
|
13020
|
+
name,
|
|
13021
|
+
description: stringFromCatalogEntry(record, ["description", "summary"]),
|
|
13022
|
+
url: stringFromCatalogEntry(record, ["url", "homepage", "baseUrl"]),
|
|
13023
|
+
icon: stringFromCatalogEntry(record, ["icon", "logo"]) || null,
|
|
13024
|
+
domain,
|
|
13025
|
+
categories: stringArrayFromCatalogEntry(record, "categories"),
|
|
13026
|
+
feeds: stringArrayFromCatalogEntry(record, "feeds")
|
|
13027
|
+
};
|
|
13028
|
+
}
|
|
13029
|
+
function catalogEntriesFromPayload(payload) {
|
|
13030
|
+
if (Array.isArray(payload))
|
|
13031
|
+
return payload;
|
|
13032
|
+
if (!payload || typeof payload !== "object")
|
|
13033
|
+
return [];
|
|
13034
|
+
const record = payload;
|
|
13035
|
+
for (const key of ["entries", "integrations", "data", "items"]) {
|
|
13036
|
+
if (Array.isArray(record[key]))
|
|
13037
|
+
return record[key];
|
|
13038
|
+
}
|
|
13039
|
+
return [];
|
|
13040
|
+
}
|
|
13041
|
+
async function fetchIntegrationsCatalog() {
|
|
13042
|
+
const now = Date.now();
|
|
13043
|
+
if (integrationsCatalogCache && integrationsCatalogCache.expiresAtMs > now) {
|
|
13044
|
+
return integrationsCatalogCache.payload;
|
|
13045
|
+
}
|
|
13046
|
+
const controller = new AbortController;
|
|
13047
|
+
const timeout = setTimeout(() => controller.abort(), INTEGRATIONS_CATALOG_TIMEOUT_MS);
|
|
13048
|
+
try {
|
|
13049
|
+
const response = await fetch("https://integrations.sh/api.json", {
|
|
13050
|
+
headers: { accept: "application/json" },
|
|
13051
|
+
signal: controller.signal
|
|
13052
|
+
});
|
|
13053
|
+
if (!response.ok) {
|
|
13054
|
+
throw new Error(`integrations.sh returned HTTP ${response.status}`);
|
|
13055
|
+
}
|
|
13056
|
+
const payload = await response.json();
|
|
13057
|
+
const entries = catalogEntriesFromPayload(payload).map(normalizeCatalogEntry).filter((entry) => Boolean(entry));
|
|
13058
|
+
const cachedAt = new Date().toISOString();
|
|
13059
|
+
integrationsCatalogCache = {
|
|
13060
|
+
expiresAtMs: now + INTEGRATIONS_CATALOG_TTL_MS,
|
|
13061
|
+
payload: { entries, cachedAt }
|
|
13062
|
+
};
|
|
13063
|
+
return integrationsCatalogCache.payload;
|
|
13064
|
+
} catch (error) {
|
|
13065
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
13066
|
+
throw new Error("Timed out fetching integrations catalog after 15s.");
|
|
13067
|
+
}
|
|
13068
|
+
throw error;
|
|
13069
|
+
} finally {
|
|
13070
|
+
clearTimeout(timeout);
|
|
13071
|
+
}
|
|
13072
|
+
}
|
|
13073
|
+
function stringOrNull(value) {
|
|
13074
|
+
return typeof value === "string" && value ? value : null;
|
|
13075
|
+
}
|
|
13076
|
+
function trimSurfacePayload(domain, payload) {
|
|
13077
|
+
const record = payload && typeof payload === "object" ? payload : {};
|
|
13078
|
+
const rawSurfaces = Array.isArray(record.surfaces) ? record.surfaces : [];
|
|
13079
|
+
const surfaces = [];
|
|
13080
|
+
const referencedCredentialIds = new Set;
|
|
13081
|
+
for (const raw of rawSurfaces) {
|
|
13082
|
+
if (!raw || typeof raw !== "object")
|
|
13083
|
+
continue;
|
|
13084
|
+
const surface = raw;
|
|
13085
|
+
const type = typeof surface.type === "string" ? surface.type : "";
|
|
13086
|
+
if (type !== "http" && type !== "mcp")
|
|
13087
|
+
continue;
|
|
13088
|
+
const auth = surface.auth && typeof surface.auth === "object" ? surface.auth : {};
|
|
13089
|
+
const entries = Array.isArray(auth.entries) ? auth.entries : [];
|
|
13090
|
+
const credentialIds = [];
|
|
13091
|
+
let mechanics = null;
|
|
13092
|
+
for (const entry of entries) {
|
|
13093
|
+
if (!entry || typeof entry !== "object")
|
|
13094
|
+
continue;
|
|
13095
|
+
const uses = Array.isArray(entry.use) ? entry.use : [];
|
|
13096
|
+
for (const use of uses) {
|
|
13097
|
+
if (!use || typeof use !== "object")
|
|
13098
|
+
continue;
|
|
13099
|
+
const useRecord = use;
|
|
13100
|
+
const id = typeof useRecord.id === "string" ? useRecord.id : "";
|
|
13101
|
+
if (id && !credentialIds.includes(id))
|
|
13102
|
+
credentialIds.push(id);
|
|
13103
|
+
const rawMechanics = useRecord.mechanics && typeof useRecord.mechanics === "object" ? useRecord.mechanics : {};
|
|
13104
|
+
const mechanicsIn = typeof rawMechanics.in === "string" ? rawMechanics.in : "";
|
|
13105
|
+
if (mechanicsIn && (!mechanics || mechanics.in !== "header" && mechanicsIn === "header")) {
|
|
13106
|
+
mechanics = {
|
|
13107
|
+
in: mechanicsIn,
|
|
13108
|
+
headerName: stringOrNull(rawMechanics.headerName),
|
|
13109
|
+
scheme: stringOrNull(rawMechanics.scheme)
|
|
13110
|
+
};
|
|
13111
|
+
}
|
|
13112
|
+
}
|
|
13113
|
+
}
|
|
13114
|
+
for (const id of credentialIds)
|
|
13115
|
+
referencedCredentialIds.add(id);
|
|
13116
|
+
surfaces.push({
|
|
13117
|
+
type,
|
|
13118
|
+
name: typeof surface.name === "string" ? surface.name : "",
|
|
13119
|
+
url: stringOrNull(surface.url),
|
|
13120
|
+
docs: stringOrNull(surface.docs),
|
|
13121
|
+
spec: stringOrNull(surface.spec),
|
|
13122
|
+
auth: { required: auth.status === "required", credentialIds, mechanics }
|
|
13123
|
+
});
|
|
13124
|
+
}
|
|
13125
|
+
const rawCredentials = record.credentials && typeof record.credentials === "object" ? record.credentials : {};
|
|
13126
|
+
const credentials = {};
|
|
13127
|
+
for (const [id, raw] of Object.entries(rawCredentials)) {
|
|
13128
|
+
if (!referencedCredentialIds.has(id) || !raw || typeof raw !== "object")
|
|
13129
|
+
continue;
|
|
13130
|
+
const credential = raw;
|
|
13131
|
+
credentials[id] = {
|
|
13132
|
+
type: typeof credential.type === "string" ? credential.type : "unknown",
|
|
13133
|
+
label: typeof credential.label === "string" ? credential.label : id,
|
|
13134
|
+
generateUrl: stringOrNull(credential.generateUrl),
|
|
13135
|
+
setup: stringOrNull(credential.setup)
|
|
13136
|
+
};
|
|
13137
|
+
}
|
|
13138
|
+
return {
|
|
13139
|
+
domain: typeof record.domain === "string" && record.domain ? record.domain : domain,
|
|
13140
|
+
summary: typeof record.summary === "string" ? record.summary : "",
|
|
13141
|
+
surfaces,
|
|
13142
|
+
credentials
|
|
13143
|
+
};
|
|
13144
|
+
}
|
|
13145
|
+
async function fetchIntegrationsSurface(domain) {
|
|
13146
|
+
const cacheKey2 = domain.toLowerCase();
|
|
13147
|
+
const now = Date.now();
|
|
13148
|
+
const cached = integrationsSurfaceCache.get(cacheKey2);
|
|
13149
|
+
if (cached && cached.expiresAtMs > now)
|
|
13150
|
+
return cached.payload;
|
|
13151
|
+
integrationsSurfaceCache.delete(cacheKey2);
|
|
13152
|
+
const controller = new AbortController;
|
|
13153
|
+
const timeout = setTimeout(() => controller.abort(), INTEGRATIONS_CATALOG_TIMEOUT_MS);
|
|
13154
|
+
try {
|
|
13155
|
+
const response = await fetch(`https://integrations.sh/api/${encodeURIComponent(cacheKey2)}/surface`, { headers: { accept: "application/json" }, signal: controller.signal });
|
|
13156
|
+
if (response.status === 404) {
|
|
13157
|
+
throw new SurfaceNotFoundError(`No integration surface found for ${domain}.`);
|
|
13158
|
+
}
|
|
13159
|
+
if (!response.ok) {
|
|
13160
|
+
throw new Error(`integrations.sh returned HTTP ${response.status}`);
|
|
13161
|
+
}
|
|
13162
|
+
const payload = trimSurfacePayload(cacheKey2, await response.json());
|
|
13163
|
+
if (integrationsSurfaceCache.size >= INTEGRATIONS_SURFACE_CACHE_MAX_ENTRIES) {
|
|
13164
|
+
const oldestKey = integrationsSurfaceCache.keys().next().value;
|
|
13165
|
+
if (oldestKey !== undefined)
|
|
13166
|
+
integrationsSurfaceCache.delete(oldestKey);
|
|
13167
|
+
}
|
|
13168
|
+
integrationsSurfaceCache.set(cacheKey2, {
|
|
13169
|
+
expiresAtMs: now + INTEGRATIONS_CATALOG_TTL_MS,
|
|
13170
|
+
payload
|
|
13171
|
+
});
|
|
13172
|
+
return payload;
|
|
13173
|
+
} catch (error) {
|
|
13174
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
13175
|
+
throw new Error("Timed out fetching integration surface after 15s.");
|
|
13176
|
+
}
|
|
13177
|
+
throw error;
|
|
13178
|
+
} finally {
|
|
13179
|
+
clearTimeout(timeout);
|
|
13180
|
+
}
|
|
13181
|
+
}
|
|
13182
|
+
function deleteOAuthApp(provider) {
|
|
13183
|
+
const existing = getOAuthApp(provider);
|
|
13184
|
+
if (!existing)
|
|
13185
|
+
return false;
|
|
13186
|
+
const tx = getDb().transaction(() => {
|
|
13187
|
+
deleteOAuthTokens(provider);
|
|
13188
|
+
getDb().query("DELETE FROM oauth_apps WHERE provider = ?").run(provider);
|
|
13189
|
+
});
|
|
13190
|
+
tx();
|
|
13191
|
+
return true;
|
|
13192
|
+
}
|
|
13193
|
+
async function refreshHttpConnection(id, userId, agentId) {
|
|
13194
|
+
return refreshScriptConnection(id, userId, agentId);
|
|
13195
|
+
}
|
|
13196
|
+
async function handleScriptConnections(req, res, pathSegments, queryParams, agentId) {
|
|
13197
|
+
if (listConnectionsRoute.match(req.method, pathSegments)) {
|
|
13198
|
+
const parsed = await listConnectionsRoute.parse(req, res, pathSegments, queryParams);
|
|
13199
|
+
if (!parsed)
|
|
13200
|
+
return true;
|
|
13201
|
+
json(res, { connections: listConnections(parsed.query) });
|
|
13202
|
+
return true;
|
|
13203
|
+
}
|
|
13204
|
+
if (getConnectionRoute.match(req.method, pathSegments)) {
|
|
13205
|
+
const parsed = await getConnectionRoute.parse(req, res, pathSegments, queryParams);
|
|
13206
|
+
if (!parsed)
|
|
13207
|
+
return true;
|
|
13208
|
+
const connection = listScriptConnections({ includeDisabled: true, allScopes: true }).find((candidate) => candidate.id === parsed.params.id) ?? null;
|
|
13209
|
+
if (!connection) {
|
|
13210
|
+
jsonError(res, "Script connection not found.", 404);
|
|
13211
|
+
return true;
|
|
13212
|
+
}
|
|
13213
|
+
json(res, { connection: connectionDetail(connection) });
|
|
13214
|
+
return true;
|
|
13215
|
+
}
|
|
13216
|
+
if (upsertConnectionRoute.match(req.method, pathSegments)) {
|
|
13217
|
+
const parsed = await upsertConnectionRoute.parse(req, res, pathSegments, queryParams);
|
|
12353
13218
|
if (!parsed)
|
|
12354
13219
|
return true;
|
|
12355
|
-
|
|
12356
|
-
if (!schedule) {
|
|
12357
|
-
jsonError(res, "Schedule not found", 404);
|
|
12358
|
-
return true;
|
|
12359
|
-
}
|
|
12360
|
-
if (!schedule.enabled) {
|
|
12361
|
-
jsonError(res, "Schedule is disabled", 400);
|
|
13220
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
12362
13221
|
return true;
|
|
12363
|
-
}
|
|
12364
13222
|
try {
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
updateScheduledTask(schedule.id, {
|
|
12369
|
-
lastRunAt: now,
|
|
12370
|
-
nextRunAt: null,
|
|
12371
|
-
enabled: false,
|
|
12372
|
-
lastUpdatedAt: now
|
|
12373
|
-
});
|
|
12374
|
-
} else {
|
|
12375
|
-
updateScheduledTask(schedule.id, {
|
|
12376
|
-
lastRunAt: now,
|
|
12377
|
-
lastUpdatedAt: now
|
|
12378
|
-
});
|
|
13223
|
+
if (parsed.body.kind === "openapi" && Boolean(parsed.body.openapiSpecJson) && Boolean(parsed.body.openapiSpecUrl)) {
|
|
13224
|
+
jsonError(res, "Provide exactly one of openapiSpecJson or openapiSpecUrl.", 400);
|
|
13225
|
+
return true;
|
|
12379
13226
|
}
|
|
12380
|
-
const
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
13227
|
+
const existingConnection = parsed.body.id ? getScriptConnectionById(parsed.body.id) : null;
|
|
13228
|
+
const existingOpenapiConnection = existingConnection?.kind === "openapi" ? existingConnection : null;
|
|
13229
|
+
if (parsed.body.kind === "openapi" && !parsed.body.openapiSpecJson && !parsed.body.openapiSpecUrl && !existingOpenapiConnection) {
|
|
13230
|
+
jsonError(res, "Provide exactly one of openapiSpecJson or openapiSpecUrl.", 400);
|
|
13231
|
+
return true;
|
|
13232
|
+
}
|
|
13233
|
+
const scopeWasProvided = Object.hasOwn(parsed.body, "scope");
|
|
13234
|
+
const scopeIdWasProvided = Object.hasOwn(parsed.body, "scopeId");
|
|
13235
|
+
const enabledWasProvided = Object.hasOwn(parsed.body, "enabled");
|
|
13236
|
+
const scope = (scopeWasProvided ? parsed.body.scope : existingConnection?.scope) ?? "global";
|
|
13237
|
+
const scopeIdInput = scopeIdWasProvided ? parsed.body.scopeId : existingConnection && scope === existingConnection.scope ? existingConnection.scopeId : null;
|
|
13238
|
+
const scopeId = connectionScopeId(scope, scopeIdInput);
|
|
13239
|
+
const enabled = enabledWasProvided ? parsed.body.enabled !== false : existingConnection?.enabled ?? true;
|
|
13240
|
+
const credentialBindingId = maybeCreateInlineBinding(parsed.body, scope, scopeId);
|
|
13241
|
+
const userId = resolveHttpAuditUserId(req, agentId);
|
|
13242
|
+
const openapiSpecUrl = parsed.body.kind === "openapi" ? parsed.body.openapiSpecUrl : undefined;
|
|
13243
|
+
const openapiSpecJson = parsed.body.kind === "openapi" ? parsed.body.openapiSpecJson : undefined;
|
|
13244
|
+
const openapiSpecUrlChanged = parsed.body.kind === "openapi" && Boolean(openapiSpecUrl) && openapiSpecUrl !== existingOpenapiConnection?.openapiSpecSource;
|
|
13245
|
+
const reuseExistingOpenapiSpec = parsed.body.kind === "openapi" && Boolean(existingOpenapiConnection) && openapiSpecJson === undefined && !openapiSpecUrlChanged;
|
|
13246
|
+
const connection = await upsertScriptConnection({
|
|
13247
|
+
id: parsed.body.id,
|
|
13248
|
+
slug: parsed.body.slug,
|
|
13249
|
+
displayName: parsed.body.displayName,
|
|
13250
|
+
kind: parsed.body.kind,
|
|
13251
|
+
scope,
|
|
13252
|
+
scopeId,
|
|
13253
|
+
baseUrl: "baseUrl" in parsed.body ? parsed.body.baseUrl : null,
|
|
13254
|
+
allowedHosts: parsed.body.allowedHosts ?? ("baseUrl" in parsed.body ? [new URL(parsed.body.baseUrl).hostname] : []),
|
|
13255
|
+
credentialBindingId,
|
|
13256
|
+
openapiSpecSourceKind: reuseExistingOpenapiSpec && !openapiSpecUrl ? existingOpenapiConnection?.openapiSpecSourceKind : undefined,
|
|
13257
|
+
openapiSpecSource: reuseExistingOpenapiSpec && !openapiSpecUrl ? existingOpenapiConnection?.openapiSpecSource : undefined,
|
|
13258
|
+
openapiSpecUrl,
|
|
13259
|
+
openapiSpecJson: parsed.body.kind === "openapi" ? openapiSpecJson ?? (reuseExistingOpenapiSpec ? existingOpenapiConnection?.openapiSpecJson ?? undefined : undefined) : undefined,
|
|
13260
|
+
openapiSpecEtag: reuseExistingOpenapiSpec ? existingOpenapiConnection?.openapiSpecEtag : undefined,
|
|
13261
|
+
openapiSpecFetchedAt: reuseExistingOpenapiSpec ? existingOpenapiConnection?.openapiSpecFetchedAt : undefined,
|
|
13262
|
+
mcpServerId: parsed.body.kind === "mcp" ? parsed.body.mcpServerId : null,
|
|
13263
|
+
enabled,
|
|
13264
|
+
agentId,
|
|
13265
|
+
userId
|
|
13266
|
+
});
|
|
13267
|
+
json(res, { connection: decorateConnections([connection])[0] });
|
|
13268
|
+
} catch (err) {
|
|
13269
|
+
jsonError(res, err instanceof Error ? err.message : String(err), 400);
|
|
12384
13270
|
}
|
|
12385
13271
|
return true;
|
|
12386
13272
|
}
|
|
12387
|
-
if (
|
|
12388
|
-
const parsed = await
|
|
13273
|
+
if (refreshConnectionRoute.match(req.method, pathSegments)) {
|
|
13274
|
+
const parsed = await refreshConnectionRoute.parse(req, res, pathSegments, queryParams);
|
|
12389
13275
|
if (!parsed)
|
|
12390
13276
|
return true;
|
|
12391
|
-
|
|
12392
|
-
if (!schedule) {
|
|
12393
|
-
jsonError(res, "Schedule not found", 404);
|
|
13277
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
12394
13278
|
return true;
|
|
13279
|
+
try {
|
|
13280
|
+
const refreshed = await refreshHttpConnection(parsed.params.id, resolveHttpAuditUserId(req, agentId), agentId);
|
|
13281
|
+
if (!refreshed) {
|
|
13282
|
+
jsonError(res, "Script connection not found.", 404);
|
|
13283
|
+
return true;
|
|
13284
|
+
}
|
|
13285
|
+
json(res, { connection: decorateConnections([refreshed])[0] });
|
|
13286
|
+
} catch (err) {
|
|
13287
|
+
jsonError(res, err instanceof Error ? err.message : String(err), 400);
|
|
12395
13288
|
}
|
|
12396
|
-
const userId = resolveHttpAuditUserId(req, myAgentId);
|
|
12397
|
-
const [decorated] = withFavoriteFlags([schedule], { userId, itemType: "schedule" });
|
|
12398
|
-
json(res, decorated ?? schedule);
|
|
12399
13289
|
return true;
|
|
12400
13290
|
}
|
|
12401
|
-
if (
|
|
12402
|
-
const
|
|
12403
|
-
const parsed = await routeHandle.parse(req, res, pathSegments, queryParams);
|
|
13291
|
+
if (setConnectionEnabledRoute.match(req.method, pathSegments)) {
|
|
13292
|
+
const parsed = await setConnectionEnabledRoute.parse(req, res, pathSegments, queryParams);
|
|
12404
13293
|
if (!parsed)
|
|
12405
13294
|
return true;
|
|
12406
|
-
|
|
12407
|
-
if (parsed.body.model !== undefined || parsed.body.modelTier !== undefined) {
|
|
12408
|
-
const normalizedModel = splitLegacyModelAlias({
|
|
12409
|
-
model: parsed.body.model,
|
|
12410
|
-
modelTier: parsed.body.modelTier
|
|
12411
|
-
});
|
|
12412
|
-
if (parsed.body.model !== undefined)
|
|
12413
|
-
body.model = normalizedModel.model ?? null;
|
|
12414
|
-
if (parsed.body.modelTier !== undefined || normalizedModel.modelTier) {
|
|
12415
|
-
body.modelTier = normalizedModel.modelTier ?? null;
|
|
12416
|
-
}
|
|
12417
|
-
}
|
|
12418
|
-
const existing = getScheduledTaskById(parsed.params.id);
|
|
12419
|
-
if (!existing) {
|
|
12420
|
-
jsonError(res, "Schedule not found", 404);
|
|
13295
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
12421
13296
|
return true;
|
|
12422
|
-
|
|
12423
|
-
if (
|
|
12424
|
-
jsonError(res, "
|
|
13297
|
+
const updated = setScriptConnectionEnabled(parsed.params.id, parsed.body.enabled, resolveHttpAuditUserId(req, agentId));
|
|
13298
|
+
if (!updated) {
|
|
13299
|
+
jsonError(res, "Script connection not found.", 404);
|
|
12425
13300
|
return true;
|
|
12426
13301
|
}
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
13302
|
+
json(res, { connection: decorateConnections([updated])[0] });
|
|
13303
|
+
return true;
|
|
13304
|
+
}
|
|
13305
|
+
if (listCredentialBindingsRoute.match(req.method, pathSegments)) {
|
|
13306
|
+
json(res, {
|
|
13307
|
+
bindings: listRelationalCredentialBindings({ includeInactive: true }).map(decorateBinding)
|
|
13308
|
+
});
|
|
13309
|
+
return true;
|
|
13310
|
+
}
|
|
13311
|
+
if (upsertCredentialBindingRoute.match(req.method, pathSegments)) {
|
|
13312
|
+
const parsed = await upsertCredentialBindingRoute.parse(req, res, pathSegments, queryParams);
|
|
13313
|
+
if (!parsed)
|
|
13314
|
+
return true;
|
|
13315
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
13316
|
+
return true;
|
|
13317
|
+
try {
|
|
13318
|
+
const scope = parsed.body.scope ?? "global";
|
|
13319
|
+
const scopeId = connectionScopeId(scope, parsed.body.scopeId, "bindings");
|
|
13320
|
+
if (!parsed.body.headerTemplate && !parsed.body.queryTemplate) {
|
|
13321
|
+
jsonError(res, "At least one of headerTemplate or queryTemplate is required.", 400);
|
|
12434
13322
|
return true;
|
|
12435
13323
|
}
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
try {
|
|
12439
|
-
import_cron_parser.CronExpressionParser.parse(parsed.body.cronExpression);
|
|
12440
|
-
} catch {
|
|
12441
|
-
jsonError(res, "Invalid cron expression", 400);
|
|
13324
|
+
if ((parsed.body.authKind ?? "config") === "oauth" && !parsed.body.oauthProvider) {
|
|
13325
|
+
jsonError(res, "oauthProvider is required for oauth credential bindings.", 400);
|
|
12442
13326
|
return true;
|
|
12443
13327
|
}
|
|
13328
|
+
validateCredentialTemplate({
|
|
13329
|
+
configKey: parsed.body.configKey,
|
|
13330
|
+
headerTemplate: parsed.body.headerTemplate,
|
|
13331
|
+
queryTemplate: parsed.body.queryTemplate,
|
|
13332
|
+
requireTemplate: true
|
|
13333
|
+
});
|
|
13334
|
+
const nextBinding = CredentialBindingSchema.parse({
|
|
13335
|
+
configKey: parsed.body.configKey,
|
|
13336
|
+
allowedHosts: parsed.body.allowedHosts,
|
|
13337
|
+
headerTemplate: parsed.body.headerTemplate,
|
|
13338
|
+
queryTemplate: parsed.body.queryTemplate,
|
|
13339
|
+
scope,
|
|
13340
|
+
scopeId,
|
|
13341
|
+
active: parsed.body.active ?? true,
|
|
13342
|
+
authKind: parsed.body.authKind ?? "config",
|
|
13343
|
+
oauthProvider: parsed.body.oauthProvider
|
|
13344
|
+
});
|
|
13345
|
+
const binding = upsertCredentialBinding({
|
|
13346
|
+
id: parsed.body.id,
|
|
13347
|
+
configKey: nextBinding.configKey,
|
|
13348
|
+
allowedHosts: nextBinding.allowedHosts,
|
|
13349
|
+
headerTemplate: nextBinding.headerTemplate,
|
|
13350
|
+
queryTemplate: nextBinding.queryTemplate,
|
|
13351
|
+
scope: nextBinding.scope,
|
|
13352
|
+
scopeId: nextBinding.scopeId ?? null,
|
|
13353
|
+
active: nextBinding.active,
|
|
13354
|
+
authKind: nextBinding.authKind,
|
|
13355
|
+
oauthProvider: nextBinding.oauthProvider ?? null,
|
|
13356
|
+
userId: resolveHttpAuditUserId(req, agentId)
|
|
13357
|
+
});
|
|
13358
|
+
json(res, { binding: decorateBinding(binding) });
|
|
13359
|
+
} catch (err) {
|
|
13360
|
+
jsonError(res, err instanceof Error ? err.message : String(err), 400);
|
|
12444
13361
|
}
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
13362
|
+
return true;
|
|
13363
|
+
}
|
|
13364
|
+
if (listOAuthAppsRoute.match(req.method, pathSegments)) {
|
|
13365
|
+
json(res, { oauthApps: listOAuthApps() });
|
|
13366
|
+
return true;
|
|
13367
|
+
}
|
|
13368
|
+
if (upsertOAuthAppRoute.match(req.method, pathSegments)) {
|
|
13369
|
+
const parsed = await upsertOAuthAppRoute.parse(req, res, pathSegments, queryParams);
|
|
13370
|
+
if (!parsed)
|
|
13371
|
+
return true;
|
|
13372
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
13373
|
+
return true;
|
|
13374
|
+
try {
|
|
13375
|
+
assertOAuthProviderIsNotReserved(parsed.body.provider);
|
|
13376
|
+
assertOAuthAppUrlsSafe(parsed.body);
|
|
13377
|
+
const existing = getOAuthApp(parsed.body.provider);
|
|
13378
|
+
const clientSecret = parsed.body.clientSecret ?? existing?.clientSecret;
|
|
13379
|
+
if (!clientSecret) {
|
|
13380
|
+
jsonError(res, "clientSecret is required when creating a new OAuth app.", 400);
|
|
12449
13381
|
return true;
|
|
12450
13382
|
}
|
|
13383
|
+
const redirectUri = genericOAuthRedirectUri(parsed.body.provider);
|
|
13384
|
+
upsertOAuthApp(parsed.body.provider, {
|
|
13385
|
+
clientId: parsed.body.clientId,
|
|
13386
|
+
clientSecret,
|
|
13387
|
+
authorizeUrl: parsed.body.authorizeUrl,
|
|
13388
|
+
tokenUrl: parsed.body.tokenUrl,
|
|
13389
|
+
redirectUri,
|
|
13390
|
+
scopes: (parsed.body.scopes ?? []).join(","),
|
|
13391
|
+
...parsed.body.extraParams || parsed.body.tokenAuthStyle || parsed.body.tokenBodyFormat ? {
|
|
13392
|
+
metadata: JSON.stringify({
|
|
13393
|
+
...parsed.body.extraParams ? { extraParams: parsed.body.extraParams } : {},
|
|
13394
|
+
...parsed.body.tokenAuthStyle ? { tokenAuthStyle: parsed.body.tokenAuthStyle } : {},
|
|
13395
|
+
...parsed.body.tokenBodyFormat ? { tokenBodyFormat: parsed.body.tokenBodyFormat } : {}
|
|
13396
|
+
})
|
|
13397
|
+
} : {}
|
|
13398
|
+
});
|
|
13399
|
+
const app = listOAuthApps().find((row) => row.provider === parsed.body.provider);
|
|
13400
|
+
json(res, { oauthApp: app });
|
|
13401
|
+
} catch (err) {
|
|
13402
|
+
jsonError(res, err instanceof Error ? err.message : String(err), 400);
|
|
12451
13403
|
}
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
const
|
|
12456
|
-
if (
|
|
12457
|
-
jsonError(res, "taskTemplate is required when targetType is 'agent-task'.", 400);
|
|
13404
|
+
return true;
|
|
13405
|
+
}
|
|
13406
|
+
if (discoverOAuthAppRoute.match(req.method, pathSegments)) {
|
|
13407
|
+
const parsed = await discoverOAuthAppRoute.parse(req, res, pathSegments, queryParams);
|
|
13408
|
+
if (!parsed)
|
|
12458
13409
|
return true;
|
|
13410
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
13411
|
+
return true;
|
|
13412
|
+
try {
|
|
13413
|
+
const discovered = await discoverOAuthApp(parsed.body.url);
|
|
13414
|
+
json(res, discovered);
|
|
13415
|
+
} catch (err) {
|
|
13416
|
+
jsonError(res, err instanceof Error ? err.message : String(err), 400);
|
|
12459
13417
|
}
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
13418
|
+
return true;
|
|
13419
|
+
}
|
|
13420
|
+
if (deleteOAuthAppRoute.match(req.method, pathSegments)) {
|
|
13421
|
+
const parsed = await deleteOAuthAppRoute.parse(req, res, pathSegments, queryParams);
|
|
13422
|
+
if (!parsed)
|
|
13423
|
+
return true;
|
|
13424
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
13425
|
+
return true;
|
|
13426
|
+
if (!deleteOAuthApp(parsed.params.provider)) {
|
|
13427
|
+
jsonError(res, `OAuth app ${parsed.params.provider} not found.`, 404);
|
|
13428
|
+
return true;
|
|
12469
13429
|
}
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
13430
|
+
json(res, { success: true });
|
|
13431
|
+
return true;
|
|
13432
|
+
}
|
|
13433
|
+
if (authorizeUrlRoute2.match(req.method, pathSegments)) {
|
|
13434
|
+
const parsed = await authorizeUrlRoute2.parse(req, res, pathSegments, queryParams);
|
|
13435
|
+
if (!parsed)
|
|
13436
|
+
return true;
|
|
13437
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
13438
|
+
return true;
|
|
13439
|
+
const config = getOAuthProviderConfig(parsed.params.provider);
|
|
13440
|
+
if (!config) {
|
|
13441
|
+
jsonError(res, `OAuth app ${parsed.params.provider} is not configured.`, 404);
|
|
13442
|
+
return true;
|
|
12479
13443
|
}
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
13444
|
+
const result = await buildAuthorizationUrl(config);
|
|
13445
|
+
json(res, { authorizeUrl: result.url, redirectUri: config.redirectUri });
|
|
13446
|
+
return true;
|
|
13447
|
+
}
|
|
13448
|
+
if (integrationsCatalogRoute.match(req.method, pathSegments)) {
|
|
13449
|
+
try {
|
|
13450
|
+
json(res, await fetchIntegrationsCatalog());
|
|
13451
|
+
} catch (err) {
|
|
13452
|
+
jsonError(res, `Failed to fetch integrations catalog: ${err instanceof Error ? err.message : String(err)}`, 502);
|
|
13453
|
+
}
|
|
13454
|
+
return true;
|
|
13455
|
+
}
|
|
13456
|
+
if (integrationsSurfaceRoute.match(req.method, pathSegments)) {
|
|
13457
|
+
const parsed = await integrationsSurfaceRoute.parse(req, res, pathSegments, queryParams);
|
|
13458
|
+
if (!parsed)
|
|
13459
|
+
return true;
|
|
13460
|
+
try {
|
|
13461
|
+
json(res, await fetchIntegrationsSurface(parsed.params.domain));
|
|
13462
|
+
} catch (err) {
|
|
13463
|
+
if (err instanceof SurfaceNotFoundError) {
|
|
13464
|
+
jsonError(res, err.message, 404);
|
|
12484
13465
|
return true;
|
|
12485
13466
|
}
|
|
13467
|
+
jsonError(res, `Failed to fetch integration surface: ${err instanceof Error ? err.message : String(err)}`, 502);
|
|
12486
13468
|
}
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
}, { cronExpression: parsed.body.cronExpression, intervalMs: parsed.body.intervalMs });
|
|
12500
|
-
const mergedTimezone = parsed.body.timezone !== undefined ? parsed.body.timezone : existing.timezone;
|
|
12501
|
-
if (timing.mergedCron || timing.mergedInterval) {
|
|
12502
|
-
body.nextRunAt = calculateNextRun({
|
|
12503
|
-
cronExpression: timing.mergedCron,
|
|
12504
|
-
intervalMs: timing.mergedInterval,
|
|
12505
|
-
timezone: mergedTimezone
|
|
12506
|
-
});
|
|
12507
|
-
}
|
|
12508
|
-
}
|
|
13469
|
+
return true;
|
|
13470
|
+
}
|
|
13471
|
+
if (disconnectOAuthAppRoute.match(req.method, pathSegments)) {
|
|
13472
|
+
const parsed = await disconnectOAuthAppRoute.parse(req, res, pathSegments, queryParams);
|
|
13473
|
+
if (!parsed)
|
|
13474
|
+
return true;
|
|
13475
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
13476
|
+
return true;
|
|
13477
|
+
const app = getOAuthApp(parsed.params.provider);
|
|
13478
|
+
if (!app) {
|
|
13479
|
+
jsonError(res, `OAuth app ${parsed.params.provider} is not configured.`, 404);
|
|
13480
|
+
return true;
|
|
12509
13481
|
}
|
|
12510
|
-
const
|
|
12511
|
-
if (
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
13482
|
+
const tokens = getOAuthTokens(parsed.params.provider);
|
|
13483
|
+
if (!tokens) {
|
|
13484
|
+
json(res, { disconnected: false, message: "no stored tokens" });
|
|
13485
|
+
return true;
|
|
13486
|
+
}
|
|
13487
|
+
const revocationAttempted = await attemptRemoteRevocation(app, tokens.accessToken);
|
|
13488
|
+
deleteOAuthTokens(parsed.params.provider);
|
|
13489
|
+
json(res, { disconnected: true, revocationAttempted });
|
|
12515
13490
|
return true;
|
|
12516
13491
|
}
|
|
12517
|
-
if (
|
|
12518
|
-
const parsed = await
|
|
13492
|
+
if (refreshOAuthAppTokensRoute.match(req.method, pathSegments)) {
|
|
13493
|
+
const parsed = await refreshOAuthAppTokensRoute.parse(req, res, pathSegments, queryParams);
|
|
12519
13494
|
if (!parsed)
|
|
12520
13495
|
return true;
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
13496
|
+
if (!ensureConnectionAdmin(req, res, agentId))
|
|
13497
|
+
return true;
|
|
13498
|
+
const provider = parsed.params.provider;
|
|
13499
|
+
if (!getOAuthApp(provider)) {
|
|
13500
|
+
jsonError(res, `OAuth app ${provider} is not configured.`, 404);
|
|
12524
13501
|
return true;
|
|
12525
13502
|
}
|
|
12526
|
-
|
|
13503
|
+
const tokens = getOAuthTokens(provider);
|
|
13504
|
+
if (!tokens) {
|
|
13505
|
+
jsonError(res, "Nothing to refresh — authorize first.", 400);
|
|
13506
|
+
return true;
|
|
13507
|
+
}
|
|
13508
|
+
if (!tokens.refreshToken) {
|
|
13509
|
+
jsonError(res, `OAuth app ${provider} does not support refresh (no refresh token stored).`, 400);
|
|
13510
|
+
return true;
|
|
13511
|
+
}
|
|
13512
|
+
try {
|
|
13513
|
+
await forceRefreshTokenOrThrow(provider);
|
|
13514
|
+
} catch (err) {
|
|
13515
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13516
|
+
jsonError(res, scrubSecrets(`Token refresh failed: ${message}`), 502);
|
|
13517
|
+
return true;
|
|
13518
|
+
}
|
|
13519
|
+
json(res, {
|
|
13520
|
+
refreshed: true,
|
|
13521
|
+
tokenStatus: getOAuthBindingTokenStatus(provider),
|
|
13522
|
+
expiresAt: getOAuthTokens(provider)?.expiresAt ?? null
|
|
13523
|
+
});
|
|
12527
13524
|
return true;
|
|
12528
13525
|
}
|
|
12529
13526
|
return false;
|
|
@@ -12579,7 +13576,7 @@ function lintWorkflowLabels(source) {
|
|
|
12579
13576
|
init_types();
|
|
12580
13577
|
var DEFAULT_SCRIPT_RUN_CONCURRENCY_CAP = 10;
|
|
12581
13578
|
var runIdParamsSchema = exports_external.object({ runId: exports_external.string().uuid() });
|
|
12582
|
-
var
|
|
13579
|
+
var idParamsSchema2 = exports_external.object({ id: exports_external.string().uuid() });
|
|
12583
13580
|
var stepParamsSchema = exports_external.object({
|
|
12584
13581
|
runId: exports_external.string().uuid(),
|
|
12585
13582
|
stepKey: exports_external.string().min(1)
|
|
@@ -12663,7 +13660,7 @@ var getScriptRunRoute = route({
|
|
|
12663
13660
|
operationId: "script_runs_get",
|
|
12664
13661
|
summary: "Get a script workflow run with journal",
|
|
12665
13662
|
tags: ["Script Runs"],
|
|
12666
|
-
params:
|
|
13663
|
+
params: idParamsSchema2,
|
|
12667
13664
|
responses: {
|
|
12668
13665
|
200: { description: "Script run detail" },
|
|
12669
13666
|
404: { description: "Script run not found" }
|
|
@@ -12676,7 +13673,7 @@ var deleteScriptRunRoute = route({
|
|
|
12676
13673
|
operationId: "script_runs_cancel",
|
|
12677
13674
|
summary: "Cancel a script workflow run",
|
|
12678
13675
|
tags: ["Script Runs"],
|
|
12679
|
-
params:
|
|
13676
|
+
params: idParamsSchema2,
|
|
12680
13677
|
responses: {
|
|
12681
13678
|
204: { description: "Script run cancelled, or already terminal" },
|
|
12682
13679
|
404: { description: "Script run not found" }
|
|
@@ -13089,7 +14086,7 @@ var searchBodySchema = exports_external.object({
|
|
|
13089
14086
|
var nameParamsSchema = exports_external.object({ name: scriptNameSchema });
|
|
13090
14087
|
var scopeQuerySchema = exports_external.object({ scope: ScriptScopeSchema.default("agent") });
|
|
13091
14088
|
var optionalScopeQuerySchema = exports_external.object({ scope: ScriptScopeSchema.optional() });
|
|
13092
|
-
var
|
|
14089
|
+
var idParamsSchema3 = exports_external.object({ id: exports_external.string().uuid() });
|
|
13093
14090
|
var listScriptsQuerySchema = exports_external.object({
|
|
13094
14091
|
scope: ScriptScopeSchema.optional(),
|
|
13095
14092
|
includeScratch: exports_external.enum(["true", "false"]).optional()
|
|
@@ -13138,7 +14135,8 @@ var searchRoute = route({
|
|
|
13138
14135
|
responses: {
|
|
13139
14136
|
200: { description: "Matching scripts" },
|
|
13140
14137
|
400: { description: "Validation error" }
|
|
13141
|
-
}
|
|
14138
|
+
},
|
|
14139
|
+
rbac: { permission: "script.search" }
|
|
13142
14140
|
});
|
|
13143
14141
|
var deleteRoute = route({
|
|
13144
14142
|
method: "delete",
|
|
@@ -13204,7 +14202,7 @@ var getScriptByIdRoute = route({
|
|
|
13204
14202
|
summary: "Get a saved script by id",
|
|
13205
14203
|
description: "Dashboard read: full record including source and parsed signature.",
|
|
13206
14204
|
tags: ["Scripts"],
|
|
13207
|
-
params:
|
|
14205
|
+
params: idParamsSchema3,
|
|
13208
14206
|
responses: {
|
|
13209
14207
|
200: { description: "Script detail" },
|
|
13210
14208
|
404: { description: "Script not found" }
|
|
@@ -13218,7 +14216,7 @@ var listVersionsRoute = route({
|
|
|
13218
14216
|
summary: "List versions of a saved script",
|
|
13219
14217
|
description: "Dashboard read: version history, newest first.",
|
|
13220
14218
|
tags: ["Scripts"],
|
|
13221
|
-
params:
|
|
14219
|
+
params: idParamsSchema3,
|
|
13222
14220
|
responses: {
|
|
13223
14221
|
200: { description: "Script versions" },
|
|
13224
14222
|
404: { description: "Script not found" }
|
|
@@ -13245,13 +14243,14 @@ var createScriptApiRoute = route({
|
|
|
13245
14243
|
summary: "Expose a script as an external HTTP API endpoint",
|
|
13246
14244
|
description: "Returns the endpoint plus the plaintext bearer token (when authMode is 'bearer').",
|
|
13247
14245
|
tags: ["Scripts"],
|
|
13248
|
-
params:
|
|
14246
|
+
params: idParamsSchema3,
|
|
13249
14247
|
body: createScriptApiBodySchema,
|
|
13250
14248
|
responses: {
|
|
13251
14249
|
201: { description: "Endpoint created" },
|
|
13252
14250
|
400: { description: "Validation error or script has no owning agent" },
|
|
13253
14251
|
404: { description: "Script not found" }
|
|
13254
|
-
}
|
|
14252
|
+
},
|
|
14253
|
+
rbac: { permission: "script.api.create" }
|
|
13255
14254
|
});
|
|
13256
14255
|
var listScriptApisRoute = route({
|
|
13257
14256
|
method: "get",
|
|
@@ -13260,7 +14259,7 @@ var listScriptApisRoute = route({
|
|
|
13260
14259
|
operationId: "scripts_api_list",
|
|
13261
14260
|
summary: "List external API endpoints for a script",
|
|
13262
14261
|
tags: ["Scripts"],
|
|
13263
|
-
params:
|
|
14262
|
+
params: idParamsSchema3,
|
|
13264
14263
|
responses: {
|
|
13265
14264
|
200: { description: "Endpoints (without secrets)" },
|
|
13266
14265
|
404: { description: "Script not found" }
|
|
@@ -13277,7 +14276,8 @@ var revealScriptApiSecretRoute = route({
|
|
|
13277
14276
|
responses: {
|
|
13278
14277
|
200: { description: "Decrypted token (null when authMode is 'none')" },
|
|
13279
14278
|
404: { description: "Endpoint not found" }
|
|
13280
|
-
}
|
|
14279
|
+
},
|
|
14280
|
+
rbac: { permission: "script.api.read.secrets" }
|
|
13281
14281
|
});
|
|
13282
14282
|
var patchScriptApiRoute = route({
|
|
13283
14283
|
method: "patch",
|
|
@@ -13291,7 +14291,8 @@ var patchScriptApiRoute = route({
|
|
|
13291
14291
|
responses: {
|
|
13292
14292
|
200: { description: "Updated endpoint" },
|
|
13293
14293
|
404: { description: "Endpoint not found" }
|
|
13294
|
-
}
|
|
14294
|
+
},
|
|
14295
|
+
rbac: { permission: "script.api.update" }
|
|
13295
14296
|
});
|
|
13296
14297
|
var rotateScriptApiRoute = route({
|
|
13297
14298
|
method: "post",
|
|
@@ -13305,7 +14306,8 @@ var rotateScriptApiRoute = route({
|
|
|
13305
14306
|
200: { description: "Endpoint with new plaintext token" },
|
|
13306
14307
|
400: { description: "Endpoint uses 'none' auth — nothing to rotate" },
|
|
13307
14308
|
404: { description: "Endpoint not found" }
|
|
13308
|
-
}
|
|
14309
|
+
},
|
|
14310
|
+
rbac: { permission: "script.api.rotate" }
|
|
13309
14311
|
});
|
|
13310
14312
|
var deleteScriptApiRoute = route({
|
|
13311
14313
|
method: "delete",
|
|
@@ -13318,7 +14320,8 @@ var deleteScriptApiRoute = route({
|
|
|
13318
14320
|
responses: {
|
|
13319
14321
|
200: { description: "Deleted" },
|
|
13320
14322
|
404: { description: "Endpoint not found" }
|
|
13321
|
-
}
|
|
14323
|
+
},
|
|
14324
|
+
rbac: { permission: "script.api.delete" }
|
|
13322
14325
|
});
|
|
13323
14326
|
function requireAgent2(res, agentId) {
|
|
13324
14327
|
if (!agentId) {
|
|
@@ -13454,8 +14457,9 @@ async function handleScripts(req, res, pathSegments, queryParams, agentId) {
|
|
|
13454
14457
|
args: parsed.body.args,
|
|
13455
14458
|
fsMode,
|
|
13456
14459
|
agentId: agent.id,
|
|
13457
|
-
egressSecrets: buildScriptCredentialBindings({ agentId: agent.id }),
|
|
13458
|
-
apiConnections: getScriptApiConnectionDescriptors({ agentId: agent.id })
|
|
14460
|
+
egressSecrets: await buildScriptCredentialBindings({ agentId: agent.id }),
|
|
14461
|
+
apiConnections: getScriptApiConnectionDescriptors({ agentId: agent.id }),
|
|
14462
|
+
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: agent.id })
|
|
13459
14463
|
});
|
|
13460
14464
|
let kvSaved;
|
|
13461
14465
|
if (parsed.body.idempotencyKey && !output.error && output.exitCode === 0) {
|
|
@@ -13578,9 +14582,11 @@ async function handleScripts(req, res, pathSegments, queryParams, agentId) {
|
|
|
13578
14582
|
return true;
|
|
13579
14583
|
}
|
|
13580
14584
|
if (typeDefsRoute.match(req.method, pathSegments)) {
|
|
14585
|
+
const apiTypes = getScriptApiTypes();
|
|
14586
|
+
const mcpTypes = getScriptMcpTypes();
|
|
13581
14587
|
json(res, {
|
|
13582
|
-
sdkTypes: scriptSdkTypesWithGeneratedApis(
|
|
13583
|
-
stdlibTypes:
|
|
14588
|
+
sdkTypes: scriptSdkTypesWithGeneratedApis(apiTypes, mcpTypes),
|
|
14589
|
+
stdlibTypes: scriptStdlibTypesWithGeneratedApis(apiTypes, mcpTypes)
|
|
13584
14590
|
});
|
|
13585
14591
|
return true;
|
|
13586
14592
|
}
|
|
@@ -13627,8 +14633,8 @@ async function handleScripts(req, res, pathSegments, queryParams, agentId) {
|
|
|
13627
14633
|
json(res, {
|
|
13628
14634
|
signature: JSON.parse(script.signatureJson),
|
|
13629
14635
|
argsJsonSchema: script.argsJsonSchema ? JSON.parse(script.argsJsonSchema) : null,
|
|
13630
|
-
sdkTypes: scriptSdkTypesWithGeneratedApis(getScriptApiTypes({ agentId: agent.id })),
|
|
13631
|
-
stdlibTypes:
|
|
14636
|
+
sdkTypes: scriptSdkTypesWithGeneratedApis(getScriptApiTypes({ agentId: agent.id }), getScriptMcpTypes({ agentId: agent.id })),
|
|
14637
|
+
stdlibTypes: scriptStdlibTypesWithGeneratedApis(getScriptApiTypes({ agentId: agent.id }), getScriptMcpTypes({ agentId: agent.id }))
|
|
13632
14638
|
});
|
|
13633
14639
|
return true;
|
|
13634
14640
|
}
|
|
@@ -14202,6 +15208,7 @@ var bulkUpsertSkillFilesRoute = route({
|
|
|
14202
15208
|
summary: "Bulk upsert bundled files for a skill",
|
|
14203
15209
|
tags: ["Skills"],
|
|
14204
15210
|
auth: { apiKey: true },
|
|
15211
|
+
rbac: { permission: "skill.update.any" },
|
|
14205
15212
|
params: exports_external.object({ id: exports_external.string() }),
|
|
14206
15213
|
body: exports_external.object({
|
|
14207
15214
|
files: exports_external.array(skillFileWithPathSchema).max(100)
|
|
@@ -14234,6 +15241,7 @@ var upsertSkillFileRoute = route({
|
|
|
14234
15241
|
summary: "Upsert a bundled skill file",
|
|
14235
15242
|
tags: ["Skills"],
|
|
14236
15243
|
auth: { apiKey: true },
|
|
15244
|
+
rbac: { permission: "skill.update.any" },
|
|
14237
15245
|
params: exports_external.object({ id: exports_external.string(), path: exports_external.string() }),
|
|
14238
15246
|
body: skillFileBodySchema,
|
|
14239
15247
|
responses: {
|
|
@@ -14250,6 +15258,7 @@ var deleteSkillFileRoute = route({
|
|
|
14250
15258
|
summary: "Delete a bundled skill file",
|
|
14251
15259
|
tags: ["Skills"],
|
|
14252
15260
|
auth: { apiKey: true },
|
|
15261
|
+
rbac: { permission: "skill.update.any" },
|
|
14253
15262
|
params: exports_external.object({ id: exports_external.string(), path: exports_external.string() }),
|
|
14254
15263
|
responses: {
|
|
14255
15264
|
200: { description: "Skill file deleted" },
|
|
@@ -14263,6 +15272,7 @@ var createSkillRoute = route({
|
|
|
14263
15272
|
summary: "Create a new skill",
|
|
14264
15273
|
tags: ["Skills"],
|
|
14265
15274
|
auth: { apiKey: true },
|
|
15275
|
+
rbac: { permission: "skill.create.swarm" },
|
|
14266
15276
|
body: exports_external.object({
|
|
14267
15277
|
content: exports_external.string().min(1),
|
|
14268
15278
|
type: exports_external.string().optional(),
|
|
@@ -14282,6 +15292,7 @@ var updateSkillRoute = route({
|
|
|
14282
15292
|
summary: "Update a skill",
|
|
14283
15293
|
tags: ["Skills"],
|
|
14284
15294
|
auth: { apiKey: true },
|
|
15295
|
+
rbac: { permission: "skill.update.any" },
|
|
14285
15296
|
params: exports_external.object({ id: exports_external.string() }),
|
|
14286
15297
|
body: exports_external.record(exports_external.string(), exports_external.unknown()),
|
|
14287
15298
|
responses: {
|
|
@@ -14297,6 +15308,7 @@ var deleteSkillRoute = route({
|
|
|
14297
15308
|
summary: "Delete a skill",
|
|
14298
15309
|
tags: ["Skills"],
|
|
14299
15310
|
auth: { apiKey: true },
|
|
15311
|
+
rbac: { permission: "skill.delete.any" },
|
|
14300
15312
|
params: exports_external.object({ id: exports_external.string() }),
|
|
14301
15313
|
responses: {
|
|
14302
15314
|
200: { description: "Skill deleted" },
|
|
@@ -14311,6 +15323,7 @@ var installSkillRoute = route({
|
|
|
14311
15323
|
summary: "Install skill for an agent",
|
|
14312
15324
|
tags: ["Skills"],
|
|
14313
15325
|
auth: { apiKey: true },
|
|
15326
|
+
rbac: { permission: "skill.install.any" },
|
|
14314
15327
|
params: exports_external.object({ id: exports_external.string() }),
|
|
14315
15328
|
body: exports_external.object({
|
|
14316
15329
|
agentId: exports_external.string()
|
|
@@ -14327,6 +15340,7 @@ var uninstallSkillRoute = route({
|
|
|
14327
15340
|
summary: "Uninstall skill for an agent",
|
|
14328
15341
|
tags: ["Skills"],
|
|
14329
15342
|
auth: { apiKey: true },
|
|
15343
|
+
rbac: { permission: "skill.uninstall.any" },
|
|
14330
15344
|
params: exports_external.object({ id: exports_external.string(), agentId: exports_external.string() }),
|
|
14331
15345
|
responses: {
|
|
14332
15346
|
200: { description: "Skill uninstalled" }
|
|
@@ -14339,6 +15353,7 @@ var installRemoteRoute = route({
|
|
|
14339
15353
|
summary: "Install a remote skill from GitHub",
|
|
14340
15354
|
tags: ["Skills"],
|
|
14341
15355
|
auth: { apiKey: true },
|
|
15356
|
+
rbac: { permission: "skill.install.global" },
|
|
14342
15357
|
body: exports_external.object({
|
|
14343
15358
|
sourceRepo: exports_external.string(),
|
|
14344
15359
|
sourcePath: exports_external.string().optional(),
|
|
@@ -14357,6 +15372,7 @@ var syncRemoteRoute = route({
|
|
|
14357
15372
|
summary: "Trigger remote skill sync",
|
|
14358
15373
|
tags: ["Skills"],
|
|
14359
15374
|
auth: { apiKey: true },
|
|
15375
|
+
rbac: { permission: "skill.update.any" },
|
|
14360
15376
|
body: exports_external.object({
|
|
14361
15377
|
skillId: exports_external.string().optional(),
|
|
14362
15378
|
force: exports_external.boolean().optional()
|
|
@@ -14372,6 +15388,7 @@ var syncFilesystemRoute = route({
|
|
|
14372
15388
|
summary: "Sync installed skills to agent filesystem",
|
|
14373
15389
|
tags: ["Skills"],
|
|
14374
15390
|
auth: { apiKey: true, agentId: true },
|
|
15391
|
+
rbac: { ungated: "self-scoped: syncs the caller's own agent FS" },
|
|
14375
15392
|
responses: {
|
|
14376
15393
|
200: { description: "Filesystem sync results" }
|
|
14377
15394
|
}
|
|
@@ -15450,7 +16467,6 @@ async function handleTaskTemplates(req, res, pathSegments, queryParams) {
|
|
|
15450
16467
|
init_zod();
|
|
15451
16468
|
init_db();
|
|
15452
16469
|
init_types();
|
|
15453
|
-
init_request_auth_context();
|
|
15454
16470
|
var listTasks = route({
|
|
15455
16471
|
method: "get",
|
|
15456
16472
|
path: "/api/tasks",
|
|
@@ -15465,7 +16481,9 @@ var listTasks = route({
|
|
|
15465
16481
|
search: exports_external.string().optional(),
|
|
15466
16482
|
includeHeartbeat: exports_external.enum(["true", "false"]).optional(),
|
|
15467
16483
|
createdAfter: exports_external.string().datetime().optional(),
|
|
16484
|
+
createdBefore: exports_external.string().datetime().optional(),
|
|
15468
16485
|
source: exports_external.string().optional(),
|
|
16486
|
+
orderBy: exports_external.enum(["lastUpdatedAt", "createdAt"]).optional(),
|
|
15469
16487
|
limit: exports_external.coerce.number().int().optional(),
|
|
15470
16488
|
offset: exports_external.coerce.number().int().optional(),
|
|
15471
16489
|
fields: exports_external.enum(["full", "slim"]).optional()
|
|
@@ -15714,7 +16732,9 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
|
|
|
15714
16732
|
search: parsed.query.search || undefined,
|
|
15715
16733
|
includeHeartbeat: parsed.query.includeHeartbeat === "true" || undefined,
|
|
15716
16734
|
createdAfter: parsed.query.createdAfter || undefined,
|
|
16735
|
+
createdBefore: parsed.query.createdBefore || undefined,
|
|
15717
16736
|
source,
|
|
16737
|
+
orderBy: parsed.query.orderBy,
|
|
15718
16738
|
limit: parsed.query.limit,
|
|
15719
16739
|
offset: parsed.query.offset
|
|
15720
16740
|
};
|
|
@@ -15727,11 +16747,12 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
|
|
|
15727
16747
|
const parsed = await createTask.parse(req, res, pathSegments, queryParams);
|
|
15728
16748
|
if (!parsed)
|
|
15729
16749
|
return true;
|
|
15730
|
-
|
|
15731
|
-
|
|
15732
|
-
if (
|
|
15733
|
-
|
|
15734
|
-
|
|
16750
|
+
let requestedByUserId = resolveHttpAuditUserId(req, myAgentId) ?? undefined;
|
|
16751
|
+
const trustBodyRequestedByUserId = process.env.TRUST_BODY_REQUESTED_BY_USER_ID === "true";
|
|
16752
|
+
if (trustBodyRequestedByUserId && !requestedByUserId && parsed.body.requestedByUserId) {
|
|
16753
|
+
const candidate = findUserById(parsed.body.requestedByUserId);
|
|
16754
|
+
if (candidate)
|
|
16755
|
+
requestedByUserId = candidate.id;
|
|
15735
16756
|
}
|
|
15736
16757
|
let defaultAgentId = parsed.body.agentId || undefined;
|
|
15737
16758
|
if (!defaultAgentId) {
|
|
@@ -17655,11 +18676,13 @@ function extractText(message) {
|
|
|
17655
18676
|
function buildTaskDescription(payload) {
|
|
17656
18677
|
const message = payload.message ?? {};
|
|
17657
18678
|
const conversation = payload.conversation ?? {};
|
|
18679
|
+
const externalId = normalizeKapsoSender(payload);
|
|
18680
|
+
const contactDisplay = externalId ? renderIdentity(resolveKapsoIdentity(externalId)) : "(unknown user)";
|
|
17658
18681
|
return resolveTemplate("kapso.message.received", {
|
|
17659
18682
|
conversation_id: conversation.id ?? "unknown",
|
|
17660
18683
|
inbound_wamid: message.id ?? "unknown",
|
|
17661
18684
|
sender_phone: message.from ?? conversation.phone_number ?? "unknown",
|
|
17662
|
-
contact_name:
|
|
18685
|
+
contact_name: contactDisplay,
|
|
17663
18686
|
phone_number_id: payload.phone_number_id ?? "unknown",
|
|
17664
18687
|
test_note: payload.test ? `
|
|
17665
18688
|
- test: true (do NOT send a real WhatsApp reply)` : "",
|
|
@@ -17671,13 +18694,22 @@ function normalizeKapsoSender(payload) {
|
|
|
17671
18694
|
const digits = raw.replace(/\D/g, "");
|
|
17672
18695
|
return digits || null;
|
|
17673
18696
|
}
|
|
18697
|
+
function resolveKapsoIdentity(externalId) {
|
|
18698
|
+
const kapsoResolution = resolveIdentity(KAPSO_IDENTITY_KIND, externalId);
|
|
18699
|
+
if (kapsoResolution.status === "resolved")
|
|
18700
|
+
return kapsoResolution;
|
|
18701
|
+
const whatsappResolution = resolveIdentity(WHATSAPP_IDENTITY_KIND, externalId);
|
|
18702
|
+
if (whatsappResolution.status === "resolved")
|
|
18703
|
+
return whatsappResolution;
|
|
18704
|
+
return kapsoResolution;
|
|
18705
|
+
}
|
|
17674
18706
|
function resolveKapsoRequestedByUserId(payload) {
|
|
17675
18707
|
const externalId = normalizeKapsoSender(payload);
|
|
17676
18708
|
if (!externalId)
|
|
17677
18709
|
return;
|
|
17678
|
-
const
|
|
17679
|
-
if (
|
|
17680
|
-
return
|
|
18710
|
+
const resolution = resolveKapsoIdentity(externalId);
|
|
18711
|
+
if (resolution.status === "resolved")
|
|
18712
|
+
return resolution.userId;
|
|
17681
18713
|
recordUnmappedIdentity(KAPSO_IDENTITY_KIND, externalId, {
|
|
17682
18714
|
sampleEventType: "kapso.message.received",
|
|
17683
18715
|
sampleContext: [
|
|
@@ -19030,8 +20062,9 @@ async function handleX(req, res, pathSegments) {
|
|
|
19030
20062
|
fsMode: "none",
|
|
19031
20063
|
agentId: endpoint.agentId,
|
|
19032
20064
|
timeoutMs,
|
|
19033
|
-
egressSecrets: buildScriptCredentialBindings({ agentId: endpoint.agentId }),
|
|
19034
|
-
apiConnections: getScriptApiConnectionDescriptors({ agentId: endpoint.agentId })
|
|
20065
|
+
egressSecrets: await buildScriptCredentialBindings({ agentId: endpoint.agentId }),
|
|
20066
|
+
apiConnections: getScriptApiConnectionDescriptors({ agentId: endpoint.agentId }),
|
|
20067
|
+
mcpConnections: getScriptMcpConnectionDescriptors({ agentId: endpoint.agentId })
|
|
19035
20068
|
});
|
|
19036
20069
|
const ok = output.exitCode === 0 && !output.error && !output.runtimeError;
|
|
19037
20070
|
const error = ok ? null : buildExecutionError(output);
|
|
@@ -19213,6 +20246,8 @@ var httpServer = createHttpServer(async (req, res) => {
|
|
|
19213
20246
|
() => handleMetrics(req, res, pathSegments, queryParams, myAgentId),
|
|
19214
20247
|
() => handleRepos(req, res, pathSegments, queryParams),
|
|
19215
20248
|
() => handleSkills(req, res, pathSegments, queryParams, myAgentId),
|
|
20249
|
+
() => handleScriptConnections(req, res, pathSegments, queryParams, myAgentId),
|
|
20250
|
+
() => handleScriptConnectionProxy(req, res, pathSegments, queryParams, myAgentId),
|
|
19216
20251
|
() => handleScriptRuns(req, res, pathSegments, queryParams, myAgentId),
|
|
19217
20252
|
() => handleScripts(req, res, pathSegments, queryParams, myAgentId),
|
|
19218
20253
|
() => handleX(req, res, pathSegments),
|
|
@@ -19221,6 +20256,7 @@ var httpServer = createHttpServer(async (req, res) => {
|
|
|
19221
20256
|
() => handleMcpOAuth(req, res, pathSegments, queryParams),
|
|
19222
20257
|
() => handleMemory(req, res, pathSegments, myAgentId),
|
|
19223
20258
|
() => handleOAuthLocks(req, res, pathSegments, queryParams),
|
|
20259
|
+
() => handleGenericOAuth(req, res, pathSegments, queryParams),
|
|
19224
20260
|
() => handleCodexOAuthKeepWarm(req, res, pathSegments),
|
|
19225
20261
|
() => handlePagesPublic(req, res, pathSegments, queryParams),
|
|
19226
20262
|
() => handlePageProxy(req, res),
|
|
@@ -19280,14 +20316,14 @@ async function shutdown() {
|
|
|
19280
20316
|
sessionsProcessed: getServerSessionsProcessed()
|
|
19281
20317
|
});
|
|
19282
20318
|
if (hasCapability("scheduling")) {
|
|
19283
|
-
const { stopScheduler } = await import("./index-
|
|
20319
|
+
const { stopScheduler } = await import("./index-9ht8gr56.js");
|
|
19284
20320
|
stopScheduler();
|
|
19285
20321
|
}
|
|
19286
20322
|
stopHeartbeat();
|
|
19287
20323
|
stopScriptRunSupervisor();
|
|
19288
20324
|
await stopSlackApp();
|
|
19289
20325
|
if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
|
|
19290
|
-
const { stopOAuthKeepalive } = await import("./keepalive-
|
|
20326
|
+
const { stopOAuthKeepalive } = await import("./keepalive-3cps6e3e.js");
|
|
19291
20327
|
await stopOAuthKeepalive();
|
|
19292
20328
|
}
|
|
19293
20329
|
stopMcpOAuthPendingGc();
|
|
@@ -19344,15 +20380,23 @@ try {
|
|
|
19344
20380
|
throw err;
|
|
19345
20381
|
}
|
|
19346
20382
|
try {
|
|
19347
|
-
const { seedPricingFromModelsDev } = await import("./seed-pricing-
|
|
20383
|
+
const { seedPricingFromModelsDev } = await import("./seed-pricing-h2kdq4cq.js");
|
|
19348
20384
|
seedPricingFromModelsDev();
|
|
19349
|
-
const { startPricingRefreshLoop } = await import("./pricing-refresh-
|
|
20385
|
+
const { startPricingRefreshLoop } = await import("./pricing-refresh-0hvazmkf.js");
|
|
19350
20386
|
startPricingRefreshLoop();
|
|
19351
20387
|
} catch (err) {
|
|
19352
20388
|
console.error("[startup] Failed to seed pricing rows:", err);
|
|
19353
20389
|
}
|
|
19354
20390
|
try {
|
|
19355
|
-
const {
|
|
20391
|
+
const { ensureRbacSeedsSynced } = await import("./rbac-roles-kfesjy9y.js");
|
|
20392
|
+
ensureRbacSeedsSynced();
|
|
20393
|
+
} catch (err) {
|
|
20394
|
+
console.error("[startup] Failed to sync RBAC seed rows:", err);
|
|
20395
|
+
if (isRbacEnabled())
|
|
20396
|
+
throw err;
|
|
20397
|
+
}
|
|
20398
|
+
try {
|
|
20399
|
+
const { runAllSeeders } = await import("./index-rm85cfdx.js");
|
|
19356
20400
|
await runAllSeeders({ scriptEmbeddingMode: "skip" });
|
|
19357
20401
|
} catch (err) {
|
|
19358
20402
|
console.error("[startup] Failed to seed built-in entities:", err);
|
|
@@ -19388,31 +20432,35 @@ httpServer.listen(port, async () => {
|
|
|
19388
20432
|
initWorkflows();
|
|
19389
20433
|
startScriptRunSupervisor(getMcpBaseUrl());
|
|
19390
20434
|
if (hasCapability("scheduling")) {
|
|
19391
|
-
const { startScheduler } = await import("./index-
|
|
19392
|
-
const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-
|
|
20435
|
+
const { startScheduler } = await import("./index-9ht8gr56.js");
|
|
20436
|
+
const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-9fqaktjr.js");
|
|
19393
20437
|
const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
|
|
19394
20438
|
startScheduler(getExecutorRegistry2(), intervalMs, {
|
|
19395
20439
|
runId: globalState.__runId
|
|
19396
20440
|
});
|
|
19397
20441
|
}
|
|
19398
20442
|
if (process.env.HEARTBEAT_DISABLE !== "true") {
|
|
19399
|
-
const { startHeartbeat } = await import("./index-
|
|
20443
|
+
const { startHeartbeat } = await import("./index-47em3d2n.js");
|
|
19400
20444
|
const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
|
|
19401
20445
|
startHeartbeat(heartbeatMs);
|
|
19402
20446
|
}
|
|
19403
20447
|
if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
|
|
19404
|
-
const { startOAuthKeepalive } = await import("./keepalive-
|
|
20448
|
+
const { startOAuthKeepalive } = await import("./keepalive-3cps6e3e.js");
|
|
19405
20449
|
startOAuthKeepalive();
|
|
19406
20450
|
}
|
|
20451
|
+
if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
|
|
20452
|
+
const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-sycqtbph.js");
|
|
20453
|
+
startOAuthRefreshSweep();
|
|
20454
|
+
}
|
|
19407
20455
|
startMcpOAuthPendingGc();
|
|
19408
20456
|
startMemoryGc();
|
|
19409
|
-
import("./boot-reembed-
|
|
20457
|
+
import("./boot-reembed-qata34xa.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
|
|
19410
20458
|
console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
|
|
19411
20459
|
});
|
|
19412
|
-
import("./boot-reembed-
|
|
20460
|
+
import("./boot-reembed-4jpxhb25.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
|
|
19413
20461
|
console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
|
|
19414
20462
|
});
|
|
19415
|
-
import("./boot-scrub-logs-
|
|
20463
|
+
import("./boot-scrub-logs-tv48nn3z.js").then(({ runBootScrubLogs }) => runBootScrubLogs()).catch((err) => {
|
|
19416
20464
|
console.error("[boot-scrub-logs] startup scrub failed (non-fatal):", err);
|
|
19417
20465
|
});
|
|
19418
20466
|
}).on("error", (err) => {
|