@sentry/junior 0.203.0 → 0.204.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.
Files changed (174) hide show
  1. package/dist/agent-CMyTD4DD.js +17509 -0
  2. package/dist/agent-hooks-Bbcp9e33.js +4040 -0
  3. package/dist/agent-runner-DNb1A49M.js +678 -0
  4. package/dist/api/schema.d.ts +2728 -2639
  5. package/dist/api/schema.js +4 -169
  6. package/dist/api.d.ts +23 -17
  7. package/dist/api.js +3881 -5326
  8. package/dist/app-B1TOqBI8.d.ts +246 -0
  9. package/dist/app.d.ts +4 -520
  10. package/dist/app.js +9522 -11875
  11. package/dist/auth-pause-state-DosEalIG.js +168 -0
  12. package/dist/catalog-runtime-BReos-D4.js +7 -0
  13. package/dist/checkpoint-CNrPyRJN.js +2450 -0
  14. package/dist/cli/chat.d.ts +20 -0
  15. package/dist/cli/chat.js +229 -336
  16. package/dist/cli/check.d.ts +8 -0
  17. package/dist/cli/check.js +513 -806
  18. package/dist/cli/env.d.ts +13 -0
  19. package/dist/cli/env.js +63 -12
  20. package/dist/cli/init.d.ts +3 -0
  21. package/dist/cli/init.js +90 -159
  22. package/dist/cli/main.d.ts +6 -0
  23. package/dist/cli/main.js +43 -64
  24. package/dist/cli/plugins.d.ts +13 -0
  25. package/dist/cli/plugins.js +148 -227
  26. package/dist/cli/run.d.ts +16 -0
  27. package/dist/cli/run.js +52 -8
  28. package/dist/cli/snapshot-create.d.ts +4 -0
  29. package/dist/cli/snapshot-create.js +71 -125
  30. package/dist/cli/upgrade.d.ts +15 -0
  31. package/dist/cli/upgrade.js +232 -335
  32. package/dist/config-nGCW1OTG.js +941 -0
  33. package/dist/context-B45_WqVX.js +298 -0
  34. package/dist/conversation-privacy-B4voFRSm.js +182 -0
  35. package/dist/conversation-work-C6y3T5Hx.js +51 -0
  36. package/dist/credential-sync-kXr1hvO1.js +2660 -0
  37. package/dist/db-BeyXKJOI.d.ts +5 -0
  38. package/dist/destinations-BeBiJeEM.js +29 -0
  39. package/dist/execution-stats-DWfSihcH.js +373 -0
  40. package/dist/executor-DcMSlI5c.js +721 -0
  41. package/dist/experimental-CIXf7Uye.js +46 -0
  42. package/dist/history-ClE4toxe.d.ts +32 -0
  43. package/dist/history-CqTuSAdI.js +441 -0
  44. package/dist/instrumentation.d.ts +3 -3
  45. package/dist/instrumentation.js +130 -176
  46. package/dist/job-queue-Drn_LXlM.js +63 -0
  47. package/dist/logging-BzAk1S_s.js +1210 -0
  48. package/dist/model-profile-CZmVoGdX.js +49 -0
  49. package/dist/nitro.d.ts +31 -36
  50. package/dist/nitro.js +275 -456
  51. package/dist/oauth-callback-server-JiftokvA.js +130 -0
  52. package/dist/oauth-relay-CdCnu80W.js +62 -0
  53. package/dist/package-discovery-7a9YqLhE.js +455 -0
  54. package/dist/pending-auth-CWmSdZVg.js +185 -0
  55. package/dist/plugin-module-BKGOXca2.js +81 -0
  56. package/dist/plugins-BI9A1vKA.d.ts +95 -0
  57. package/dist/plugins-egKWjgue.js +99 -0
  58. package/dist/private-trace-result-BLvBjy_e.js +16 -0
  59. package/dist/profile-CrT5nXbw.js +861 -0
  60. package/dist/registry-DH1xUo2N.js +1419 -0
  61. package/dist/reporting-schema-DosTN__Q.js +105 -0
  62. package/dist/resolve-CZHnfxaS.js +546 -0
  63. package/dist/rolldown-runtime-B4FMCO8f.js +28 -0
  64. package/dist/runner-CZV2sC46.js +392 -0
  65. package/dist/sandbox-egress-signals-bHZfmhbd.js +46 -0
  66. package/dist/schema-Rn8-XEaN.js +1140 -0
  67. package/dist/sentry-CD3SdJl-.js +26 -0
  68. package/dist/skills-CIcDQSgM.js +316 -0
  69. package/dist/spawn-DdkUFRmq.js +29 -0
  70. package/dist/stats-DTUbAb88.js +2634 -0
  71. package/dist/store-C5qxF2ac.d.ts +208 -0
  72. package/dist/task-queue-CJIR4RF7.js +136 -0
  73. package/dist/task-runner-D-O3A-Cp.js +639 -0
  74. package/dist/thread-state-CRc9jTJS.js +431 -0
  75. package/dist/turn-execution-BkinIdJF.js +47 -0
  76. package/dist/turn-lifecycle-DQV19VTW.js +54 -0
  77. package/dist/usage-D3EbSO7w.js +296 -0
  78. package/dist/usage-schema-CKaDkH_j.js +20 -0
  79. package/dist/validation-C2CR-OX3.js +40 -0
  80. package/dist/vercel-queue-DIkVcfK4.js +167 -0
  81. package/dist/vercel.d.ts +6 -6
  82. package/dist/vercel.js +14 -6
  83. package/dist/version-CnXZ614E.d.ts +5 -0
  84. package/dist/version-DxcWdoKa.js +8 -0
  85. package/dist/version.d.ts +2 -4
  86. package/dist/version.js +2 -6
  87. package/dist/web-authorization-RMBxAR_t.js +76 -0
  88. package/dist/web-input-3EPfpf55.js +494 -0
  89. package/dist/work-fGofI7Op.js +509 -0
  90. package/dist/worker-CsOLq-mR.js +625 -0
  91. package/package.json +8 -8
  92. package/dist/agent-hooks-GB2VPTNA.js +0 -51
  93. package/dist/catalog-runtime-UBLIWD3O.js +0 -14
  94. package/dist/chunk-2LYHWMR4.js +0 -46
  95. package/dist/chunk-352OWAZH.js +0 -320
  96. package/dist/chunk-3Y47VMHV.js +0 -6
  97. package/dist/chunk-3YJSKOWF.js +0 -164
  98. package/dist/chunk-4KSSKTQ5.js +0 -229
  99. package/dist/chunk-4UO6FK4G.js +0 -64
  100. package/dist/chunk-5AHX53NQ.js +0 -717
  101. package/dist/chunk-5J3E5FWA.js +0 -700
  102. package/dist/chunk-5PDAG6O3.js +0 -206
  103. package/dist/chunk-6Y5B5PEX.js +0 -80
  104. package/dist/chunk-7GWQ5S5R.js +0 -2086
  105. package/dist/chunk-7H2VZTWN.js +0 -103
  106. package/dist/chunk-7NL7T6VU.js +0 -406
  107. package/dist/chunk-A5L27LE4.js +0 -387
  108. package/dist/chunk-ADIAWL34.js +0 -81
  109. package/dist/chunk-ASTEMBKJ.js +0 -519
  110. package/dist/chunk-CEMMLV5L.js +0 -98
  111. package/dist/chunk-CM5HNV2W.js +0 -131
  112. package/dist/chunk-CRTRBBRI.js +0 -105
  113. package/dist/chunk-DA2KGZZI.js +0 -13
  114. package/dist/chunk-DIB4GX3G.js +0 -8
  115. package/dist/chunk-DKIJ2RP5.js +0 -68
  116. package/dist/chunk-DWPN6SCH.js +0 -172
  117. package/dist/chunk-EDKUFGPL.js +0 -64
  118. package/dist/chunk-ENY6SKIU.js +0 -1228
  119. package/dist/chunk-F4MGCZOX.js +0 -2943
  120. package/dist/chunk-FA6POGLH.js +0 -70
  121. package/dist/chunk-FSUR7GTG.js +0 -20475
  122. package/dist/chunk-GKEOHJL5.js +0 -1856
  123. package/dist/chunk-GMCB6W5Y.js +0 -333
  124. package/dist/chunk-IE2VPG7O.js +0 -1135
  125. package/dist/chunk-IZ5B5IHQ.js +0 -3656
  126. package/dist/chunk-JAL3CH4G.js +0 -27
  127. package/dist/chunk-KIIEUXD3.js +0 -1764
  128. package/dist/chunk-KXU4NIL7.js +0 -692
  129. package/dist/chunk-MTM5IFCY.js +0 -1190
  130. package/dist/chunk-N7TMSOZH.js +0 -235
  131. package/dist/chunk-NCEBK6PH.js +0 -36
  132. package/dist/chunk-NHJU2ZAL.js +0 -56
  133. package/dist/chunk-P6KTRNVC.js +0 -321
  134. package/dist/chunk-PDO5BLNM.js +0 -22
  135. package/dist/chunk-PDRNLLFT.js +0 -3295
  136. package/dist/chunk-PEQXYSSE.js +0 -45
  137. package/dist/chunk-PKEX2AQX.js +0 -198
  138. package/dist/chunk-PSJCGIGO.js +0 -1163
  139. package/dist/chunk-PZUSUL55.js +0 -506
  140. package/dist/chunk-R45M4TUS.js +0 -37
  141. package/dist/chunk-RC745GGZ.js +0 -122
  142. package/dist/chunk-SP3QKZXJ.js +0 -820
  143. package/dist/chunk-TXKUKMVO.js +0 -417
  144. package/dist/chunk-UE4GPWSD.js +0 -931
  145. package/dist/chunk-UIE3R5XU.js +0 -17
  146. package/dist/chunk-UXPG6ZIN.js +0 -84
  147. package/dist/chunk-VFUK3X5B.js +0 -50
  148. package/dist/chunk-VNMJWREK.js +0 -787
  149. package/dist/chunk-VZ7CSOX2.js +0 -706
  150. package/dist/chunk-WCNQJKFT.js +0 -10
  151. package/dist/chunk-WDZR3QQM.js +0 -135
  152. package/dist/chunk-WLYVJCHV.js +0 -1433
  153. package/dist/chunk-WS7NAFTM.js +0 -1520
  154. package/dist/chunk-X6GDDWQH.js +0 -60
  155. package/dist/chunk-XPDEYNQI.js +0 -102
  156. package/dist/chunk-XRW44VGM.js +0 -58
  157. package/dist/chunk-Y2CM7HXH.js +0 -111
  158. package/dist/chunk-Y7X25LFY.js +0 -15
  159. package/dist/conversation-work-VIOQ5ZF3.js +0 -54
  160. package/dist/db-NGDJV7YH.js +0 -26
  161. package/dist/discovery-DWVDM7UK.js +0 -44
  162. package/dist/experimental-P5FZM4CU.js +0 -12
  163. package/dist/oauth-callback-server-YFY7YEGK.js +0 -243
  164. package/dist/oauth-relay-5XPVRPYB.js +0 -8
  165. package/dist/plugins-YTYJMMJF.js +0 -14
  166. package/dist/runner-ORTNSB2Q.js +0 -522
  167. package/dist/sandbox-egress-signals-FIL7EGPR.js +0 -73
  168. package/dist/sentry-MB3NL3IR.js +0 -42
  169. package/dist/skills-H376WQOH.js +0 -27
  170. package/dist/spawn-T43WHLOS.js +0 -42
  171. package/dist/task-runner-LBZWF32V.js +0 -36
  172. package/dist/validation-REJLRKI6.js +0 -17
  173. package/dist/work-3KJ3RE76.js +0 -51
  174. package/dist/worker-QFEBU72E.js +0 -24
@@ -0,0 +1,1419 @@
1
+ import { _ as setSpanAttributes, d as logInfo, f as logWarn } from "./logging-BzAk1S_s.js";
2
+ import { i as credentialUserSubjectId } from "./context-B45_WqVX.js";
3
+ import { n as normalizePluginPackageNames, o as pluginRoots, t as discoverInstalledPluginPackageContent } from "./package-discovery-7a9YqLhE.js";
4
+ import path from "node:path";
5
+ import { z } from "zod";
6
+ import { randomUUID } from "node:crypto";
7
+ import { parse } from "yaml";
8
+ import { readFileSync, readdirSync, statSync } from "node:fs";
9
+ //#region src/chat/plugins/inline-manifest-source.ts
10
+ function setDefined$1(target, key, value) {
11
+ if (value !== void 0) target[key] = value;
12
+ }
13
+ function isRecord(value) {
14
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
15
+ }
16
+ function unqualifyManifestToken(name, value) {
17
+ if (typeof name === "string" && typeof value === "string" && value.startsWith(`${name}.`)) return value.slice(name.length + 1);
18
+ return value;
19
+ }
20
+ function inlineTokenListSource(name, values) {
21
+ if (values === void 0 || !Array.isArray(values)) return values;
22
+ return values.map((value) => unqualifyManifestToken(name, value));
23
+ }
24
+ function inlineCredentialsSource(credentials) {
25
+ if (credentials === void 0 || !isRecord(credentials)) return credentials;
26
+ const result = {};
27
+ setDefined$1(result, "type", credentials.type);
28
+ setDefined$1(result, "domains", credentials.domains);
29
+ setDefined$1(result, "api-headers", credentials.apiHeaders);
30
+ setDefined$1(result, "auth-token-env", credentials.authTokenEnv);
31
+ setDefined$1(result, "auth-token-placeholder", credentials.authTokenPlaceholder);
32
+ return result;
33
+ }
34
+ function inlineMcpSource(mcp) {
35
+ if (mcp === void 0 || !isRecord(mcp)) return mcp;
36
+ const result = {};
37
+ setDefined$1(result, "transport", mcp.transport);
38
+ setDefined$1(result, "url", mcp.url);
39
+ setDefined$1(result, "headers", mcp.headers);
40
+ setDefined$1(result, "allowed-tools", mcp.allowedTools);
41
+ setDefined$1(result, "wrapped-tools", mcp.wrappedTools);
42
+ return result;
43
+ }
44
+ function inlineOauthSource(oauth) {
45
+ if (oauth === void 0 || !isRecord(oauth)) return oauth;
46
+ const result = {};
47
+ setDefined$1(result, "client-id-env", oauth.clientIdEnv);
48
+ setDefined$1(result, "client-secret-env", oauth.clientSecretEnv);
49
+ setDefined$1(result, "authorize-endpoint", oauth.authorizeEndpoint);
50
+ setDefined$1(result, "token-endpoint", oauth.tokenEndpoint);
51
+ setDefined$1(result, "scope", oauth.scope);
52
+ setDefined$1(result, "authorize-params", oauth.authorizeParams);
53
+ setDefined$1(result, "token-auth-method", oauth.tokenAuthMethod);
54
+ setDefined$1(result, "token-extra-headers", oauth.tokenExtraHeaders);
55
+ setDefined$1(result, "treat-empty-scope-as-unreported", oauth.treatEmptyScopeAsUnreported);
56
+ return result;
57
+ }
58
+ function inlineTargetSource(name, target) {
59
+ if (target === void 0 || !isRecord(target)) return target;
60
+ const result = {};
61
+ setDefined$1(result, "type", target.type);
62
+ setDefined$1(result, "config-key", unqualifyManifestToken(name, target.configKey));
63
+ setDefined$1(result, "command-flags", target.commandFlags);
64
+ return result;
65
+ }
66
+ /** Convert inline JavaScript plugin manifests to the canonical source shape. */
67
+ function inlineManifestSource(manifest) {
68
+ const result = {};
69
+ setDefined$1(result, "name", manifest.name);
70
+ setDefined$1(result, "display-name", manifest.displayName);
71
+ setDefined$1(result, "description", manifest.description);
72
+ setDefined$1(result, "config-keys", inlineTokenListSource(manifest.name, manifest.configKeys));
73
+ setDefined$1(result, "domains", manifest.domains);
74
+ setDefined$1(result, "api-headers", manifest.apiHeaders);
75
+ setDefined$1(result, "command-env", manifest.commandEnv);
76
+ setDefined$1(result, "env-vars", manifest.envVars);
77
+ setDefined$1(result, "credentials", inlineCredentialsSource(manifest.credentials));
78
+ setDefined$1(result, "runtime-dependencies", manifest.runtimeDependencies);
79
+ setDefined$1(result, "runtime-postinstall", manifest.runtimePostinstall);
80
+ setDefined$1(result, "mcp", inlineMcpSource(manifest.mcp));
81
+ setDefined$1(result, "oauth", inlineOauthSource(manifest.oauth));
82
+ setDefined$1(result, "target", inlineTargetSource(manifest.name, manifest.target));
83
+ return result;
84
+ }
85
+ //#endregion
86
+ //#region src/chat/plugins/manifest.ts
87
+ const PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
88
+ const SHORT_CONFIG_KEY_RE = /^[a-z0-9]+(\.[a-z0-9-]+)*$/;
89
+ const TARGET_FLAG_RE = /^-{1,2}[A-Za-z0-9][A-Za-z0-9-]*$/;
90
+ const AUTH_TOKEN_ENV_RE = /^[A-Z][A-Z0-9_]*$/;
91
+ const ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;
92
+ const ENV_PLACEHOLDER_RE$2 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
93
+ const API_DOMAIN_RE = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
94
+ const RUNTIME_POSTINSTALL_CMD_RE = /^[A-Za-z0-9._/-]+$/;
95
+ const RESERVED_AUTHORIZE_PARAM_KEYS = /* @__PURE__ */ new Set([
96
+ "client_id",
97
+ "scope",
98
+ "state",
99
+ "redirect_uri",
100
+ "response_type"
101
+ ]);
102
+ const FORBIDDEN_API_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
103
+ const FORBIDDEN_TOKEN_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
104
+ const nonEmptyTrimmedString = z.string().transform((value) => value.trim()).pipe(z.string().min(1, { error: "must be a non-empty string" }));
105
+ const nonEmptyStringArraySchema = (fieldName, options = {}) => z.array(z.string(), { error: "must be an array of strings when provided" }).min(1, { error: options.nonEmptyMessage ?? "must be a non-empty array of strings when provided" }).transform((values, ctx) => {
106
+ const result = [];
107
+ const seen = /* @__PURE__ */ new Set();
108
+ for (const rawValue of values) {
109
+ const value = rawValue.trim();
110
+ if (!value) {
111
+ ctx.addIssue({
112
+ code: z.ZodIssueCode.custom,
113
+ message: `${fieldName} entries must be non-empty strings`
114
+ });
115
+ return z.NEVER;
116
+ }
117
+ if (seen.has(value)) continue;
118
+ seen.add(value);
119
+ result.push(value);
120
+ }
121
+ return result;
122
+ });
123
+ const envVarString = nonEmptyTrimmedString.refine((value) => AUTH_TOKEN_ENV_RE.test(value), { error: "must be an uppercase env var name" });
124
+ const httpsUrlString = nonEmptyTrimmedString.superRefine((value, ctx) => {
125
+ let parsed;
126
+ try {
127
+ parsed = new URL(value);
128
+ } catch {
129
+ ctx.addIssue({
130
+ code: z.ZodIssueCode.custom,
131
+ message: "must be a valid URL"
132
+ });
133
+ return;
134
+ }
135
+ if (parsed.protocol !== "https:") ctx.addIssue({
136
+ code: z.ZodIssueCode.custom,
137
+ message: "must use https"
138
+ });
139
+ });
140
+ const stringMapSchema = z.record(z.string(), z.unknown()).transform((record, ctx) => {
141
+ const entries = Object.entries(record);
142
+ const result = {};
143
+ const seen = /* @__PURE__ */ new Set();
144
+ for (const [rawKey, rawValue] of entries) {
145
+ const key = rawKey.trim();
146
+ if (!key) {
147
+ ctx.addIssue({
148
+ code: z.ZodIssueCode.custom,
149
+ message: "keys must be non-empty strings"
150
+ });
151
+ return z.NEVER;
152
+ }
153
+ if (typeof rawValue !== "string" || !rawValue.trim()) {
154
+ ctx.addIssue({
155
+ code: z.ZodIssueCode.custom,
156
+ message: `${key} must be a non-empty string`
157
+ });
158
+ return z.NEVER;
159
+ }
160
+ const normalizedKey = key.toLowerCase();
161
+ if (seen.has(normalizedKey)) {
162
+ ctx.addIssue({
163
+ code: z.ZodIssueCode.custom,
164
+ message: `${key} is duplicated`
165
+ });
166
+ return z.NEVER;
167
+ }
168
+ seen.add(normalizedKey);
169
+ result[key] = rawValue.trim();
170
+ }
171
+ return result;
172
+ });
173
+ const domainsSchema = z.array(z.unknown()).min(1, { error: "must be a non-empty array of strings" }).transform((domains, ctx) => {
174
+ return domains.map((rawDomain) => {
175
+ const domain = typeof rawDomain === "string" ? rawDomain.trim().toLowerCase() : "";
176
+ if (!domain) {
177
+ ctx.addIssue({
178
+ code: z.ZodIssueCode.custom,
179
+ message: "entries must be non-empty strings"
180
+ });
181
+ return z.NEVER;
182
+ }
183
+ if (!API_DOMAIN_RE.test(domain)) {
184
+ ctx.addIssue({
185
+ code: z.ZodIssueCode.custom,
186
+ message: "entries must be valid domain names"
187
+ });
188
+ return z.NEVER;
189
+ }
190
+ return domain;
191
+ });
192
+ });
193
+ const oauthBearerCredentialsSchema = z.object({ domains: domainsSchema.optional() }).passthrough().extend({
194
+ "api-headers": stringMapSchema.optional(),
195
+ "auth-token-env": envVarString,
196
+ "auth-token-placeholder": nonEmptyTrimmedString.optional(),
197
+ type: z.literal("oauth-bearer")
198
+ });
199
+ const runtimeDependencyEntrySchema = z.object({
200
+ type: z.enum(["npm", "system"]),
201
+ package: z.string().optional(),
202
+ version: z.string().optional(),
203
+ url: z.string().optional(),
204
+ sha256: z.string().optional()
205
+ }).passthrough();
206
+ const runtimePostinstallCommandSourceSchema = z.object({
207
+ cmd: nonEmptyTrimmedString,
208
+ args: z.array(z.string(), { error: "args must be an array of strings when provided" }).optional(),
209
+ sudo: z.boolean({ error: "sudo must be a boolean when provided" }).optional()
210
+ }).passthrough();
211
+ const oauthSourceSchema = z.object({
212
+ "client-id-env": envVarString,
213
+ "client-secret-env": envVarString,
214
+ "authorize-endpoint": httpsUrlString,
215
+ "token-endpoint": httpsUrlString,
216
+ scope: nonEmptyTrimmedString.optional(),
217
+ "authorize-params": stringMapSchema.optional(),
218
+ "token-extra-headers": stringMapSchema.optional(),
219
+ "token-auth-method": nonEmptyTrimmedString.refine((value) => value === "body" || value === "basic", { error: "must be \"body\" or \"basic\"" }).optional(),
220
+ "treat-empty-scope-as-unreported": z.boolean().optional()
221
+ }).passthrough();
222
+ const mcpSourceSchema = z.object({
223
+ transport: nonEmptyTrimmedString.refine((value) => value === "http", { error: "must be \"http\"" }).optional(),
224
+ url: httpsUrlString,
225
+ headers: stringMapSchema.optional(),
226
+ "allowed-tools": nonEmptyStringArraySchema("allowed-tools").optional(),
227
+ "wrapped-tools": nonEmptyStringArraySchema("wrapped-tools").optional()
228
+ }).passthrough();
229
+ const targetSourceSchema = z.object({
230
+ type: nonEmptyTrimmedString.refine((value) => PLUGIN_NAME_RE.test(value), { error: "type must be a lowercase target identifier" }),
231
+ "config-key": nonEmptyTrimmedString,
232
+ "command-flags": nonEmptyStringArraySchema("command-flags").optional()
233
+ }).passthrough();
234
+ const manifestSourceSchema = z.object({
235
+ name: z.string().refine((value) => PLUGIN_NAME_RE.test(value), { error: "invalid" }),
236
+ "display-name": nonEmptyTrimmedString,
237
+ description: nonEmptyTrimmedString,
238
+ "config-keys": z.array(z.string(), { error: "must be an array when provided" }).optional(),
239
+ domains: domainsSchema.optional(),
240
+ "api-headers": stringMapSchema.optional(),
241
+ "command-env": stringMapSchema.optional(),
242
+ credentials: z.record(z.string(), z.unknown(), { error: "must be an object when provided" }).optional(),
243
+ "runtime-dependencies": z.array(z.unknown(), { error: "must be an array" }).optional(),
244
+ "runtime-postinstall": z.array(z.unknown(), { error: "must be an array" }).optional(),
245
+ "env-vars": z.record(z.string(), z.unknown(), { error: "must be an object" }).optional(),
246
+ mcp: z.record(z.string(), z.unknown(), { error: "must be an object" }).optional(),
247
+ oauth: z.record(z.string(), z.unknown(), { error: "must be an object" }).optional(),
248
+ target: z.record(z.string(), z.unknown(), { error: "must be an object" }).optional()
249
+ }).passthrough();
250
+ function setDefined(target, key, value) {
251
+ if (value !== void 0) target[key] = value;
252
+ }
253
+ function manifestConfigPatch(config) {
254
+ const result = {};
255
+ setDefined(result, "display-name", config.displayName);
256
+ setDefined(result, "description", config.description);
257
+ setDefined(result, "config-keys", config.configKeys);
258
+ setDefined(result, "domains", config.domains);
259
+ setDefined(result, "api-headers", config.apiHeaders);
260
+ setDefined(result, "command-env", config.commandEnv);
261
+ setDefined(result, "env-vars", config.envVars);
262
+ if (config.credentials !== void 0) {
263
+ if (!config.credentials) result.credentials = null;
264
+ else {
265
+ const credentials = {};
266
+ setDefined(credentials, "type", config.credentials.type);
267
+ setDefined(credentials, "domains", config.credentials.domains);
268
+ setDefined(credentials, "api-headers", config.credentials.apiHeaders);
269
+ setDefined(credentials, "auth-token-env", config.credentials.authTokenEnv);
270
+ setDefined(credentials, "auth-token-placeholder", config.credentials.authTokenPlaceholder);
271
+ result.credentials = credentials;
272
+ }
273
+ }
274
+ setDefined(result, "runtime-dependencies", config.runtimeDependencies);
275
+ setDefined(result, "runtime-postinstall", config.runtimePostinstall);
276
+ if (config.mcp !== void 0) {
277
+ if (!config.mcp) result.mcp = null;
278
+ else {
279
+ const mcp = {};
280
+ setDefined(mcp, "transport", config.mcp.transport);
281
+ setDefined(mcp, "url", config.mcp.url);
282
+ setDefined(mcp, "headers", config.mcp.headers);
283
+ setDefined(mcp, "allowed-tools", config.mcp.allowedTools);
284
+ setDefined(mcp, "wrapped-tools", config.mcp.wrappedTools);
285
+ result.mcp = mcp;
286
+ }
287
+ }
288
+ if (config.oauth !== void 0) {
289
+ if (!config.oauth) result.oauth = null;
290
+ else {
291
+ const oauth = {};
292
+ setDefined(oauth, "client-id-env", config.oauth.clientIdEnv);
293
+ setDefined(oauth, "client-secret-env", config.oauth.clientSecretEnv);
294
+ setDefined(oauth, "authorize-endpoint", config.oauth.authorizeEndpoint);
295
+ setDefined(oauth, "token-endpoint", config.oauth.tokenEndpoint);
296
+ setDefined(oauth, "scope", config.oauth.scope);
297
+ setDefined(oauth, "authorize-params", config.oauth.authorizeParams);
298
+ setDefined(oauth, "token-auth-method", config.oauth.tokenAuthMethod);
299
+ setDefined(oauth, "token-extra-headers", config.oauth.tokenExtraHeaders);
300
+ setDefined(oauth, "treat-empty-scope-as-unreported", config.oauth.treatEmptyScopeAsUnreported);
301
+ result.oauth = oauth;
302
+ }
303
+ }
304
+ if (config.target !== void 0) {
305
+ if (!config.target) result.target = null;
306
+ else {
307
+ const target = {};
308
+ setDefined(target, "type", config.target.type);
309
+ setDefined(target, "config-key", config.target.configKey);
310
+ setDefined(target, "command-flags", config.target.commandFlags);
311
+ result.target = target;
312
+ }
313
+ }
314
+ return result;
315
+ }
316
+ function isPlainRecord(value) {
317
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
318
+ }
319
+ function mergeManifestConfig(base, patch, options = {}) {
320
+ const merged = { ...base };
321
+ for (const [key, value] of Object.entries(patch)) {
322
+ if (options.root && key === "name") throw new Error("plugins.manifests cannot change plugin names");
323
+ if (value === null) {
324
+ delete merged[key];
325
+ continue;
326
+ }
327
+ const current = merged[key];
328
+ if (isPlainRecord(current) && isPlainRecord(value)) {
329
+ merged[key] = mergeManifestConfig(current, value);
330
+ continue;
331
+ }
332
+ merged[key] = value;
333
+ }
334
+ return merged;
335
+ }
336
+ function applyManifestConfig(source, config) {
337
+ const name = source.name;
338
+ if (typeof name !== "string") return source;
339
+ const manifestConfig = config?.manifests?.[name];
340
+ if (manifestConfig && "name" in manifestConfig) throw new Error("plugins.manifests cannot change plugin names");
341
+ return manifestConfig ? mergeManifestConfig(source, manifestConfigPatch(manifestConfig), { root: true }) : source;
342
+ }
343
+ function formatPath(path) {
344
+ return path.map((segment) => String(segment)).join(".");
345
+ }
346
+ function issueMessage(error, prefix) {
347
+ const issue = error.issues[0];
348
+ if (!issue) return prefix;
349
+ const suffix = formatPath(issue.path);
350
+ return suffix ? `${prefix}.${suffix} ${issue.message}` : `${prefix} ${issue.message}`;
351
+ }
352
+ function normalizeStringMap(value, prefix, options = {}) {
353
+ if (!value) return;
354
+ const keys = Object.keys(value);
355
+ if (keys.length === 0) return;
356
+ for (const key of keys) {
357
+ const normalizedKey = key.toLowerCase();
358
+ if (options.reservedKeys?.has(normalizedKey)) throw new Error(`${prefix}.${key} is reserved by the runtime`);
359
+ if (options.forbiddenKeys?.has(normalizedKey)) throw new Error(`${prefix}.${key} is not allowed`);
360
+ }
361
+ return value;
362
+ }
363
+ function envReferences(value) {
364
+ return Array.from(value.matchAll(ENV_PLACEHOLDER_RE$2), (match) => {
365
+ return match[1];
366
+ });
367
+ }
368
+ function assertDeclaredEnvReferences(value, envVars, context) {
369
+ for (const name of envReferences(value)) {
370
+ if (!Object.prototype.hasOwnProperty.call(envVars, name)) throw new Error(`${context} references env var ${name} which is not declared in env-vars`);
371
+ if (envVars[name]?.default !== void 0) throw new Error(`${context} references env var ${name}, but API header env vars must not declare defaults`);
372
+ }
373
+ }
374
+ function normalizeRequiredApiHeaders(value, prefix, envVars) {
375
+ const apiHeaders = normalizeStringMap(value, prefix);
376
+ if (!apiHeaders) throw new Error(`${prefix} must contain at least one header`);
377
+ for (const [key, headerValue] of Object.entries(apiHeaders)) assertDeclaredEnvReferences(headerValue, envVars, `${prefix}.${key}`);
378
+ return apiHeaders;
379
+ }
380
+ function assertCommandEnvReferencesDeclared(value, envVars, context) {
381
+ for (const name of envReferences(value)) if (!Object.prototype.hasOwnProperty.call(envVars, name)) throw new Error(`${context} references env var ${name} which is not declared in env-vars`);
382
+ }
383
+ function expandCommandEnvPlaceholders(template, envVars, context) {
384
+ return template.replace(ENV_PLACEHOLDER_RE$2, (match, name) => {
385
+ if (envVars[name]?.default === void 0) return match;
386
+ return expandEnvPlaceholders(match, envVars, context);
387
+ });
388
+ }
389
+ function normalizeCommandEnv(value, prefix, envVars) {
390
+ const env = normalizeStringMap(value, prefix);
391
+ if (!env) throw new Error(`${prefix} must contain at least one env var`);
392
+ for (const [key, envValue] of Object.entries(env)) {
393
+ if (!ENV_VAR_NAME_RE.test(key)) throw new Error(`${prefix}.${key} must be an uppercase env var name`);
394
+ assertCommandEnvReferencesDeclared(envValue, envVars, `${prefix}.${key}`);
395
+ }
396
+ return Object.fromEntries(Object.entries(env).map(([key, envValue]) => [key, expandCommandEnvPlaceholders(envValue, envVars, `${prefix}.${key}`)]));
397
+ }
398
+ function assertCommandEnvDoesNotExposeHostSecretRefs(commandEnv, apiHeaders, credentials, oauth, pluginName) {
399
+ if (!commandEnv) return;
400
+ const hostOnlyRefs = /* @__PURE__ */ new Set();
401
+ for (const value of Object.values(apiHeaders ?? {})) for (const name of envReferences(value)) hostOnlyRefs.add(name);
402
+ if (credentials) {
403
+ if (credentials.authTokenEnv) hostOnlyRefs.add(credentials.authTokenEnv);
404
+ }
405
+ if (oauth) {
406
+ hostOnlyRefs.add(oauth.clientIdEnv);
407
+ hostOnlyRefs.add(oauth.clientSecretEnv);
408
+ }
409
+ for (const [key, value] of Object.entries(commandEnv)) for (const name of envReferences(value)) if (hostOnlyRefs.has(name)) throw new Error(`Plugin ${pluginName} command-env.${key} references env var ${name}, but credential/API header env vars must stay host-only`);
410
+ }
411
+ function assertCommandEnvHostRefsAreExplicitlyExposed(commandEnv, envVars, pluginName) {
412
+ if (!commandEnv) return;
413
+ for (const [key, value] of Object.entries(commandEnv)) for (const name of envReferences(value)) {
414
+ const declaration = envVars[name];
415
+ if (declaration && declaration.default === void 0 && declaration.exposeToCommandEnv !== true) throw new Error(`Plugin ${pluginName} command-env.${key} references env var ${name}, but env-vars.${name} must set expose-to-command-env: true before host env can be exposed to sandbox`);
416
+ }
417
+ }
418
+ function normalizeCredentials(data, name) {
419
+ if (data.type !== "oauth-bearer") throw new Error(`Plugin ${name} has unsupported credentials.type: "${String(data.type)}"`);
420
+ const result = oauthBearerCredentialsSchema.safeParse(data);
421
+ if (!result.success) throw new Error(issueMessage(result.error, `Plugin ${name} credentials`));
422
+ if (!result.data.domains) throw new Error(`Plugin ${name} credentials requires domains`);
423
+ const domains = result.data.domains;
424
+ const apiHeaders = result.data["api-headers"] ? normalizeStringMap(result.data["api-headers"], `Plugin ${name} credentials.api-headers`, { forbiddenKeys: FORBIDDEN_API_HEADER_NAMES }) : void 0;
425
+ return {
426
+ type: "oauth-bearer",
427
+ domains,
428
+ ...apiHeaders ? { apiHeaders } : void 0,
429
+ authTokenEnv: result.data["auth-token-env"],
430
+ ...result.data["auth-token-placeholder"] ? { authTokenPlaceholder: result.data["auth-token-placeholder"] } : void 0
431
+ };
432
+ }
433
+ function normalizeRuntimeDependencies(entries, name) {
434
+ const parsed = [];
435
+ const seen = /* @__PURE__ */ new Set();
436
+ for (const entry of entries) {
437
+ const result = runtimeDependencyEntrySchema.safeParse(entry);
438
+ if (!result.success) throw new Error(issueMessage(result.error, `Plugin ${name} runtime-dependencies entries`));
439
+ const record = result.data;
440
+ const packageName = typeof record.package === "string" ? record.package.trim() : "";
441
+ const packageUrl = typeof record.url === "string" ? record.url.trim() : "";
442
+ const version = record.version;
443
+ const sha256 = record.sha256;
444
+ if (record.type === "npm") {
445
+ if (!packageName) throw new Error(`Plugin ${name} runtime dependency package must be a non-empty string`);
446
+ if (record.url !== void 0 || sha256 !== void 0) throw new Error(`Plugin ${name} npm runtime dependencies must only include package/version fields`);
447
+ const normalizedVersion = typeof version === "string" ? version.trim() : "latest";
448
+ if (!normalizedVersion) throw new Error(`Plugin ${name} runtime dependency version must be a non-empty string when provided`);
449
+ const dedupeKey = `${record.type}:${packageName}:${normalizedVersion}`;
450
+ if (seen.has(dedupeKey)) continue;
451
+ seen.add(dedupeKey);
452
+ parsed.push({
453
+ type: "npm",
454
+ package: packageName,
455
+ version: normalizedVersion
456
+ });
457
+ continue;
458
+ }
459
+ if (version !== void 0) throw new Error(`Plugin ${name} system runtime dependencies must not include a version`);
460
+ if (packageName && packageUrl) throw new Error(`Plugin ${name} system runtime dependencies must specify either package or url, not both`);
461
+ if (!packageName && !packageUrl) throw new Error(`Plugin ${name} system runtime dependencies must specify package or url`);
462
+ if (packageName) {
463
+ if (sha256 !== void 0) throw new Error(`Plugin ${name} system runtime dependency package entries must not include sha256`);
464
+ const dedupeKey = `${record.type}:package:${packageName}`;
465
+ if (seen.has(dedupeKey)) continue;
466
+ seen.add(dedupeKey);
467
+ parsed.push({
468
+ type: "system",
469
+ package: packageName
470
+ });
471
+ continue;
472
+ }
473
+ if (!/^https:\/\//i.test(packageUrl)) throw new Error(`Plugin ${name} system runtime dependency url must be an https URL`);
474
+ const normalizedSha256 = typeof sha256 === "string" ? sha256.trim().toLowerCase() : "";
475
+ if (!/^[a-f0-9]{64}$/.test(normalizedSha256)) throw new Error(`Plugin ${name} system runtime dependency url entries must include a valid sha256`);
476
+ const dedupeKey = `${record.type}:url:${packageUrl}:${normalizedSha256}`;
477
+ if (seen.has(dedupeKey)) continue;
478
+ seen.add(dedupeKey);
479
+ parsed.push({
480
+ type: "system",
481
+ url: packageUrl,
482
+ sha256: normalizedSha256
483
+ });
484
+ }
485
+ return parsed.length > 0 ? parsed : void 0;
486
+ }
487
+ function normalizeRuntimePostinstall(commands, name) {
488
+ const parsed = [];
489
+ for (const command of commands) {
490
+ const result = runtimePostinstallCommandSourceSchema.safeParse(command);
491
+ if (!result.success) throw new Error(issueMessage(result.error, `Plugin ${name} runtime-postinstall`));
492
+ if (!RUNTIME_POSTINSTALL_CMD_RE.test(result.data.cmd)) throw new Error(`Plugin ${name} runtime-postinstall cmd must be a single executable token (letters, digits, ., _, /, -)`);
493
+ const normalizedArgs = result.data.args?.map((arg) => arg.trim()).filter((arg) => arg.length > 0);
494
+ parsed.push({
495
+ cmd: result.data.cmd,
496
+ ...normalizedArgs && normalizedArgs.length > 0 ? { args: normalizedArgs } : void 0,
497
+ ...typeof result.data.sudo === "boolean" ? { sudo: result.data.sudo } : void 0
498
+ });
499
+ }
500
+ return parsed.length > 0 ? parsed : void 0;
501
+ }
502
+ const envVarDeclarationSchema = z.preprocess((value) => value === null || value === void 0 ? {} : value, z.object({
503
+ default: z.string().optional(),
504
+ "expose-to-command-env": z.boolean().optional(),
505
+ exposeToCommandEnv: z.boolean().optional()
506
+ }).strict());
507
+ function normalizeEnvVars(data, pluginName) {
508
+ const normalized = {};
509
+ for (const [rawName, rawDecl] of Object.entries(data)) {
510
+ const name = rawName.trim();
511
+ if (!ENV_VAR_NAME_RE.test(name)) throw new Error(`Plugin ${pluginName} env-vars key "${rawName}" must match [A-Z_][A-Z0-9_]*`);
512
+ const parsed = envVarDeclarationSchema.safeParse(rawDecl);
513
+ if (!parsed.success) throw new Error(issueMessage(parsed.error, `Plugin ${pluginName} env-vars.${name}`));
514
+ const decl = {};
515
+ if (parsed.data.default !== void 0) decl.default = parsed.data.default;
516
+ if (parsed.data["expose-to-command-env"] === true || parsed.data.exposeToCommandEnv === true) decl.exposeToCommandEnv = true;
517
+ normalized[name] = decl;
518
+ }
519
+ return normalized;
520
+ }
521
+ /**
522
+ * Expand `${NAME}` placeholders in a manifest string field using the plugin's
523
+ * declared env-vars block. `NAME` must match `[A-Z_][A-Z0-9_]*` and must be
524
+ * declared in the plugin's `env-vars` block — otherwise load fails. This
525
+ * keeps the manifest's env-var surface explicit and auditable, and prevents
526
+ * a plugin manifest from opportunistically exfiltrating ambient process env
527
+ * vars via `mcp.url`. If `NAME` is declared without a default and
528
+ * `process.env[NAME]` is unset, load also fails.
529
+ */
530
+ function expandEnvPlaceholders(template, envVars, context) {
531
+ return template.replace(ENV_PLACEHOLDER_RE$2, (_match, name) => {
532
+ const varName = name;
533
+ if (!Object.prototype.hasOwnProperty.call(envVars, varName)) throw new Error(`${context} references env var ${varName} which is not declared in env-vars`);
534
+ const decl = envVars[varName];
535
+ const fromProcess = process.env[varName];
536
+ if (fromProcess !== void 0 && fromProcess !== "") return fromProcess;
537
+ if (decl.default !== void 0) return decl.default;
538
+ throw new Error(`${context} env var ${varName} is unset and has no default in env-vars`);
539
+ });
540
+ }
541
+ function normalizeMcp(data, envVars, name) {
542
+ const prepared = { ...data };
543
+ if (typeof prepared.url === "string") prepared.url = expandEnvPlaceholders(prepared.url, envVars, `Plugin ${name} mcp.url`);
544
+ const result = mcpSourceSchema.safeParse(prepared);
545
+ if (!result.success) throw new Error(issueMessage(result.error, `Plugin ${name} mcp`));
546
+ const headers = result.data.headers ? normalizeStringMap(result.data.headers, `Plugin ${name} mcp.headers`, { forbiddenKeys: FORBIDDEN_API_HEADER_NAMES }) : void 0;
547
+ return {
548
+ transport: "http",
549
+ url: result.data.url,
550
+ ...headers ? { headers } : void 0,
551
+ ...result.data["allowed-tools"] ? { allowedTools: result.data["allowed-tools"] } : void 0,
552
+ ...result.data["wrapped-tools"] ? { wrappedTools: result.data["wrapped-tools"] } : void 0
553
+ };
554
+ }
555
+ function parseManifestSource(parsedSource, dir, config, options) {
556
+ const source = applyManifestConfig(parsedSource, config);
557
+ const sourceResult = manifestSourceSchema.safeParse(source);
558
+ if (!sourceResult.success) {
559
+ const issue = sourceResult.error.issues[0];
560
+ const path = formatPath(issue?.path ?? []);
561
+ if (path === "name") throw new Error(`Invalid plugin name in ${dir}: "${parsedSource.name}"`);
562
+ if (path === "description") throw new Error(`Invalid plugin description in ${dir}`);
563
+ if (path === "config-keys") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} config-keys must be an array when provided`);
564
+ if (path === "domains") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} ${path} must be a non-empty array of domains`);
565
+ if (path === "api-headers") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} api-headers must be an object when provided`);
566
+ if (path === "command-env") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} command-env must be an object when provided`);
567
+ if (path === "credentials") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} credentials must be an object when provided`);
568
+ if (path === "runtime-dependencies") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} runtime-dependencies must be an array`);
569
+ if (path === "runtime-postinstall") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} runtime-postinstall must be an array`);
570
+ if (path === "env-vars") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} env-vars must be an object`);
571
+ if (path === "mcp") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} mcp must be an object`);
572
+ if (path === "oauth") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} oauth must be an object`);
573
+ if (path === "target") throw new Error(`Plugin ${String(parsedSource.name ?? "unknown")} target must be an object`);
574
+ throw new Error(issue?.message ?? `Invalid plugin manifest in ${dir}`);
575
+ }
576
+ const data = sourceResult.data;
577
+ const configKeys = (data["config-keys"] ?? []).map((key) => {
578
+ if (!SHORT_CONFIG_KEY_RE.test(key)) throw new Error(`Invalid config key "${key}" in plugin ${data.name}`);
579
+ return `${data.name}.${key}`;
580
+ });
581
+ const envVars = data["env-vars"] ? normalizeEnvVars(data["env-vars"], data.name) : {};
582
+ const apiHeaders = data["api-headers"] ? normalizeRequiredApiHeaders(data["api-headers"], `Plugin ${data.name} api-headers`, envVars) : void 0;
583
+ const domains = data.domains;
584
+ if (apiHeaders && !domains) throw new Error(`Plugin ${data.name} api-headers requires domains`);
585
+ if (domains && !apiHeaders && !data.credentials && options?.allowHookManagedEgress !== true) throw new Error(`Plugin ${data.name} domains requires credentials or api-headers`);
586
+ const commandEnv = data["command-env"] ? normalizeCommandEnv(data["command-env"], `Plugin ${data.name} command-env`, envVars) : void 0;
587
+ const credentials = data.credentials ? normalizeCredentials(data.credentials, data.name) : void 0;
588
+ const runtimeDependencies = data["runtime-dependencies"] ? normalizeRuntimeDependencies(data["runtime-dependencies"], data.name) : void 0;
589
+ const runtimePostinstall = data["runtime-postinstall"] ? normalizeRuntimePostinstall(data["runtime-postinstall"], data.name) : void 0;
590
+ const mcp = data.mcp ? normalizeMcp(data.mcp, envVars, data.name) : void 0;
591
+ const manifest = {
592
+ name: data.name,
593
+ displayName: data["display-name"],
594
+ description: data.description,
595
+ configKeys,
596
+ ...domains ? { domains } : void 0,
597
+ ...apiHeaders ? { apiHeaders } : void 0,
598
+ ...commandEnv ? { commandEnv } : void 0,
599
+ ...Object.keys(envVars).length > 0 ? { envVars } : void 0,
600
+ ...credentials ? { credentials } : void 0,
601
+ ...runtimeDependencies ? { runtimeDependencies } : void 0,
602
+ ...runtimePostinstall ? { runtimePostinstall } : void 0,
603
+ ...mcp ? { mcp } : void 0
604
+ };
605
+ if (data.oauth) {
606
+ if (!credentials && options?.allowHookManagedEgress !== true) throw new Error(`Plugin ${data.name} oauth requires credentials`);
607
+ const result = oauthSourceSchema.safeParse(data.oauth);
608
+ if (!result.success) throw new Error(issueMessage(result.error, `Plugin ${data.name} oauth`));
609
+ const authorizeParams = result.data["authorize-params"] ? normalizeStringMap(result.data["authorize-params"], `Plugin ${data.name} oauth.authorize-params`, { reservedKeys: RESERVED_AUTHORIZE_PARAM_KEYS }) : void 0;
610
+ const tokenExtraHeaders = result.data["token-extra-headers"] ? normalizeStringMap(result.data["token-extra-headers"], `Plugin ${data.name} oauth.token-extra-headers`, { forbiddenKeys: FORBIDDEN_TOKEN_HEADER_NAMES }) : void 0;
611
+ manifest.oauth = {
612
+ clientIdEnv: result.data["client-id-env"],
613
+ clientSecretEnv: result.data["client-secret-env"],
614
+ authorizeEndpoint: result.data["authorize-endpoint"],
615
+ tokenEndpoint: result.data["token-endpoint"],
616
+ ...result.data.scope ? { scope: result.data.scope } : void 0,
617
+ ...authorizeParams ? { authorizeParams } : void 0,
618
+ ...result.data["token-auth-method"] ? { tokenAuthMethod: result.data["token-auth-method"] } : void 0,
619
+ ...tokenExtraHeaders ? { tokenExtraHeaders } : void 0,
620
+ ...result.data["treat-empty-scope-as-unreported"] ? { treatEmptyScopeAsUnreported: true } : void 0
621
+ };
622
+ }
623
+ assertCommandEnvDoesNotExposeHostSecretRefs(data["command-env"], apiHeaders, credentials, manifest.oauth, data.name);
624
+ assertCommandEnvHostRefsAreExplicitlyExposed(data["command-env"], envVars, data.name);
625
+ if (data.target) {
626
+ const result = targetSourceSchema.safeParse(data.target);
627
+ if (!result.success) throw new Error(issueMessage(result.error, `Plugin ${data.name} target`));
628
+ if (!SHORT_CONFIG_KEY_RE.test(result.data["config-key"])) throw new Error(`Plugin ${data.name} target.config-key "${result.data["config-key"]}" is invalid`);
629
+ const qualifiedKey = `${data.name}.${result.data["config-key"]}`;
630
+ if (!configKeys.includes(qualifiedKey)) throw new Error(`Plugin ${data.name} target.config-key "${result.data["config-key"]}" must be listed in config-keys`);
631
+ const commandFlags = result.data["command-flags"];
632
+ if (commandFlags && commandFlags.some((flag) => !TARGET_FLAG_RE.test(flag))) throw new Error(`Plugin ${data.name} target.command-flags must contain CLI flags like --repo or -R`);
633
+ manifest.target = {
634
+ type: result.data.type,
635
+ configKey: qualifiedKey,
636
+ ...commandFlags ? { commandFlags } : void 0
637
+ };
638
+ }
639
+ return manifest;
640
+ }
641
+ /** Parse one plugin.yaml manifest after applying install-level plugin config. */
642
+ function parsePluginManifest(raw, dir, config) {
643
+ let parsedYaml;
644
+ try {
645
+ parsedYaml = parse(raw);
646
+ } catch (error) {
647
+ throw new Error(`Invalid plugin manifest in ${dir}: ${error instanceof Error ? error.message : String(error)}`);
648
+ }
649
+ if (!parsedYaml || typeof parsedYaml !== "object" || Array.isArray(parsedYaml)) throw new Error(`Invalid plugin manifest in ${dir}: expected an object`);
650
+ return parseManifestSource(parsedYaml, dir, config);
651
+ }
652
+ /** Parse one inline JavaScript manifest through the same effective manifest pipeline as plugin.yaml. */
653
+ function parseInlinePluginManifest(manifest, dir, config) {
654
+ return parseManifestSource(inlineManifestSource(manifest), dir, config, { allowHookManagedEgress: true });
655
+ }
656
+ //#endregion
657
+ //#region src/chat/credentials/broker.ts
658
+ var CredentialUnavailableError = class extends Error {
659
+ provider;
660
+ constructor(provider, message) {
661
+ super(message);
662
+ this.name = "CredentialUnavailableError";
663
+ this.provider = provider;
664
+ }
665
+ };
666
+ //#endregion
667
+ //#region src/chat/credentials/header-transforms.ts
668
+ /** Merge transforms by domain so later transforms override earlier headers. */
669
+ function mergeHeaderTransforms(transforms) {
670
+ const byDomain = /* @__PURE__ */ new Map();
671
+ for (const transform of transforms) byDomain.set(transform.domain, {
672
+ ...byDomain.get(transform.domain) ?? {},
673
+ ...transform.headers
674
+ });
675
+ return [...byDomain.entries()].map(([domain, headers]) => ({
676
+ domain,
677
+ headers
678
+ }));
679
+ }
680
+ //#endregion
681
+ //#region src/chat/credentials/oauth-scope.ts
682
+ function parseScope(scope) {
683
+ if (!scope) return [];
684
+ return [...new Set(scope.split(/\s+/).filter(Boolean))].sort();
685
+ }
686
+ /** Normalize OAuth scope strings so persisted grants can be compared reliably. */
687
+ function normalizeOAuthScope(scope) {
688
+ const parsed = parseScope(scope);
689
+ return parsed.length > 0 ? parsed.join(" ") : void 0;
690
+ }
691
+ /** Return whether the stored grant still satisfies the provider's current scope contract. */
692
+ function hasRequiredOAuthScope(storedScope, requiredScope) {
693
+ const required = parseScope(requiredScope);
694
+ if (required.length === 0) return true;
695
+ const stored = new Set(parseScope(storedScope));
696
+ if (stored.size === 0) return false;
697
+ return required.every((scope) => stored.has(scope));
698
+ }
699
+ //#endregion
700
+ //#region src/chat/oauth-response.ts
701
+ const MAX_OAUTH_ERROR_BODY_BYTES = 16384;
702
+ const MAX_OAUTH_ERROR_BODY_READS = MAX_OAUTH_ERROR_BODY_BYTES;
703
+ const MAX_OAUTH_ERROR_BODY_READ_MS = 5e3;
704
+ const OAUTH_ERROR_BODY_READ_TIMEOUT = Symbol("oauth-error-body-read-timeout");
705
+ async function readOAuthErrorBodyChunk(reader) {
706
+ let timeout;
707
+ const timeoutResult = new Promise((resolve) => {
708
+ timeout = setTimeout(resolve, MAX_OAUTH_ERROR_BODY_READ_MS, OAUTH_ERROR_BODY_READ_TIMEOUT);
709
+ });
710
+ try {
711
+ const result = await Promise.race([reader.read(), timeoutResult]);
712
+ if (result === OAUTH_ERROR_BODY_READ_TIMEOUT) {
713
+ await reader.cancel();
714
+ throw new Error("OAuth error body read timed out");
715
+ }
716
+ return result;
717
+ } finally {
718
+ if (timeout) clearTimeout(timeout);
719
+ }
720
+ }
721
+ /** Safe failure at a generic OAuth provider boundary. */
722
+ var OAuthProviderError = class extends Error {
723
+ phase;
724
+ provider;
725
+ resourceHost;
726
+ status;
727
+ constructor(details) {
728
+ super(`OAuth provider ${details.phase.replace("_", " ")} failed`);
729
+ this.name = "OAuthProviderError";
730
+ this.phase = details.phase;
731
+ this.provider = details.provider;
732
+ this.resourceHost = details.resourceHost;
733
+ this.status = details.status;
734
+ }
735
+ };
736
+ /** Return safe generic OAuth provider fields for logs and spans. */
737
+ function getOAuthProviderErrorAttributes(error) {
738
+ if (!(error instanceof OAuthProviderError)) return {};
739
+ return {
740
+ "app.credential.provider": error.provider,
741
+ "app.oauth.error.phase": error.phase,
742
+ "server.address": error.resourceHost,
743
+ ...error.status !== void 0 ? { "http.response.status_code": error.status } : void 0
744
+ };
745
+ }
746
+ /** Read only the bounded prefix needed to classify an OAuth error response. */
747
+ async function readBoundedOAuthErrorBody(response) {
748
+ if (!response.body) return "";
749
+ const reader = response.body.getReader();
750
+ const decoder = new TextDecoder();
751
+ let bytesRead = 0;
752
+ let reads = 0;
753
+ let text = "";
754
+ try {
755
+ while (bytesRead < MAX_OAUTH_ERROR_BODY_BYTES && reads < MAX_OAUTH_ERROR_BODY_READS) {
756
+ reads += 1;
757
+ const { done, value } = await readOAuthErrorBodyChunk(reader);
758
+ if (done) return text + decoder.decode();
759
+ const remaining = MAX_OAUTH_ERROR_BODY_BYTES - bytesRead;
760
+ const chunk = value.subarray(0, remaining);
761
+ bytesRead += chunk.byteLength;
762
+ text += decoder.decode(chunk, { stream: true });
763
+ if (chunk.byteLength < value.byteLength) {
764
+ await reader.cancel();
765
+ return text + decoder.decode();
766
+ }
767
+ }
768
+ await reader.cancel();
769
+ return text + decoder.decode();
770
+ } finally {
771
+ reader.releaseLock();
772
+ }
773
+ }
774
+ /** Preserve small OAuth error responses while bounding bodies before SDK parsing. */
775
+ function fetchWithBoundedOAuthErrorBodies(fetchFn = globalThis.fetch, onErrorResponseStatus) {
776
+ return (async (input, init) => {
777
+ const response = await fetchFn(input, init);
778
+ if (response.ok) return response;
779
+ onErrorResponseStatus?.(response.status);
780
+ if (!response.body) return response;
781
+ let body = "";
782
+ try {
783
+ body = await readBoundedOAuthErrorBody(response);
784
+ } catch {
785
+ await response.body.cancel().catch(() => void 0);
786
+ }
787
+ return new Response(body, {
788
+ headers: response.headers,
789
+ status: response.status,
790
+ statusText: response.statusText
791
+ });
792
+ });
793
+ }
794
+ //#endregion
795
+ //#region src/chat/plugins/command-env.ts
796
+ const ENV_PLACEHOLDER_RE$1 = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
797
+ function resolveValue(manifest, value) {
798
+ let missing = false;
799
+ const resolved = value.replace(ENV_PLACEHOLDER_RE$1, (match, name) => {
800
+ const envName = name;
801
+ const declaration = manifest.envVars?.[envName];
802
+ if (!declaration || declaration.default !== void 0) return match;
803
+ const hostValue = process.env[envName];
804
+ if (hostValue === void 0 || hostValue === "") {
805
+ missing = true;
806
+ return "";
807
+ }
808
+ return hostValue;
809
+ });
810
+ return missing ? void 0 : resolved;
811
+ }
812
+ /** Resolve sandbox command env declared by a plugin manifest. */
813
+ function resolvePluginCommandEnv(manifest) {
814
+ const env = {};
815
+ for (const [key, value] of Object.entries(manifest.commandEnv ?? {})) {
816
+ const resolved = resolveValue(manifest, value);
817
+ if (resolved === void 0) continue;
818
+ env[key] = resolved;
819
+ }
820
+ return env;
821
+ }
822
+ //#endregion
823
+ //#region src/chat/plugins/auth/auth-token-placeholder.ts
824
+ const DEFAULT_PLACEHOLDERS = { "oauth-bearer": "host_managed_credential" };
825
+ /** Resolve the non-secret sandbox token placeholder for token-backed credentials. */
826
+ function resolveAuthTokenPlaceholder(credentials) {
827
+ return credentials.authTokenPlaceholder?.trim() || DEFAULT_PLACEHOLDERS[credentials.type];
828
+ }
829
+ //#endregion
830
+ //#region src/chat/plugins/auth/api-headers-broker.ts
831
+ const MAX_LEASE_MS$1 = 36e5;
832
+ const ENV_PLACEHOLDER_RE = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
833
+ function resolveHeaders(provider, headers) {
834
+ return Object.fromEntries(Object.entries(headers).map(([key, value]) => {
835
+ return [key, value.replace(ENV_PLACEHOLDER_RE, (_match, name) => {
836
+ const envName = name;
837
+ const envValue = process.env[envName]?.trim();
838
+ if (!envValue) throw new Error(`Missing ${envName} for API header provider "${provider}"`);
839
+ return envValue;
840
+ })];
841
+ }));
842
+ }
843
+ /** Resolve plugin-level API headers into sandbox header transforms. */
844
+ function resolveApiHeaderTransforms(manifest) {
845
+ const { domains, apiHeaders } = manifest;
846
+ if (!domains || !apiHeaders) return [];
847
+ const resolvedHeaders = resolveHeaders(manifest.name, apiHeaders);
848
+ return domains.map((domain) => ({
849
+ domain,
850
+ headers: resolvedHeaders
851
+ }));
852
+ }
853
+ /** Issue host-managed API header transforms backed by deployment env vars. */
854
+ function createApiHeadersBroker(manifest) {
855
+ const provider = manifest.name;
856
+ return { async issue(input) {
857
+ const headerTransforms = resolveApiHeaderTransforms(manifest);
858
+ if (headerTransforms.length === 0) throw new Error(`No API headers configured for plugin "${provider}"`);
859
+ return {
860
+ id: randomUUID(),
861
+ provider,
862
+ env: resolvePluginCommandEnv(manifest),
863
+ headerTransforms,
864
+ expiresAt: new Date(Date.now() + MAX_LEASE_MS$1).toISOString(),
865
+ metadata: { reason: input.reason }
866
+ };
867
+ } };
868
+ }
869
+ //#endregion
870
+ //#region src/chat/plugins/auth/oauth-request.ts
871
+ const DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
872
+ function requireNonEmptyTokenField(data, field) {
873
+ const value = data[field];
874
+ if (typeof value !== "string" || !value.trim()) throw new Error(`OAuth token response missing ${field}`);
875
+ return value;
876
+ }
877
+ function requireTokenResponseObject(data) {
878
+ if (!data || typeof data !== "object" || Array.isArray(data)) throw new Error("OAuth token response must be an object");
879
+ return data;
880
+ }
881
+ const parsedOAuthTokenResponseSchema = z.object({
882
+ accessToken: z.string().min(1),
883
+ refreshToken: z.string().min(1),
884
+ expiresAt: z.number().positive().optional(),
885
+ refreshTokenExpiresAt: z.number().positive().optional(),
886
+ scope: z.string().min(1).optional()
887
+ }).strict();
888
+ function contentTypeToBody(contentType, payload) {
889
+ const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
890
+ if (!mediaType || mediaType === DEFAULT_TOKEN_CONTENT_TYPE) return new URLSearchParams(payload);
891
+ if (mediaType === "application/json" || mediaType.endsWith("+json")) return JSON.stringify(payload);
892
+ throw new Error(`Unsupported OAuth token Content-Type: ${contentType}`);
893
+ }
894
+ function buildOAuthTokenRequest(input) {
895
+ const headers = new Headers({ Accept: "application/json" });
896
+ for (const [name, value] of Object.entries(input.tokenExtraHeaders ?? {})) headers.set(name, value);
897
+ if (!headers.has("Content-Type")) headers.set("Content-Type", DEFAULT_TOKEN_CONTENT_TYPE);
898
+ const payload = { ...input.payload };
899
+ if (input.tokenAuthMethod === "basic") headers.set("Authorization", `Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString("base64")}`);
900
+ else {
901
+ payload.client_id = input.clientId;
902
+ payload.client_secret = input.clientSecret;
903
+ }
904
+ const contentType = headers.get("Content-Type") ?? DEFAULT_TOKEN_CONTENT_TYPE;
905
+ const serializedHeaders = {};
906
+ headers.forEach((value, key) => {
907
+ serializedHeaders[key] = value;
908
+ });
909
+ return {
910
+ headers: serializedHeaders,
911
+ body: contentTypeToBody(contentType, payload)
912
+ };
913
+ }
914
+ /** Parse provider tokens, retaining the request token when a refresh response omits rotation. */
915
+ function parseOAuthTokenResponse(data, requestedScope, options) {
916
+ const response = requireTokenResponseObject(data);
917
+ const accessToken = requireNonEmptyTokenField(response, "access_token");
918
+ const resolvedRefreshToken = response.refresh_token === void 0 ? options?.refreshToken : requireNonEmptyTokenField(response, "refresh_token");
919
+ if (!resolvedRefreshToken) throw new Error("OAuth token response missing refresh_token");
920
+ const expiresIn = response.expires_in;
921
+ const refreshTokenExpiresIn = response.refresh_token_expires_in;
922
+ const responseScope = response.scope;
923
+ let scope;
924
+ if (responseScope !== void 0) {
925
+ if (typeof responseScope !== "string") throw new Error("OAuth token response returned invalid scope");
926
+ const normalized = normalizeOAuthScope(responseScope);
927
+ if (normalized !== void 0) scope = normalized;
928
+ else if (options?.treatEmptyScopeAsUnreported) scope = normalizeOAuthScope(requestedScope);
929
+ else throw new Error("OAuth token response returned empty scope");
930
+ } else scope = normalizeOAuthScope(requestedScope);
931
+ const result = {
932
+ accessToken,
933
+ refreshToken: resolvedRefreshToken,
934
+ ...scope ? { scope } : void 0
935
+ };
936
+ if (expiresIn !== void 0) {
937
+ if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) throw new Error("OAuth token response returned invalid expires_in");
938
+ result.expiresAt = Date.now() + expiresIn * 1e3;
939
+ }
940
+ if (refreshTokenExpiresIn !== void 0) {
941
+ if (typeof refreshTokenExpiresIn !== "number" || !Number.isFinite(refreshTokenExpiresIn) || refreshTokenExpiresIn <= 0) throw new Error("OAuth token response returned invalid refresh_token_expires_in");
942
+ result.refreshTokenExpiresAt = Date.now() + refreshTokenExpiresIn * 1e3;
943
+ }
944
+ return parsedOAuthTokenResponseSchema.parse(result);
945
+ }
946
+ //#endregion
947
+ //#region src/chat/plugins/auth/oauth-bearer-broker.ts
948
+ const MAX_LEASE_MS = 36e5;
949
+ const REFRESH_BUFFER_MS = 3e5;
950
+ const TOKEN_REFRESH_TIMEOUT_MS = 2e4;
951
+ var OAuthRefreshRejectedError = class extends Error {
952
+ constructor(message) {
953
+ super(message);
954
+ this.name = "OAuthRefreshRejectedError";
955
+ }
956
+ };
957
+ function parseRefreshError(text) {
958
+ if (!text.trim()) return;
959
+ try {
960
+ const parsed = JSON.parse(text);
961
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.error === "string" ? parsed.error : void 0;
962
+ } catch {
963
+ return;
964
+ }
965
+ }
966
+ async function refreshAccessToken(provider, refreshToken, oauth, requestedScope) {
967
+ const clientId = process.env[oauth.clientIdEnv]?.trim();
968
+ const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
969
+ if (!clientId || !clientSecret) throw new Error(`Missing ${oauth.clientIdEnv} or ${oauth.clientSecretEnv} for token refresh`);
970
+ const request = buildOAuthTokenRequest({
971
+ clientId,
972
+ clientSecret,
973
+ payload: {
974
+ grant_type: "refresh_token",
975
+ refresh_token: refreshToken
976
+ },
977
+ tokenAuthMethod: oauth.tokenAuthMethod,
978
+ tokenExtraHeaders: oauth.tokenExtraHeaders
979
+ });
980
+ const errorDetails = {
981
+ phase: "token_refresh",
982
+ provider,
983
+ resourceHost: new URL(oauth.tokenEndpoint).hostname
984
+ };
985
+ let response;
986
+ try {
987
+ response = await fetch(oauth.tokenEndpoint, {
988
+ method: "POST",
989
+ headers: request.headers,
990
+ body: request.body,
991
+ signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
992
+ });
993
+ } catch {
994
+ throw new OAuthProviderError(errorDetails);
995
+ }
996
+ if (!response.ok) {
997
+ let errorCode;
998
+ try {
999
+ errorCode = parseRefreshError(await readBoundedOAuthErrorBody(response));
1000
+ } catch {
1001
+ throw new OAuthProviderError({
1002
+ ...errorDetails,
1003
+ status: response.status
1004
+ });
1005
+ }
1006
+ if (errorCode === "invalid_grant" || errorCode === "bad_refresh_token") throw new OAuthRefreshRejectedError("Token refresh was rejected");
1007
+ throw new OAuthProviderError({
1008
+ ...errorDetails,
1009
+ status: response.status
1010
+ });
1011
+ }
1012
+ try {
1013
+ return parseOAuthTokenResponse(await response.json(), requestedScope, {
1014
+ treatEmptyScopeAsUnreported: oauth.treatEmptyScopeAsUnreported,
1015
+ refreshToken
1016
+ });
1017
+ } catch {
1018
+ throw new OAuthProviderError(errorDetails);
1019
+ }
1020
+ }
1021
+ function getLeaseExpiry(expiresAt) {
1022
+ return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS) : Date.now() + MAX_LEASE_MS;
1023
+ }
1024
+ function canUseStoredToken(stored) {
1025
+ return stored !== void 0 && (stored.expiresAt === void 0 || stored.expiresAt > Date.now());
1026
+ }
1027
+ function shouldRefreshStoredToken(stored) {
1028
+ return stored !== void 0 && stored.expiresAt !== void 0 && stored.expiresAt - Date.now() < REFRESH_BUFFER_MS;
1029
+ }
1030
+ function createOAuthBearerBroker(manifest, credentials, deps) {
1031
+ const provider = manifest.name;
1032
+ const { domains, apiHeaders, authTokenEnv } = credentials;
1033
+ const authTokenPlaceholder = resolveAuthTokenPlaceholder(credentials);
1034
+ const pluginHeaderTransforms = () => resolveApiHeaderTransforms(manifest);
1035
+ function buildLease(token, expiresAtMs, reason) {
1036
+ return {
1037
+ id: randomUUID(),
1038
+ provider,
1039
+ env: {
1040
+ ...resolvePluginCommandEnv(manifest),
1041
+ [authTokenEnv]: authTokenPlaceholder
1042
+ },
1043
+ headerTransforms: mergeHeaderTransforms([...pluginHeaderTransforms(), ...domains.map((domain) => ({
1044
+ domain,
1045
+ headers: {
1046
+ ...apiHeaders ?? {},
1047
+ Authorization: `Bearer ${token}`
1048
+ }
1049
+ }))]),
1050
+ expiresAt: new Date(expiresAtMs).toISOString(),
1051
+ metadata: { reason }
1052
+ };
1053
+ }
1054
+ return { async issue(input) {
1055
+ const envToken = process.env[authTokenEnv]?.trim();
1056
+ const oauth = manifest.oauth;
1057
+ const userSubjectId = credentialUserSubjectId(input.context);
1058
+ if (!oauth) {
1059
+ if (envToken) return buildLease(envToken, Date.now() + MAX_LEASE_MS, input.reason);
1060
+ throw new CredentialUnavailableError(provider, `No ${provider} credentials available.`);
1061
+ }
1062
+ if (userSubjectId) {
1063
+ const stored = await deps.userTokenStore.get(userSubjectId, provider);
1064
+ if (stored) {
1065
+ if (!hasRequiredOAuthScope(stored.scope, oauth.scope)) throw new CredentialUnavailableError(provider, `Your ${provider} connection needs to be reauthorized.`);
1066
+ if (shouldRefreshStoredToken(stored)) try {
1067
+ return await deps.userTokenStore.withRefresh(userSubjectId, provider, async () => {
1068
+ const latest = await deps.userTokenStore.get(userSubjectId, provider);
1069
+ if (latest && !hasRequiredOAuthScope(latest.scope, oauth.scope)) throw new CredentialUnavailableError(provider, `Your ${provider} connection needs to be reauthorized.`);
1070
+ if (!shouldRefreshStoredToken(latest) && canUseStoredToken(latest)) return buildLease(latest.accessToken, getLeaseExpiry(latest.expiresAt), input.reason);
1071
+ if (!latest) throw new CredentialUnavailableError(provider, `No ${provider} credentials available.`);
1072
+ const refreshed = await refreshAccessToken(provider, latest.refreshToken, oauth, latest.scope ?? oauth.scope);
1073
+ if (!hasRequiredOAuthScope(refreshed.scope, oauth.scope)) throw new CredentialUnavailableError(provider, `Your ${provider} connection needs to be reauthorized.`);
1074
+ const refreshedTokens = {
1075
+ ...latest.refreshTokenExpiresAt ? { refreshTokenExpiresAt: latest.refreshTokenExpiresAt } : void 0,
1076
+ ...refreshed,
1077
+ ...latest.account ? { account: latest.account } : void 0
1078
+ };
1079
+ await deps.userTokenStore.set(userSubjectId, provider, refreshedTokens);
1080
+ return buildLease(refreshed.accessToken, getLeaseExpiry(refreshed.expiresAt), input.reason);
1081
+ });
1082
+ } catch (error) {
1083
+ if (error instanceof CredentialUnavailableError) throw error;
1084
+ if (error instanceof OAuthRefreshRejectedError) throw new CredentialUnavailableError(provider, `Your ${provider} connection has expired.`);
1085
+ throw error;
1086
+ }
1087
+ if (canUseStoredToken(stored)) return buildLease(stored.accessToken, getLeaseExpiry(stored.expiresAt), input.reason);
1088
+ throw new CredentialUnavailableError(provider, `Your ${provider} connection has expired.`);
1089
+ }
1090
+ throw new CredentialUnavailableError(provider, `No ${provider} credentials available.`);
1091
+ }
1092
+ if (envToken) return buildLease(envToken, getLeaseExpiry(), input.reason);
1093
+ throw new CredentialUnavailableError(provider, `No ${provider} credentials available.`);
1094
+ } };
1095
+ }
1096
+ //#endregion
1097
+ //#region src/chat/plugins/registry.ts
1098
+ function getLoggedPluginNames() {
1099
+ const globalState = globalThis;
1100
+ globalState.__juniorLoggedPluginNames ??= /* @__PURE__ */ new Set();
1101
+ return globalState.__juniorLoggedPluginNames;
1102
+ }
1103
+ function createLoadedPluginState(signature) {
1104
+ return {
1105
+ signature,
1106
+ pluginDefinitions: [],
1107
+ pluginMigrationRoots: /* @__PURE__ */ new Map(),
1108
+ domainToPlugin: /* @__PURE__ */ new Map(),
1109
+ pluginConfigKeys: /* @__PURE__ */ new Set(),
1110
+ pluginsByName: /* @__PURE__ */ new Map(),
1111
+ packageSkillRoots: /* @__PURE__ */ new Set()
1112
+ };
1113
+ }
1114
+ function providerDomains(manifest) {
1115
+ return [.../* @__PURE__ */ new Set([...manifest.credentials?.domains ?? [], ...manifest.domains ?? []])].sort((left, right) => left.localeCompare(right));
1116
+ }
1117
+ function registerPluginManifest(state, manifest, pluginDir, skillsDir, migrationsDir) {
1118
+ if (state.pluginsByName.has(manifest.name)) throw new Error(`Duplicate plugin name "${manifest.name}"`);
1119
+ for (const domain of providerDomains(manifest)) {
1120
+ const owner = state.domainToPlugin.get(domain);
1121
+ if (owner) throw new Error(`Duplicate provider domain "${domain}" in plugin "${manifest.name}" already declared by plugin "${owner}". Use plugins.manifests in PluginCatalogConfig to change one plugin's domains or credentials.`);
1122
+ }
1123
+ const definition = {
1124
+ manifest,
1125
+ dir: pluginDir,
1126
+ ...migrationsDir ? { migrationsDir } : void 0,
1127
+ ...skillsDir ? { skillsDir } : void 0
1128
+ };
1129
+ state.pluginDefinitions.push(definition);
1130
+ state.pluginsByName.set(manifest.name, definition);
1131
+ if (definition.migrationsDir) state.pluginMigrationRoots.set(manifest.name, definition.migrationsDir);
1132
+ for (const key of manifest.configKeys) state.pluginConfigKeys.add(key);
1133
+ for (const domain of providerDomains(manifest)) state.domainToPlugin.set(domain, manifest.name);
1134
+ }
1135
+ function registerYamlPluginManifest(state, raw, pluginDir, config) {
1136
+ const manifest = parsePluginManifest(raw, pluginDir, config);
1137
+ const candidateSkillsDir = path.join(pluginDir, "skills");
1138
+ registerPluginManifest(state, manifest, pluginDir, (() => {
1139
+ try {
1140
+ return statSync(candidateSkillsDir).isDirectory();
1141
+ } catch {
1142
+ return false;
1143
+ }
1144
+ })() ? candidateSkillsDir : void 0);
1145
+ }
1146
+ function normalizePluginRoots(roots) {
1147
+ const resolved = [];
1148
+ const seen = /* @__PURE__ */ new Set();
1149
+ for (const root of roots) {
1150
+ const normalized = path.resolve(root);
1151
+ if (seen.has(normalized)) continue;
1152
+ seen.add(normalized);
1153
+ resolved.push(normalized);
1154
+ }
1155
+ return resolved;
1156
+ }
1157
+ function getPluginCatalogSource(runtime) {
1158
+ const packagedContent = discoverConfiguredPluginPackageContent(runtime.pluginConfig);
1159
+ const manifestRoots = normalizePluginRoots([...normalizePluginRoots(pluginRoots()), ...packagedContent.manifestRoots]);
1160
+ const packagedSkillRoots = normalizePluginRoots(packagedContent.skillRoots);
1161
+ const inlineManifests = runtime.pluginConfig?.inlineManifests ?? [];
1162
+ return {
1163
+ inlineManifests,
1164
+ manifestRoots,
1165
+ packagedSkillRoots,
1166
+ packagedContent,
1167
+ signature: JSON.stringify({
1168
+ inlineManifests,
1169
+ manifestRoots,
1170
+ packages: packagedContent.packages.map((pkg) => ({
1171
+ dir: path.resolve(pkg.dir),
1172
+ hasMigrationsDir: pkg.hasMigrationsDir,
1173
+ hasSkillsDir: pkg.hasSkillsDir,
1174
+ packageName: pkg.packageName
1175
+ })).sort((left, right) => left.packageName.localeCompare(right.packageName)),
1176
+ packagedSkillRoots,
1177
+ packageNames: [...packagedContent.packageNames].sort(),
1178
+ pluginConfig: runtime.pluginConfig ?? {}
1179
+ })
1180
+ };
1181
+ }
1182
+ function normalizePluginCatalogConfig(config) {
1183
+ if (!config) return;
1184
+ return {
1185
+ inlineManifests: config.inlineManifests ? structuredClone(config.inlineManifests) : void 0,
1186
+ packages: normalizePluginPackageNames(config.packages),
1187
+ ...config.manifests ? { manifests: structuredClone(config.manifests) } : void 0
1188
+ };
1189
+ }
1190
+ function clonePluginCatalogConfig(config) {
1191
+ if (!config) return;
1192
+ return {
1193
+ ...config.inlineManifests ? { inlineManifests: structuredClone(config.inlineManifests) } : void 0,
1194
+ packages: [...config.packages ?? []],
1195
+ ...config.manifests ? { manifests: structuredClone(config.manifests) } : void 0
1196
+ };
1197
+ }
1198
+ function packageContentByName(packagedContent, packageName) {
1199
+ return packagedContent.packages.find((pkg) => pkg.packageName === packageName);
1200
+ }
1201
+ function registerInlineManifests(state, source, config) {
1202
+ const migrationOwners = /* @__PURE__ */ new Map();
1203
+ for (const definition of source.inlineManifests) {
1204
+ const pkg = definition.packageName ? packageContentByName(source.packagedContent, definition.packageName) : void 0;
1205
+ const dir = pkg?.dir ?? process.cwd();
1206
+ const skillsDir = pkg?.hasSkillsDir ? path.join(pkg.dir, "skills") : void 0;
1207
+ const migrationsDir = pkg?.hasMigrationsDir && statSync(path.join(pkg.dir, "migrations"), { throwIfNoEntry: false })?.isDirectory() ? path.join(pkg.dir, "migrations") : void 0;
1208
+ const manifest = parseInlinePluginManifest(definition.manifest, dir, config);
1209
+ if (migrationsDir) {
1210
+ const owner = migrationOwners.get(migrationsDir);
1211
+ if (owner) throw new Error(`Plugin "${manifest.name}" cannot share migrations directory with plugin "${owner}"`);
1212
+ migrationOwners.set(migrationsDir, manifest.name);
1213
+ }
1214
+ registerPluginManifest(state, manifest, dir, skillsDir, migrationsDir);
1215
+ }
1216
+ }
1217
+ function discoverConfiguredPluginPackageContent(config) {
1218
+ return discoverInstalledPluginPackageContent(process.cwd(), { packageNames: config?.packages });
1219
+ }
1220
+ function buildLoadedPluginState(source, config) {
1221
+ const state = createLoadedPluginState(source.signature);
1222
+ for (const skillRoot of source.packagedSkillRoots) state.packageSkillRoots.add(skillRoot);
1223
+ registerInlineManifests(state, source, config);
1224
+ const roots = source.manifestRoots;
1225
+ for (const pluginsRoot of roots) {
1226
+ let entries;
1227
+ let rootStat;
1228
+ try {
1229
+ rootStat = statSync(pluginsRoot);
1230
+ } catch (error) {
1231
+ logWarn("plugin.root.read.failed", {
1232
+ "file.directory": pluginsRoot,
1233
+ "exception.message": error instanceof Error ? error.message : String(error)
1234
+ });
1235
+ continue;
1236
+ }
1237
+ if (rootStat.isDirectory()) {
1238
+ const manifestPath = path.join(pluginsRoot, "plugin.yaml");
1239
+ let hasRootManifest = false;
1240
+ try {
1241
+ hasRootManifest = statSync(manifestPath).isFile();
1242
+ } catch {
1243
+ hasRootManifest = false;
1244
+ }
1245
+ if (hasRootManifest) {
1246
+ registerYamlPluginManifest(state, readFileSync(manifestPath, "utf8"), pluginsRoot, config);
1247
+ continue;
1248
+ }
1249
+ }
1250
+ try {
1251
+ entries = readdirSync(pluginsRoot);
1252
+ } catch (error) {
1253
+ logWarn("plugin.root.read.failed", {
1254
+ "file.directory": pluginsRoot,
1255
+ "exception.message": error instanceof Error ? error.message : String(error)
1256
+ });
1257
+ continue;
1258
+ }
1259
+ for (const entry of entries.sort()) {
1260
+ const pluginDir = path.join(pluginsRoot, entry);
1261
+ try {
1262
+ if (!statSync(pluginDir).isDirectory()) continue;
1263
+ } catch {
1264
+ continue;
1265
+ }
1266
+ const manifestPath = path.join(pluginDir, "plugin.yaml");
1267
+ let raw;
1268
+ try {
1269
+ raw = readFileSync(manifestPath, "utf8");
1270
+ } catch {
1271
+ continue;
1272
+ }
1273
+ registerYamlPluginManifest(state, raw, pluginDir, config);
1274
+ }
1275
+ }
1276
+ for (const name of Object.keys(config?.manifests ?? {})) if (!state.pluginsByName.has(name)) throw new Error(`plugins.manifests.${name} does not match a loaded plugin`);
1277
+ return state;
1278
+ }
1279
+ function logLoadedPlugins(state) {
1280
+ const loggedPluginNames = getLoggedPluginNames();
1281
+ for (const plugin of [...state.pluginDefinitions].sort((left, right) => left.manifest.name.localeCompare(right.manifest.name))) {
1282
+ if (loggedPluginNames.has(plugin.manifest.name)) continue;
1283
+ loggedPluginNames.add(plugin.manifest.name);
1284
+ logInfo("plugin.loaded", {
1285
+ "app.plugin.name": plugin.manifest.name,
1286
+ "app.plugin.config_key_count": plugin.manifest.configKeys.length,
1287
+ "app.plugin.has_mcp": Boolean(plugin.manifest.mcp),
1288
+ "file.directory": plugin.dir,
1289
+ ...plugin.skillsDir ? { "app.file.skill_directory": plugin.skillsDir } : void 0
1290
+ });
1291
+ }
1292
+ }
1293
+ function ensurePluginsLoaded(runtime) {
1294
+ const source = getPluginCatalogSource(runtime);
1295
+ if (runtime.loadedPluginState?.signature === source.signature) return runtime.loadedPluginState;
1296
+ const state = buildLoadedPluginState(source, runtime.pluginConfig);
1297
+ runtime.loadedPluginState = state;
1298
+ logLoadedPlugins(state);
1299
+ return state;
1300
+ }
1301
+ /** Create an isolated plugin catalog runtime for explicit app wiring. */
1302
+ function createPluginCatalogRuntime() {
1303
+ const runtime = {};
1304
+ return {
1305
+ setConfig(config) {
1306
+ const previousConfig = clonePluginCatalogConfig(runtime.pluginConfig);
1307
+ runtime.pluginConfig = normalizePluginCatalogConfig(config);
1308
+ return previousConfig;
1309
+ },
1310
+ parseConfiguredInlineManifest(manifest, dir) {
1311
+ return parseInlinePluginManifest(manifest, dir, runtime.pluginConfig);
1312
+ },
1313
+ getPackageContent() {
1314
+ return discoverConfiguredPluginPackageContent(runtime.pluginConfig);
1315
+ },
1316
+ getSignature() {
1317
+ return ensurePluginsLoaded(runtime).signature;
1318
+ },
1319
+ getProviders() {
1320
+ return [...ensurePluginsLoaded(runtime).pluginDefinitions];
1321
+ },
1322
+ getMigrationRoots() {
1323
+ return [...ensurePluginsLoaded(runtime).pluginMigrationRoots.entries()].map(([pluginName, dir]) => ({
1324
+ pluginName,
1325
+ dir
1326
+ })).sort((left, right) => left.pluginName.localeCompare(right.pluginName));
1327
+ },
1328
+ getMcpProviders() {
1329
+ return ensurePluginsLoaded(runtime).pluginDefinitions.filter((plugin) => Boolean(plugin.manifest.mcp));
1330
+ },
1331
+ getRuntimeDependencies() {
1332
+ const state = ensurePluginsLoaded(runtime);
1333
+ const seen = /* @__PURE__ */ new Set();
1334
+ const deps = [];
1335
+ for (const plugin of state.pluginDefinitions) for (const dep of plugin.manifest.runtimeDependencies ?? []) {
1336
+ const key = dep.type === "npm" ? `${dep.type}:${dep.package}:${dep.version}` : "package" in dep ? `${dep.type}:package:${dep.package}` : `${dep.type}:url:${dep.url}:${dep.sha256}`;
1337
+ if (seen.has(key)) continue;
1338
+ seen.add(key);
1339
+ deps.push(dep);
1340
+ }
1341
+ return deps.sort((left, right) => {
1342
+ if (left.type !== right.type) return left.type.localeCompare(right.type);
1343
+ const leftIdentity = "package" in left ? `package:${left.package}` : `url:${left.url}:${left.sha256}`;
1344
+ const rightIdentity = "package" in right ? `package:${right.package}` : `url:${right.url}:${right.sha256}`;
1345
+ if (leftIdentity !== rightIdentity) return leftIdentity.localeCompare(rightIdentity);
1346
+ if (left.type === "npm" && right.type === "npm") return left.version.localeCompare(right.version);
1347
+ return 0;
1348
+ });
1349
+ },
1350
+ getRuntimePostinstall() {
1351
+ const state = ensurePluginsLoaded(runtime);
1352
+ const commands = [];
1353
+ for (const plugin of state.pluginDefinitions) for (const command of plugin.manifest.runtimePostinstall ?? []) commands.push({
1354
+ cmd: command.cmd,
1355
+ ...command.args ? { args: [...command.args] } : void 0,
1356
+ ...command.sudo !== void 0 ? { sudo: command.sudo } : void 0
1357
+ });
1358
+ return commands;
1359
+ },
1360
+ getOAuthConfig(provider) {
1361
+ const plugin = ensurePluginsLoaded(runtime).pluginsByName.get(provider);
1362
+ if (!plugin?.manifest.oauth) return void 0;
1363
+ const oauth = plugin.manifest.oauth;
1364
+ return {
1365
+ clientIdEnv: oauth.clientIdEnv,
1366
+ clientSecretEnv: oauth.clientSecretEnv,
1367
+ authorizeEndpoint: oauth.authorizeEndpoint,
1368
+ tokenEndpoint: oauth.tokenEndpoint,
1369
+ ...oauth.scope ? { scope: oauth.scope } : void 0,
1370
+ ...oauth.authorizeParams ? { authorizeParams: { ...oauth.authorizeParams } } : void 0,
1371
+ ...oauth.tokenAuthMethod ? { tokenAuthMethod: oauth.tokenAuthMethod } : void 0,
1372
+ ...oauth.tokenExtraHeaders ? { tokenExtraHeaders: { ...oauth.tokenExtraHeaders } } : void 0,
1373
+ ...oauth.treatEmptyScopeAsUnreported ? { treatEmptyScopeAsUnreported: true } : void 0,
1374
+ callbackPath: `/api/oauth/callback/${plugin.manifest.name}`
1375
+ };
1376
+ },
1377
+ getSkillRoots() {
1378
+ const state = ensurePluginsLoaded(runtime);
1379
+ return [.../* @__PURE__ */ new Set([...state.pluginDefinitions.flatMap((plugin) => plugin.skillsDir ? [plugin.skillsDir] : []), ...state.packageSkillRoots])];
1380
+ },
1381
+ getForSkillPath(skillPath) {
1382
+ const state = ensurePluginsLoaded(runtime);
1383
+ const resolvedSkillPath = path.resolve(skillPath);
1384
+ return state.pluginDefinitions.find((plugin) => {
1385
+ if (!plugin.skillsDir) return false;
1386
+ const resolvedSkillsDir = path.resolve(plugin.skillsDir);
1387
+ return resolvedSkillPath === resolvedSkillsDir || resolvedSkillPath.startsWith(`${resolvedSkillsDir}${path.sep}`);
1388
+ });
1389
+ },
1390
+ getDefinition(provider) {
1391
+ return ensurePluginsLoaded(runtime).pluginsByName.get(provider);
1392
+ },
1393
+ getDisplayName(provider) {
1394
+ return ensurePluginsLoaded(runtime).pluginsByName.get(provider)?.manifest.displayName;
1395
+ },
1396
+ isProvider(provider) {
1397
+ return ensurePluginsLoaded(runtime).pluginsByName.has(provider);
1398
+ },
1399
+ isConfigKey(key) {
1400
+ return ensurePluginsLoaded(runtime).pluginConfigKeys.has(key);
1401
+ },
1402
+ createBroker(provider, deps) {
1403
+ const plugin = ensurePluginsLoaded(runtime).pluginsByName.get(provider);
1404
+ if (!plugin) throw new Error(`Unknown plugin provider: "${provider}"`);
1405
+ const { credentials, name } = plugin.manifest;
1406
+ if (!credentials && !plugin.manifest.apiHeaders) throw new Error(`Provider "${name}" has no credentials or API headers configured`);
1407
+ let broker;
1408
+ if (!credentials) broker = createApiHeadersBroker(plugin.manifest);
1409
+ else broker = createOAuthBearerBroker(plugin.manifest, credentials, deps);
1410
+ setSpanAttributes({
1411
+ "app.plugin.name": name,
1412
+ "app.plugin.has_oauth": Boolean(plugin.manifest.oauth)
1413
+ });
1414
+ return broker;
1415
+ }
1416
+ };
1417
+ }
1418
+ //#endregion
1419
+ export { resolvePluginCommandEnv as a, getOAuthProviderErrorAttributes as c, parseInlinePluginManifest as d, parsePluginManifest as f, resolveAuthTokenPlaceholder as i, hasRequiredOAuthScope as l, buildOAuthTokenRequest as n, OAuthProviderError as o, parseOAuthTokenResponse as r, fetchWithBoundedOAuthErrorBodies as s, createPluginCatalogRuntime as t, CredentialUnavailableError as u };