nexus-agents 2.95.0 → 2.97.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 (120) hide show
  1. package/README.md +4 -4
  2. package/dist/{child-mcp-config-NW7JMSDK.js → child-mcp-config-WVPJIK2J.js} +2 -2
  3. package/dist/chunk-5LGAPYX7.js +2133 -0
  4. package/dist/chunk-5LGAPYX7.js.map +1 -0
  5. package/dist/{chunk-LXROEQAO.js → chunk-65LE457J.js} +2 -2
  6. package/dist/{chunk-47Z3YPOK.js → chunk-674NONPG.js} +2 -2
  7. package/dist/{chunk-2X24T4ZD.js → chunk-6IN4MKDA.js} +7 -7
  8. package/dist/{chunk-FYGZ4MEQ.js → chunk-76FTBRUS.js} +415 -1410
  9. package/dist/chunk-76FTBRUS.js.map +1 -0
  10. package/dist/{chunk-YYXJ6RTO.js → chunk-7BLBXITB.js} +3 -3
  11. package/dist/{chunk-GC5FH2IA.js → chunk-APLZRTYC.js} +7 -7
  12. package/dist/{chunk-TEKWQNQ2.js → chunk-D3OT7TOA.js} +3 -3
  13. package/dist/{chunk-3HXWI34V.js → chunk-E43RUPLP.js} +3 -3
  14. package/dist/{chunk-LZOTH2GI.js → chunk-EY7LL4F4.js} +2 -2
  15. package/dist/{chunk-5HBDXW44.js → chunk-FDTIRCSL.js} +2 -2
  16. package/dist/{chunk-VTMQND6B.js → chunk-GAIUXYF4.js} +366 -2470
  17. package/dist/chunk-GAIUXYF4.js.map +1 -0
  18. package/dist/{chunk-D3JSXVX3.js → chunk-GERVGPTF.js} +2 -2
  19. package/dist/{chunk-6IWPN5UB.js → chunk-HIWO2WWP.js} +2 -2
  20. package/dist/{chunk-WGA4VGIR.js → chunk-HWCMNFIS.js} +3 -3
  21. package/dist/{chunk-G42N55ZR.js → chunk-HZGHPBTV.js} +2 -2
  22. package/dist/{chunk-KHZGMF2W.js → chunk-IHKPGTF3.js} +4 -4
  23. package/dist/{chunk-ECBH5INZ.js → chunk-JTQLV374.js} +71 -6
  24. package/dist/chunk-JTQLV374.js.map +1 -0
  25. package/dist/{chunk-PFOY5ML6.js → chunk-JX6VL7JF.js} +3 -3
  26. package/dist/{chunk-4NVYN2MB.js → chunk-KKVH3PDK.js} +4 -4
  27. package/dist/{chunk-3H6Q3RZM.js → chunk-ML4IF3PS.js} +4 -4
  28. package/dist/{chunk-QUPPV6UO.js → chunk-N5656LE3.js} +2 -2
  29. package/dist/{chunk-FXVJLJUF.js → chunk-NTBVUWU4.js} +2 -2
  30. package/dist/{chunk-ZICVVBDW.js → chunk-OKRTOMKX.js} +2 -2
  31. package/dist/{chunk-Y3OOHHKQ.js → chunk-OSHCOBDK.js} +2 -2
  32. package/dist/chunk-P2BKWVKF.js +1095 -0
  33. package/dist/chunk-P2BKWVKF.js.map +1 -0
  34. package/dist/{chunk-FTDVA33Z.js → chunk-SAUBPHQ7.js} +4 -4
  35. package/dist/{chunk-55G7KQ7F.js → chunk-SWNOQW7Y.js} +5 -5
  36. package/dist/{chunk-O4LVC4MQ.js → chunk-SZNNT2DH.js} +2 -2
  37. package/dist/{chunk-ECZW5FXN.js → chunk-T5TYWM5S.js} +2 -2
  38. package/dist/{chunk-7GUP6KQQ.js → chunk-UNOOL6EN.js} +2 -2
  39. package/dist/{chunk-BSHDI4BH.js → chunk-WXDWDGVL.js} +2 -2
  40. package/dist/{cli-circuit-breaker-LNU5DQSB.js → cli-circuit-breaker-STKW4DTL.js} +4 -4
  41. package/dist/cli.js +149 -52
  42. package/dist/cli.js.map +1 -1
  43. package/dist/{composite-router-DJ54ND5X.js → composite-router-POFAM7T4.js} +2 -2
  44. package/dist/{consensus-vote-XLEQFVE7.js → consensus-vote-EQAYX53Z.js} +13 -12
  45. package/dist/{context-retriever-6O6TREXH.js → context-retriever-H22L3WFV.js} +6 -6
  46. package/dist/{doctor-deep-PSNJPELP.js → doctor-deep-HGAC6N5Y.js} +3 -3
  47. package/dist/{expert-bridge-Q3OWPD4L.js → expert-bridge-IAQPOJAQ.js} +4 -4
  48. package/dist/{factory-PSMTMC7V.js → factory-P2SQS7UM.js} +5 -5
  49. package/dist/{factory-43RVPRT5.js → factory-ZIZYUDKH.js} +5 -5
  50. package/dist/improvement-review-G2HAMGZP.js +27 -0
  51. package/dist/index.d.ts +8 -1
  52. package/dist/index.js +52 -49
  53. package/dist/index.js.map +1 -1
  54. package/dist/{init-opencode-25AUARJH.js → init-opencode-RHCYZDZW.js} +5 -5
  55. package/dist/{issue-triage-KZTENUDH.js → issue-triage-VVNQIQW5.js} +5 -5
  56. package/dist/{pr-reviewer-helpers-XENJ4E2T.js → pr-reviewer-helpers-UN7RDPDN.js} +4 -4
  57. package/dist/{registry-command-X3O34FAK.js → registry-command-OVRRKYHO.js} +2 -2
  58. package/dist/{repo-security-plan-XCX7EFJR.js → repo-security-plan-X74TBYOV.js} +3 -3
  59. package/dist/{research-helpers-synthesize-CGVR6M4S.js → research-helpers-synthesize-OSP6FGNV.js} +3 -3
  60. package/dist/{routing-memory-YZRYGXQC.js → routing-memory-5T5QJF47.js} +2 -2
  61. package/dist/{session-memory-SXQAA2EN.js → session-memory-X7CJ6X4A.js} +3 -3
  62. package/dist/{setup-command-VG7JZI6O.js → setup-command-ZWJCGPJ5.js} +11 -11
  63. package/dist/{setup-config-VJAK6AA2.js → setup-config-YVIKUNOB.js} +3 -3
  64. package/dist/{setup-custom-api-KSF2JMTS.js → setup-custom-api-FLV4SELI.js} +4 -4
  65. package/dist/{tool-memory-L7SDFSF5.js → tool-memory-QNEAKRKV.js} +5 -5
  66. package/dist/{weather-report-D6HFOK7S.js → weather-report-SUVIXWKI.js} +2 -2
  67. package/dist/weather-report-SUVIXWKI.js.map +1 -0
  68. package/package.json +12 -2
  69. package/dist/chunk-ECBH5INZ.js.map +0 -1
  70. package/dist/chunk-FYGZ4MEQ.js.map +0 -1
  71. package/dist/chunk-VTMQND6B.js.map +0 -1
  72. /package/dist/{child-mcp-config-NW7JMSDK.js.map → child-mcp-config-WVPJIK2J.js.map} +0 -0
  73. /package/dist/{chunk-LXROEQAO.js.map → chunk-65LE457J.js.map} +0 -0
  74. /package/dist/{chunk-47Z3YPOK.js.map → chunk-674NONPG.js.map} +0 -0
  75. /package/dist/{chunk-2X24T4ZD.js.map → chunk-6IN4MKDA.js.map} +0 -0
  76. /package/dist/{chunk-YYXJ6RTO.js.map → chunk-7BLBXITB.js.map} +0 -0
  77. /package/dist/{chunk-GC5FH2IA.js.map → chunk-APLZRTYC.js.map} +0 -0
  78. /package/dist/{chunk-TEKWQNQ2.js.map → chunk-D3OT7TOA.js.map} +0 -0
  79. /package/dist/{chunk-3HXWI34V.js.map → chunk-E43RUPLP.js.map} +0 -0
  80. /package/dist/{chunk-LZOTH2GI.js.map → chunk-EY7LL4F4.js.map} +0 -0
  81. /package/dist/{chunk-5HBDXW44.js.map → chunk-FDTIRCSL.js.map} +0 -0
  82. /package/dist/{chunk-D3JSXVX3.js.map → chunk-GERVGPTF.js.map} +0 -0
  83. /package/dist/{chunk-6IWPN5UB.js.map → chunk-HIWO2WWP.js.map} +0 -0
  84. /package/dist/{chunk-WGA4VGIR.js.map → chunk-HWCMNFIS.js.map} +0 -0
  85. /package/dist/{chunk-G42N55ZR.js.map → chunk-HZGHPBTV.js.map} +0 -0
  86. /package/dist/{chunk-KHZGMF2W.js.map → chunk-IHKPGTF3.js.map} +0 -0
  87. /package/dist/{chunk-PFOY5ML6.js.map → chunk-JX6VL7JF.js.map} +0 -0
  88. /package/dist/{chunk-4NVYN2MB.js.map → chunk-KKVH3PDK.js.map} +0 -0
  89. /package/dist/{chunk-3H6Q3RZM.js.map → chunk-ML4IF3PS.js.map} +0 -0
  90. /package/dist/{chunk-QUPPV6UO.js.map → chunk-N5656LE3.js.map} +0 -0
  91. /package/dist/{chunk-FXVJLJUF.js.map → chunk-NTBVUWU4.js.map} +0 -0
  92. /package/dist/{chunk-ZICVVBDW.js.map → chunk-OKRTOMKX.js.map} +0 -0
  93. /package/dist/{chunk-Y3OOHHKQ.js.map → chunk-OSHCOBDK.js.map} +0 -0
  94. /package/dist/{chunk-FTDVA33Z.js.map → chunk-SAUBPHQ7.js.map} +0 -0
  95. /package/dist/{chunk-55G7KQ7F.js.map → chunk-SWNOQW7Y.js.map} +0 -0
  96. /package/dist/{chunk-O4LVC4MQ.js.map → chunk-SZNNT2DH.js.map} +0 -0
  97. /package/dist/{chunk-ECZW5FXN.js.map → chunk-T5TYWM5S.js.map} +0 -0
  98. /package/dist/{chunk-7GUP6KQQ.js.map → chunk-UNOOL6EN.js.map} +0 -0
  99. /package/dist/{chunk-BSHDI4BH.js.map → chunk-WXDWDGVL.js.map} +0 -0
  100. /package/dist/{cli-circuit-breaker-LNU5DQSB.js.map → cli-circuit-breaker-STKW4DTL.js.map} +0 -0
  101. /package/dist/{composite-router-DJ54ND5X.js.map → composite-router-POFAM7T4.js.map} +0 -0
  102. /package/dist/{consensus-vote-XLEQFVE7.js.map → consensus-vote-EQAYX53Z.js.map} +0 -0
  103. /package/dist/{context-retriever-6O6TREXH.js.map → context-retriever-H22L3WFV.js.map} +0 -0
  104. /package/dist/{doctor-deep-PSNJPELP.js.map → doctor-deep-HGAC6N5Y.js.map} +0 -0
  105. /package/dist/{expert-bridge-Q3OWPD4L.js.map → expert-bridge-IAQPOJAQ.js.map} +0 -0
  106. /package/dist/{factory-43RVPRT5.js.map → factory-P2SQS7UM.js.map} +0 -0
  107. /package/dist/{factory-PSMTMC7V.js.map → factory-ZIZYUDKH.js.map} +0 -0
  108. /package/dist/{issue-triage-KZTENUDH.js.map → improvement-review-G2HAMGZP.js.map} +0 -0
  109. /package/dist/{init-opencode-25AUARJH.js.map → init-opencode-RHCYZDZW.js.map} +0 -0
  110. /package/dist/{pr-reviewer-helpers-XENJ4E2T.js.map → issue-triage-VVNQIQW5.js.map} +0 -0
  111. /package/dist/{repo-security-plan-XCX7EFJR.js.map → pr-reviewer-helpers-UN7RDPDN.js.map} +0 -0
  112. /package/dist/{registry-command-X3O34FAK.js.map → registry-command-OVRRKYHO.js.map} +0 -0
  113. /package/dist/{research-helpers-synthesize-CGVR6M4S.js.map → repo-security-plan-X74TBYOV.js.map} +0 -0
  114. /package/dist/{routing-memory-YZRYGXQC.js.map → research-helpers-synthesize-OSP6FGNV.js.map} +0 -0
  115. /package/dist/{session-memory-SXQAA2EN.js.map → routing-memory-5T5QJF47.js.map} +0 -0
  116. /package/dist/{setup-command-VG7JZI6O.js.map → session-memory-X7CJ6X4A.js.map} +0 -0
  117. /package/dist/{setup-config-VJAK6AA2.js.map → setup-command-ZWJCGPJ5.js.map} +0 -0
  118. /package/dist/{tool-memory-L7SDFSF5.js.map → setup-config-YVIKUNOB.js.map} +0 -0
  119. /package/dist/{setup-custom-api-KSF2JMTS.js.map → setup-custom-api-FLV4SELI.js.map} +0 -0
  120. /package/dist/{weather-report-D6HFOK7S.js.map → tool-memory-QNEAKRKV.js.map} +0 -0
@@ -0,0 +1,2133 @@
1
+ import {
2
+ CACHE_TIMEOUTS,
3
+ MCP_TIMEOUTS,
4
+ SecurityError,
5
+ TIMEOUT_GUARD,
6
+ ValidationError,
7
+ createLogger,
8
+ createMetricsMiddleware,
9
+ err,
10
+ formatZodError,
11
+ getErrorMessage,
12
+ getTimeProvider,
13
+ ok
14
+ } from "./chunk-JTQLV374.js";
15
+ import {
16
+ getNexusDataDir
17
+ } from "./chunk-CH7QIDHQ.js";
18
+
19
+ // src/mcp/middleware/validation.ts
20
+ function validateToolInput(schema, args) {
21
+ const parsed = schema.safeParse(args);
22
+ if (parsed.success) {
23
+ return ok(parsed.data);
24
+ }
25
+ const message = formatZodError(parsed.error);
26
+ const validationError = new ValidationError(`Invalid tool input: ${message}`, {
27
+ context: {
28
+ issues: parsed.error.issues,
29
+ receivedType: typeof args
30
+ }
31
+ });
32
+ return err(validationError);
33
+ }
34
+ function createValidator(schema) {
35
+ return (args) => validateToolInput(schema, args);
36
+ }
37
+
38
+ // src/mcp/middleware/rate-limiter.ts
39
+ var DEFAULT_REFILL_INTERVAL_MS = CACHE_TIMEOUTS.rateLimitRefillMs;
40
+ var RateLimiter = class {
41
+ tokens;
42
+ capacity;
43
+ refillRate;
44
+ refillIntervalMs;
45
+ lastRefillTime;
46
+ logger;
47
+ name;
48
+ constructor(config) {
49
+ this.capacity = config.capacity;
50
+ this.refillRate = config.refillRate;
51
+ this.refillIntervalMs = config.refillIntervalMs ?? DEFAULT_REFILL_INTERVAL_MS;
52
+ this.tokens = this.capacity;
53
+ this.lastRefillTime = getTimeProvider().now();
54
+ this.name = config.name ?? "rate-limiter";
55
+ this.logger = config.logger ?? createLogger({ component: this.name });
56
+ this.logger.debug("Rate limiter initialized", {
57
+ capacity: this.capacity,
58
+ refillRate: this.refillRate,
59
+ refillIntervalMs: this.refillIntervalMs
60
+ });
61
+ }
62
+ /**
63
+ * Refills tokens based on elapsed time.
64
+ * Called automatically before each acquire attempt.
65
+ */
66
+ refill() {
67
+ const now = getTimeProvider().now();
68
+ const elapsed = now - this.lastRefillTime;
69
+ const intervals = Math.floor(elapsed / this.refillIntervalMs);
70
+ if (intervals > 0) {
71
+ const tokensToAdd = intervals * this.refillRate;
72
+ this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
73
+ this.lastRefillTime = now - elapsed % this.refillIntervalMs;
74
+ if (tokensToAdd > 0) {
75
+ this.logger.debug("Tokens refilled", {
76
+ added: tokensToAdd,
77
+ current: this.tokens
78
+ });
79
+ }
80
+ }
81
+ }
82
+ /**
83
+ * Attempts to acquire a token.
84
+ *
85
+ * @param count - Number of tokens to acquire (default: 1)
86
+ * @returns True if tokens were acquired, false if rate limited
87
+ */
88
+ tryAcquire(count = 1) {
89
+ this.refill();
90
+ if (this.tokens >= count) {
91
+ this.tokens -= count;
92
+ this.logger.debug("Token acquired", {
93
+ requested: count,
94
+ remaining: this.tokens
95
+ });
96
+ return true;
97
+ }
98
+ this.logger.warn("Rate limit exceeded", {
99
+ requested: count,
100
+ available: this.tokens
101
+ });
102
+ return false;
103
+ }
104
+ /**
105
+ * Gets the current state of the rate limiter.
106
+ *
107
+ * @returns The current rate limiter state
108
+ */
109
+ getState() {
110
+ this.refill();
111
+ const nextTokenMs = this.tokens > 0 ? 0 : this.refillIntervalMs - (getTimeProvider().now() - this.lastRefillTime);
112
+ return {
113
+ tokens: this.tokens,
114
+ capacity: this.capacity,
115
+ nextTokenMs: Math.max(0, nextTokenMs)
116
+ };
117
+ }
118
+ /**
119
+ * Resets the rate limiter to full capacity.
120
+ * Useful for testing or after configuration changes.
121
+ */
122
+ reset() {
123
+ this.tokens = this.capacity;
124
+ this.lastRefillTime = getTimeProvider().now();
125
+ this.logger.debug("Rate limiter reset", { tokens: this.tokens });
126
+ }
127
+ };
128
+ function createDefaultRateLimiter(name, logger) {
129
+ const config = {
130
+ capacity: 100,
131
+ refillRate: 10,
132
+ refillIntervalMs: 1e3
133
+ };
134
+ if (name !== void 0) {
135
+ config.name = name;
136
+ }
137
+ if (logger !== void 0) {
138
+ config.logger = logger;
139
+ }
140
+ return new RateLimiter(config);
141
+ }
142
+
143
+ // src/mcp/middleware/policy-types.ts
144
+ import { z } from "zod";
145
+ var PolicyError = class extends SecurityError {
146
+ decision;
147
+ constructor(message, decision) {
148
+ super(message, {
149
+ context: {
150
+ allowed: decision.allowed,
151
+ reason: decision.reason,
152
+ ruleName: decision.ruleName,
153
+ requiredArtifact: decision.requiredArtifact
154
+ }
155
+ });
156
+ this.name = "PolicyError";
157
+ this.decision = decision;
158
+ }
159
+ };
160
+ var PolicyConfigSchema = z.object({
161
+ defaultMode: z.enum(["read-only", "read-write"]).default("read-only"),
162
+ policyMode: z.enum(["enforce", "warn"]).default("enforce"),
163
+ allowedPaths: z.array(z.string()).default(["./"])
164
+ });
165
+
166
+ // src/mcp/middleware/policy-helpers.ts
167
+ function isPathSafe(targetPath, allowedPaths) {
168
+ const normalizedTarget = normalizePath(targetPath);
169
+ for (const allowed of allowedPaths) {
170
+ const normalizedAllowed = normalizePath(allowed);
171
+ if (normalizedTarget.startsWith(normalizedAllowed)) {
172
+ return true;
173
+ }
174
+ }
175
+ return false;
176
+ }
177
+ function normalizePath(p) {
178
+ let normalized = p.replace(/\/{1,100}$/, "");
179
+ if (normalized === ".") {
180
+ normalized = "";
181
+ } else if (normalized.startsWith("./")) {
182
+ normalized = normalized.slice(2);
183
+ }
184
+ if (!normalized.startsWith("/")) {
185
+ normalized = "/" + normalized;
186
+ }
187
+ return normalized;
188
+ }
189
+ function extractPathFromArgs(args) {
190
+ if (args === null || typeof args !== "object") {
191
+ return void 0;
192
+ }
193
+ const argsObj = args;
194
+ const pathFields = ["path", "filePath", "file_path", "directory", "dir", "target"];
195
+ for (const field of pathFields) {
196
+ const value = argsObj[field];
197
+ if (typeof value === "string") {
198
+ return value;
199
+ }
200
+ }
201
+ return void 0;
202
+ }
203
+
204
+ // src/mcp/middleware/policy-rules.ts
205
+ var MUTATION_TOOLS = /* @__PURE__ */ new Set([
206
+ "write_file",
207
+ "edit_file",
208
+ "delete_file",
209
+ "create_directory",
210
+ "remove_directory",
211
+ "execute_command",
212
+ "run_shell",
213
+ "bash",
214
+ "create_expert",
215
+ "run_workflow"
216
+ ]);
217
+ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
218
+ "read_file",
219
+ "list_directory",
220
+ "search_files",
221
+ "get_status",
222
+ "orchestrate",
223
+ "delegate_to_model"
224
+ ]);
225
+ function isMutationTool(toolName) {
226
+ if (MUTATION_TOOLS.has(toolName)) {
227
+ return true;
228
+ }
229
+ if (READ_ONLY_TOOLS.has(toolName)) {
230
+ return false;
231
+ }
232
+ return true;
233
+ }
234
+ var denyMutationsWithoutModeRule = {
235
+ name: "deny-mutations-without-mode",
236
+ description: "Blocks write operations unless mode is read-write",
237
+ check(ctx) {
238
+ if (ctx.mode === "read-write") {
239
+ return { allowed: true, reason: "Read-write mode enabled" };
240
+ }
241
+ if (isMutationTool(ctx.toolName)) {
242
+ return {
243
+ allowed: false,
244
+ reason: `Tool '${ctx.toolName}' is a mutation operation but mode is '${ctx.mode}'. Set mode to 'read-write' to enable.`
245
+ };
246
+ }
247
+ return { allowed: true, reason: "Read-only operation allowed" };
248
+ }
249
+ };
250
+ var safePathsRule = {
251
+ name: "safe-paths",
252
+ description: "Validates paths against allowed root directories",
253
+ check(ctx) {
254
+ const targetPath = extractPathFromArgs(ctx.args);
255
+ if (targetPath === void 0) {
256
+ return { allowed: true, reason: "No path argument found" };
257
+ }
258
+ if (targetPath.includes("..")) {
259
+ return {
260
+ allowed: false,
261
+ reason: `Path contains '..' which may indicate path traversal: ${targetPath}`
262
+ };
263
+ }
264
+ const allowedPaths = ctx.allowedPaths ?? ["./"];
265
+ if (!isPathSafe(targetPath, allowedPaths)) {
266
+ return {
267
+ allowed: false,
268
+ reason: `Path '${targetPath}' is outside allowed directories: ${allowedPaths.join(", ")}`
269
+ };
270
+ }
271
+ return { allowed: true, reason: "Path is within allowed directories" };
272
+ }
273
+ };
274
+
275
+ // src/mcp/middleware/policy.ts
276
+ var PolicyFirewall = class {
277
+ rules = [];
278
+ mode;
279
+ logger;
280
+ constructor(config) {
281
+ this.mode = config?.mode ?? "enforce";
282
+ this.logger = config?.logger ?? createLogger({ component: "policy-firewall" });
283
+ if (config?.rules) {
284
+ for (const rule of config.rules) {
285
+ this.rules.push(rule);
286
+ }
287
+ }
288
+ this.logger.debug("Policy firewall initialized", {
289
+ mode: this.mode,
290
+ ruleCount: this.rules.length
291
+ });
292
+ }
293
+ /**
294
+ * Evaluates all policy rules against the given context.
295
+ *
296
+ * Rules are evaluated in order. The first rule that denies stops
297
+ * evaluation and returns the denial decision.
298
+ *
299
+ * @param ctx - The policy context to evaluate
300
+ * @returns The policy decision
301
+ */
302
+ evaluate(ctx) {
303
+ this.logger.debug("Evaluating policy", {
304
+ toolName: ctx.toolName,
305
+ mode: ctx.mode,
306
+ ruleCount: this.rules.length
307
+ });
308
+ if (this.rules.length === 0) {
309
+ return this.allowWithReason(ctx, "No policy rules configured");
310
+ }
311
+ for (const rule of this.rules) {
312
+ const decision = rule.check(ctx);
313
+ if (!decision.allowed) {
314
+ return this.handleDenial(ctx, rule, decision);
315
+ }
316
+ }
317
+ return this.allowWithReason(ctx, "All policy rules passed");
318
+ }
319
+ /**
320
+ * Creates an allow decision with the given reason and logs it.
321
+ */
322
+ allowWithReason(ctx, reason) {
323
+ const decision = { allowed: true, reason };
324
+ this.logDecision(ctx, decision);
325
+ return decision;
326
+ }
327
+ /**
328
+ * Handles a rule denial, respecting warn mode if configured.
329
+ */
330
+ handleDenial(ctx, rule, decision) {
331
+ const denialDecision = {
332
+ ...decision,
333
+ ruleName: rule.name
334
+ };
335
+ this.logDecision(ctx, denialDecision);
336
+ if (this.mode === "warn") {
337
+ this.logger.warn("Policy denial overridden by warn mode", {
338
+ toolName: ctx.toolName,
339
+ ruleName: rule.name,
340
+ reason: decision.reason
341
+ });
342
+ return {
343
+ allowed: true,
344
+ reason: `[WARN MODE] Would be denied: ${decision.reason}`,
345
+ ruleName: rule.name
346
+ };
347
+ }
348
+ return denialDecision;
349
+ }
350
+ /**
351
+ * Adds a policy rule to the firewall.
352
+ *
353
+ * @param rule - The rule to add
354
+ */
355
+ addRule(rule) {
356
+ const existingIndex = this.rules.findIndex((r) => r.name === rule.name);
357
+ if (existingIndex >= 0) {
358
+ this.logger.warn("Replacing existing policy rule", { ruleName: rule.name });
359
+ this.rules[existingIndex] = rule;
360
+ } else {
361
+ this.rules.push(rule);
362
+ this.logger.debug("Policy rule added", { ruleName: rule.name });
363
+ }
364
+ }
365
+ /**
366
+ * Removes a policy rule by name.
367
+ *
368
+ * @param name - The name of the rule to remove
369
+ * @returns True if the rule was found and removed
370
+ */
371
+ removeRule(name) {
372
+ const index = this.rules.findIndex((r) => r.name === name);
373
+ if (index >= 0) {
374
+ this.rules.splice(index, 1);
375
+ this.logger.debug("Policy rule removed", { ruleName: name });
376
+ return true;
377
+ }
378
+ return false;
379
+ }
380
+ /**
381
+ * Gets all registered policy rules.
382
+ *
383
+ * @returns A readonly array of policy rules
384
+ */
385
+ getRules() {
386
+ return [...this.rules];
387
+ }
388
+ /**
389
+ * Sets the policy enforcement mode.
390
+ *
391
+ * @param mode - The new enforcement mode
392
+ */
393
+ setMode(mode) {
394
+ const previousMode = this.mode;
395
+ this.mode = mode;
396
+ this.logger.info("Policy mode changed", { from: previousMode, to: mode });
397
+ }
398
+ /**
399
+ * Gets the current policy enforcement mode.
400
+ *
401
+ * @returns The current mode
402
+ */
403
+ getMode() {
404
+ return this.mode;
405
+ }
406
+ /**
407
+ * Logs a policy decision for audit purposes.
408
+ */
409
+ logDecision(ctx, decision) {
410
+ const logData = {
411
+ toolName: ctx.toolName,
412
+ mode: ctx.mode,
413
+ workflowId: ctx.workflowId,
414
+ allowed: decision.allowed,
415
+ reason: decision.reason,
416
+ ruleName: decision.ruleName
417
+ };
418
+ if (decision.allowed) {
419
+ this.logger.debug("Policy decision: ALLOWED", logData);
420
+ } else {
421
+ this.logger.warn("Policy decision: DENIED", logData);
422
+ }
423
+ }
424
+ };
425
+ function createDefaultPolicyFirewall(config) {
426
+ const firewall = new PolicyFirewall(config);
427
+ firewall.addRule(denyMutationsWithoutModeRule);
428
+ firewall.addRule(safePathsRule);
429
+ return firewall;
430
+ }
431
+ function evaluatePolicy(firewall, ctx) {
432
+ const decision = firewall.evaluate(ctx);
433
+ if (decision.allowed) {
434
+ return ok(void 0);
435
+ }
436
+ return err(new PolicyError(`Policy denied: ${decision.reason}`, decision));
437
+ }
438
+ function createPolicyContext(toolName, args, options) {
439
+ const base = {
440
+ toolName,
441
+ args,
442
+ mode: options?.mode ?? "read-only"
443
+ };
444
+ const result = { ...base };
445
+ if (options?.artifacts !== void 0) {
446
+ result["artifacts"] = options.artifacts;
447
+ }
448
+ if (options?.workflowId !== void 0) {
449
+ result["workflowId"] = options.workflowId;
450
+ }
451
+ if (options?.allowedPaths !== void 0) {
452
+ result["allowedPaths"] = options.allowedPaths;
453
+ }
454
+ return result;
455
+ }
456
+
457
+ // src/audit/secure-handler-audit.ts
458
+ function actorFromContext(ctx, fallback) {
459
+ const caller = ctx.caller;
460
+ const clientId = caller.clientId;
461
+ if (clientId !== void 0 && clientId.length > 0) {
462
+ return {
463
+ type: clientId.includes("api") ? "external" : clientId.includes("cli") ? "agent" : "user",
464
+ id: clientId,
465
+ name: caller.userAgent
466
+ };
467
+ }
468
+ return fallback ?? { type: "system", id: "unknown", name: "Unknown Caller" };
469
+ }
470
+ function resultToOutcome(isError, isPolicyDenied) {
471
+ if (isPolicyDenied) return "denied";
472
+ if (isError === true) return "failure";
473
+ return "success";
474
+ }
475
+ function logToolInvocationAudit(opts) {
476
+ opts.auditLogger.logToolInvocation({
477
+ toolName: opts.toolName,
478
+ outcome: opts.outcome,
479
+ actor: opts.actor,
480
+ requestId: opts.requestId,
481
+ durationMs: opts.durationMs,
482
+ errorMessage: opts.errorMessage
483
+ });
484
+ }
485
+ function logPolicyAudit(opts) {
486
+ opts.auditLogger.logPolicyDecision({
487
+ policyName: opts.policyName,
488
+ decision: opts.decision,
489
+ reason: opts.reason,
490
+ toolName: opts.toolName,
491
+ actor: opts.actor,
492
+ requestId: opts.requestId
493
+ });
494
+ }
495
+ function logRateLimitAudit(opts) {
496
+ opts.auditLogger.logRateLimitViolation({
497
+ toolName: opts.toolName,
498
+ actor: opts.actor,
499
+ currentRate: opts.currentRate,
500
+ limitRate: opts.limitRate,
501
+ requestId: opts.requestId
502
+ });
503
+ }
504
+
505
+ // src/mcp/error-envelope.ts
506
+ import { z as z2 } from "zod";
507
+ var ErrorCategorySchema = z2.enum([
508
+ "transient",
509
+ "validation",
510
+ "permission",
511
+ "business",
512
+ "internal"
513
+ ]);
514
+ var ToolErrorEnvelopeSchema = z2.object({
515
+ errorCategory: ErrorCategorySchema,
516
+ /** Whether retrying the same call could succeed without caller changes. */
517
+ isRetryable: z2.boolean(),
518
+ /** Human-readable summary. Bounded to keep stack traces out of results. */
519
+ message: z2.string().min(1).max(2e3),
520
+ /**
521
+ * Optional structured context. MUST NOT carry secrets, credentials,
522
+ * absolute filesystem paths, or raw Error/response objects — those are
523
+ * an information-disclosure risk (this field is not output-sanitized).
524
+ */
525
+ detail: z2.record(z2.string(), z2.unknown()).optional()
526
+ });
527
+ var ERROR_ENVELOPE_META_KEY = "nexus-agents/error";
528
+ function defaultRetryable(category) {
529
+ return category === "transient";
530
+ }
531
+
532
+ // src/mcp/tools/tool-result.ts
533
+ function toolSuccess(text) {
534
+ return {
535
+ content: [{ type: "text", text }]
536
+ };
537
+ }
538
+ function toolSuccessStructured(data) {
539
+ return {
540
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
541
+ structuredContent: data
542
+ };
543
+ }
544
+ function toolStructuredError(input) {
545
+ const envelope = {
546
+ errorCategory: input.errorCategory,
547
+ isRetryable: input.isRetryable ?? defaultRetryable(input.errorCategory),
548
+ message: input.message,
549
+ ...input.detail !== void 0 ? { detail: input.detail } : {}
550
+ };
551
+ return {
552
+ isError: true,
553
+ content: [{ type: "text", text: envelope.message }],
554
+ _meta: { [ERROR_ENVELOPE_META_KEY]: envelope }
555
+ };
556
+ }
557
+ function toolError(message) {
558
+ return toolStructuredError({ errorCategory: "internal", message });
559
+ }
560
+
561
+ // src/mcp/middleware/request-context.ts
562
+ import { randomBytes } from "crypto";
563
+ function generateRequestId() {
564
+ const bytes = randomBytes(8);
565
+ return `req_${bytes.toString("hex")}`;
566
+ }
567
+ function formatTimestamp() {
568
+ const now = new Date(getTimeProvider().now());
569
+ const formatter = new Intl.DateTimeFormat("en-US", {
570
+ timeZone: "America/New_York",
571
+ timeZoneName: "shortOffset"
572
+ });
573
+ const parts = formatter.formatToParts(now);
574
+ const offset = parts.find((p) => p.type === "timeZoneName")?.value ?? "-05:00";
575
+ const base = now.toLocaleString("sv-SE", {
576
+ timeZone: "America/New_York",
577
+ hour12: false
578
+ });
579
+ return base.replace(" ", "T") + offset.replace("GMT", "");
580
+ }
581
+ function deriveTrustTier(caller) {
582
+ const knownClients = ["claude-cli", "gemini-cli", "codex-cli"];
583
+ if (caller.transport === "stdio") return "1";
584
+ if (caller.authenticated === true) {
585
+ if (caller.clientId !== void 0 && knownClients.includes(caller.clientId)) {
586
+ return "1";
587
+ }
588
+ return "2";
589
+ }
590
+ return "3";
591
+ }
592
+ function createRequestContext(options) {
593
+ const caller = options.caller ?? {};
594
+ const context = {
595
+ requestId: generateRequestId(),
596
+ timestamp: formatTimestamp(),
597
+ toolName: options.toolName,
598
+ caller,
599
+ trustTier: options.trustTier ?? deriveTrustTier(caller),
600
+ ...options.traceId !== void 0 && { traceId: options.traceId },
601
+ ...options.parentSpanId !== void 0 && { parentSpanId: options.parentSpanId }
602
+ };
603
+ return Object.freeze(context);
604
+ }
605
+ function contextForLogging(ctx) {
606
+ return {
607
+ requestId: ctx.requestId,
608
+ toolName: ctx.toolName,
609
+ trustTier: ctx.trustTier,
610
+ ...ctx.caller.clientId !== void 0 && { clientId: ctx.caller.clientId },
611
+ ...ctx.traceId !== void 0 && { traceId: ctx.traceId }
612
+ };
613
+ }
614
+
615
+ // src/mcp/middleware/tool-input-sanitizer.ts
616
+ var XML_INJECTION_PATTERN = /<\/?(system|human|assistant|instructions|user|prompt|context|tool_use|tool_result)\b[^>]*>/gi;
617
+ var INJECTION_DETECTORS = [
618
+ { name: "system_prompt_override", pattern: /ignore (?:all )?previous (?:instructions|rules)/i },
619
+ { name: "role_impersonation", pattern: /i(?:'m| am) the (?:repo |project )?(?:owner|admin)/i },
620
+ { name: "hidden_instruction", pattern: /<!--[\s\S]*?(?:execute|delete|merge|apply)[\s\S]*?-->/i }
621
+ ];
622
+ function sanitizeString(value) {
623
+ XML_INJECTION_PATTERN.lastIndex = 0;
624
+ const cleaned = value.replace(XML_INJECTION_PATTERN, "");
625
+ return { cleaned, modified: cleaned !== value };
626
+ }
627
+ function detectPatterns(value) {
628
+ const detected = [];
629
+ for (const { name, pattern } of INJECTION_DETECTORS) {
630
+ pattern.lastIndex = 0;
631
+ if (pattern.test(value)) {
632
+ detected.push(name);
633
+ }
634
+ }
635
+ return detected;
636
+ }
637
+ function sanitizeValue(value, stats) {
638
+ if (typeof value === "string") {
639
+ const patterns = detectPatterns(value);
640
+ if (patterns.length > 0) {
641
+ stats.patterns.push(...patterns);
642
+ }
643
+ const { cleaned, modified } = sanitizeString(value);
644
+ if (modified) stats.count++;
645
+ return cleaned;
646
+ }
647
+ if (Array.isArray(value)) {
648
+ return value.map((item) => sanitizeValue(item, stats));
649
+ }
650
+ if (value !== null && typeof value === "object") {
651
+ const result = {};
652
+ for (const [key, val] of Object.entries(value)) {
653
+ result[key] = sanitizeValue(val, stats);
654
+ }
655
+ return result;
656
+ }
657
+ return value;
658
+ }
659
+ function sanitizeToolInput(args) {
660
+ if (args === void 0 || args === null) {
661
+ return { sanitized: args, wasModified: false, modifiedCount: 0, detectedPatterns: [] };
662
+ }
663
+ const stats = { count: 0, patterns: [] };
664
+ const sanitized = sanitizeValue(args, stats);
665
+ const uniquePatterns = [...new Set(stats.patterns)];
666
+ return {
667
+ sanitized,
668
+ wasModified: stats.count > 0,
669
+ modifiedCount: stats.count,
670
+ detectedPatterns: uniquePatterns
671
+ };
672
+ }
673
+ function logSanitizationResult(result, logger, toolName) {
674
+ if (result.wasModified) {
675
+ logger.warn("Tool input sanitized \u2014 XML injection tags stripped", {
676
+ tool: toolName,
677
+ modifiedFields: result.modifiedCount
678
+ });
679
+ }
680
+ if (result.detectedPatterns.length > 0) {
681
+ logger.warn("Injection patterns detected in tool input", {
682
+ tool: toolName,
683
+ patterns: result.detectedPatterns
684
+ });
685
+ }
686
+ }
687
+
688
+ // src/mcp/middleware/secure-handler.ts
689
+ function rateLimitError(nextTokenMs) {
690
+ return toolStructuredError({
691
+ errorCategory: "transient",
692
+ message: `Rate limit exceeded. Try again in ${String(nextTokenMs)}ms.`
693
+ });
694
+ }
695
+ function policyDeniedError(reason, requestId) {
696
+ return toolStructuredError({
697
+ errorCategory: "permission",
698
+ message: `Policy denied: ${reason} (request: ${requestId})`
699
+ });
700
+ }
701
+ function internalError(message, requestId) {
702
+ return toolStructuredError({
703
+ errorCategory: "internal",
704
+ message: `Internal error: ${message} (request: ${requestId})`
705
+ });
706
+ }
707
+ var MAX_INPUT_SIZE_BYTES = 10 * 1024 * 1024;
708
+ var SECRET_PATTERNS = [
709
+ // API keys with common prefixes
710
+ /\b(sk-[a-zA-Z0-9]{20,})\b/g,
711
+ /\b(pk-[a-zA-Z0-9]{20,})\b/g,
712
+ // AWS-style keys
713
+ /\b(AKIA[A-Z0-9]{16})\b/g,
714
+ // Bearer tokens in output
715
+ /Bearer\s+[a-zA-Z0-9_\-.~+/]+=*/g,
716
+ // Generic long hex secrets (40+ chars)
717
+ /\b[0-9a-f]{40,}\b/gi,
718
+ // password= or token= in output
719
+ /(?:password|token|secret|apikey|api_key)\s*[=:]\s*\S{8,}/gi
720
+ ];
721
+ function sanitizeOutput(text, logger) {
722
+ let sanitized = text;
723
+ for (const pattern of SECRET_PATTERNS) {
724
+ const before = sanitized;
725
+ sanitized = sanitized.replace(pattern, "[REDACTED]");
726
+ if (sanitized !== before) {
727
+ logger.warn("Potential secret detected in tool output, redacting", {
728
+ pattern: pattern.source.slice(0, 30)
729
+ });
730
+ }
731
+ }
732
+ return sanitized;
733
+ }
734
+ function sanitizeToolResult(result, logger) {
735
+ for (const item of result.content) {
736
+ item.text = sanitizeOutput(item.text, logger);
737
+ }
738
+ }
739
+ function checkInputSize(args, logger, requestId) {
740
+ if (args === void 0) return null;
741
+ const inputSize = JSON.stringify(args).length;
742
+ if (inputSize > MAX_INPUT_SIZE_BYTES) {
743
+ logger.warn("Input size exceeds limit", { inputSize, limit: MAX_INPUT_SIZE_BYTES });
744
+ return internalError("Input too large", requestId);
745
+ }
746
+ return null;
747
+ }
748
+ function checkRateLimit(rateLimiter, logger) {
749
+ const acquired = rateLimiter.tryAcquire();
750
+ if (!acquired) {
751
+ const state = rateLimiter.getState();
752
+ logger.warn("Rate limit exceeded");
753
+ return rateLimitError(state.nextTokenMs);
754
+ }
755
+ return null;
756
+ }
757
+ function checkPolicy(opts) {
758
+ const ctxOpts = {
759
+ mode: opts.mode,
760
+ ...opts.allowedPaths && { allowedPaths: opts.allowedPaths }
761
+ };
762
+ const decision = opts.firewall.evaluate(createPolicyContext(opts.toolName, opts.args, ctxOpts));
763
+ if (!decision.allowed) {
764
+ opts.logger.warn("Policy denied tool execution", {
765
+ reason: decision.reason,
766
+ ruleName: decision.ruleName
767
+ });
768
+ return policyDeniedError(decision.reason, opts.requestId);
769
+ }
770
+ opts.logger.debug("Policy check passed", { reason: decision.reason });
771
+ return null;
772
+ }
773
+ async function executeHandler(handler, args, ctx, logger) {
774
+ const startTime = getTimeProvider().now();
775
+ const result = handler.length >= 2 ? await handler(args, ctx) : await handler(args);
776
+ const durationMs = getTimeProvider().now() - startTime;
777
+ if (result.isError === true) {
778
+ logger.warn("Tool execution completed with error", { durationMs });
779
+ } else {
780
+ logger.info("Tool execution completed", { durationMs });
781
+ }
782
+ return result;
783
+ }
784
+ function emitToolAudit(auditLogger, toolName, ctx, result, durationMs) {
785
+ const actor = actorFromContext(ctx);
786
+ const outcome = resultToOutcome(result.isError, false);
787
+ auditLogger.logToolInvocation({
788
+ toolName,
789
+ outcome,
790
+ actor,
791
+ requestId: ctx.requestId,
792
+ durationMs
793
+ });
794
+ }
795
+ function emitToolAuditException(auditLogger, toolName, ctx, durationMs) {
796
+ const actor = actorFromContext(ctx);
797
+ auditLogger.logToolInvocation({
798
+ toolName,
799
+ outcome: "error",
800
+ actor,
801
+ requestId: ctx.requestId,
802
+ durationMs
803
+ });
804
+ }
805
+ function emitPolicyAudit(auditLogger, toolName, ctx, reason) {
806
+ const actor = actorFromContext(ctx);
807
+ auditLogger.logPolicyDecision({
808
+ policyName: "default",
809
+ decision: "deny",
810
+ reason,
811
+ toolName,
812
+ actor,
813
+ requestId: ctx.requestId
814
+ });
815
+ }
816
+ function emitRateLimitAudit(auditLogger, toolName, ctx) {
817
+ const actor = actorFromContext(ctx);
818
+ auditLogger.logRateLimitViolation({
819
+ toolName,
820
+ actor,
821
+ currentRate: 0,
822
+ limitRate: 0,
823
+ requestId: ctx.requestId
824
+ });
825
+ }
826
+ function checkSecurityTier(config, sanitizeResult, logger) {
827
+ const tier = config.securityTier ?? "standard";
828
+ if (tier === "standard" || sanitizeResult.detectedPatterns.length === 0) {
829
+ return null;
830
+ }
831
+ logger.warn("Input rejected by security tier validation", {
832
+ tier,
833
+ patterns: sanitizeResult.detectedPatterns
834
+ });
835
+ return toolStructuredError({
836
+ errorCategory: "permission",
837
+ message: `Input validation failed: detected patterns [${sanitizeResult.detectedPatterns.join(", ")}]. Remove prompt injection patterns and retry.`
838
+ });
839
+ }
840
+ function runPreChecks(config, args, mode, requestContext, logger) {
841
+ const sizeResult = checkInputSize(args, logger, requestContext.requestId);
842
+ if (sizeResult) return { error: sizeResult, sanitizedArgs: args };
843
+ const sanitizeResult = sanitizeToolInput(args);
844
+ logSanitizationResult(sanitizeResult, logger, config.toolName);
845
+ const sanitizedArgs = sanitizeResult.wasModified ? sanitizeResult.sanitized : args;
846
+ const tierError = checkSecurityTier(config, sanitizeResult, logger);
847
+ if (tierError !== null) return { error: tierError, sanitizedArgs };
848
+ if (config.rateLimiter) {
849
+ const rlResult = checkRateLimit(config.rateLimiter, logger);
850
+ if (rlResult) {
851
+ if (config.auditLogger)
852
+ emitRateLimitAudit(config.auditLogger, config.toolName, requestContext);
853
+ return { error: rlResult, sanitizedArgs };
854
+ }
855
+ }
856
+ if (config.policyFirewall) {
857
+ const pResult = checkPolicy({
858
+ firewall: config.policyFirewall,
859
+ toolName: config.toolName,
860
+ args: sanitizedArgs,
861
+ mode,
862
+ allowedPaths: config.allowedPaths,
863
+ logger,
864
+ requestId: requestContext.requestId
865
+ });
866
+ if (pResult) {
867
+ if (config.auditLogger)
868
+ emitPolicyAudit(config.auditLogger, config.toolName, requestContext, "policy denied");
869
+ return { error: pResult, sanitizedArgs };
870
+ }
871
+ }
872
+ return { error: null, sanitizedArgs };
873
+ }
874
+ function createSecureHandler(handler, config) {
875
+ const logger = config.logger ?? createLogger({ tool: config.toolName });
876
+ const mode = config.executionMode ?? "read-only";
877
+ return async (args) => {
878
+ const ctxOpts = {
879
+ toolName: config.toolName,
880
+ ...config.callerInfo && { caller: config.callerInfo }
881
+ };
882
+ const requestContext = createRequestContext(ctxOpts);
883
+ const requestLogger = logger.child(contextForLogging(requestContext));
884
+ requestLogger.info("Tool invocation started");
885
+ const { error: preCheckError, sanitizedArgs } = runPreChecks(
886
+ config,
887
+ args,
888
+ mode,
889
+ requestContext,
890
+ requestLogger
891
+ );
892
+ if (preCheckError) return preCheckError;
893
+ return executeAndAudit(handler, sanitizedArgs, requestContext, requestLogger, config);
894
+ };
895
+ }
896
+ async function executeAndAudit(handler, sanitizedArgs, requestContext, requestLogger, config) {
897
+ const execStartTime = getTimeProvider().now();
898
+ try {
899
+ const result = await executeHandler(
900
+ handler,
901
+ sanitizedArgs,
902
+ { requestContext, logger: requestLogger },
903
+ requestLogger
904
+ );
905
+ sanitizeToolResult(result, requestLogger);
906
+ if (config.auditLogger) {
907
+ emitToolAudit(
908
+ config.auditLogger,
909
+ config.toolName,
910
+ requestContext,
911
+ result,
912
+ getTimeProvider().now() - execStartTime
913
+ );
914
+ }
915
+ return result;
916
+ } catch (error) {
917
+ const rawMessage = error instanceof Error ? error.message : "Unknown error";
918
+ requestLogger.error("Tool execution failed", error instanceof Error ? error : void 0);
919
+ if (config.auditLogger) {
920
+ emitToolAuditException(
921
+ config.auditLogger,
922
+ config.toolName,
923
+ requestContext,
924
+ getTimeProvider().now() - execStartTime
925
+ );
926
+ }
927
+ const sanitized = sanitizeOutput(rawMessage, requestLogger);
928
+ return internalError(sanitized, requestContext.requestId);
929
+ }
930
+ }
931
+
932
+ // src/mcp/mcp-notifier.ts
933
+ import { AsyncLocalStorage } from "async_hooks";
934
+ var internalLogger = createLogger({ component: "mcp-notifier" });
935
+ function createMcpNotifier(server) {
936
+ function send(level, logger, data) {
937
+ try {
938
+ server.sendLoggingMessage({ level, logger, data }).catch((error) => {
939
+ internalLogger.debug("Failed to send MCP notification", {
940
+ level,
941
+ logger,
942
+ error: getErrorMessage(error)
943
+ });
944
+ });
945
+ } catch (error) {
946
+ internalLogger.debug("Failed to send MCP notification", {
947
+ level,
948
+ logger,
949
+ error: getErrorMessage(error)
950
+ });
951
+ }
952
+ }
953
+ return {
954
+ info: (logger, data) => {
955
+ send("info", logger, data);
956
+ },
957
+ debug: (logger, data) => {
958
+ send("debug", logger, data);
959
+ },
960
+ warn: (logger, data) => {
961
+ send("warning", logger, data);
962
+ }
963
+ };
964
+ }
965
+ var NOOP_NOTIFIER = {
966
+ info: () => void 0,
967
+ debug: () => void 0,
968
+ warn: () => void 0
969
+ };
970
+ var progressContextStorage = new AsyncLocalStorage();
971
+ var abortSignalStorage = new AsyncLocalStorage();
972
+ async function withProgressHeartbeat(toolName, notifier, operation, intervalMs = 15e3) {
973
+ const startTime = Date.now();
974
+ let beatCount = 0;
975
+ const progressCtx = progressContextStorage.getStore();
976
+ const timer = setInterval(() => {
977
+ beatCount++;
978
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
979
+ if (progressCtx !== void 0) {
980
+ progressCtx.sendNotification(beatCount);
981
+ }
982
+ notifier.debug(toolName, {
983
+ event: "heartbeat",
984
+ elapsedSeconds: elapsed,
985
+ beatCount,
986
+ hasProgressToken: progressCtx !== void 0
987
+ });
988
+ }, intervalMs);
989
+ try {
990
+ return await operation();
991
+ } finally {
992
+ clearInterval(timer);
993
+ }
994
+ }
995
+
996
+ // src/mcp/middleware/tool-wrapper.ts
997
+ import { randomUUID } from "crypto";
998
+ import { appendFileSync, existsSync, mkdirSync } from "fs";
999
+ import { dirname, join } from "path";
1000
+
1001
+ // src/mcp/middleware/timeout-guard.ts
1002
+ var DEFAULT_TIMEOUT_MS = TIMEOUT_GUARD.defaultMs;
1003
+ var MAX_TIMEOUT_MS = TIMEOUT_GUARD.maxMs;
1004
+ var NEAR_TIMEOUT_THRESHOLD = TIMEOUT_GUARD.nearTimeoutThreshold;
1005
+ function createTimeoutError(operationName, timeoutMs) {
1006
+ return {
1007
+ code: "OPERATION_TIMEOUT",
1008
+ message: `Operation '${operationName}' timed out after ${String(timeoutMs)}ms`,
1009
+ operation: operationName,
1010
+ timeoutMs
1011
+ };
1012
+ }
1013
+ function createGuardError(error, operationName) {
1014
+ const guardError = {
1015
+ code: "GUARD_ERROR",
1016
+ message: getErrorMessage(error),
1017
+ operation: operationName
1018
+ };
1019
+ if (error instanceof Error) {
1020
+ return { ...guardError, cause: error };
1021
+ }
1022
+ return guardError;
1023
+ }
1024
+ var TimeoutGuard = class {
1025
+ defaultTimeoutMs;
1026
+ maxTimeoutMs;
1027
+ enableLogging;
1028
+ logger;
1029
+ constructor(config) {
1030
+ this.defaultTimeoutMs = config?.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
1031
+ this.maxTimeoutMs = config?.maxTimeoutMs ?? MAX_TIMEOUT_MS;
1032
+ this.enableLogging = config?.enableLogging ?? true;
1033
+ this.logger = config?.logger ?? createLogger({ component: "timeout-guard" });
1034
+ }
1035
+ /** Resolves effective timeout and operation name from options. */
1036
+ resolveOptions(options) {
1037
+ return {
1038
+ timeoutMs: Math.min(options?.timeoutMs ?? this.defaultTimeoutMs, this.maxTimeoutMs),
1039
+ operationName: options?.operationName ?? "unknown"
1040
+ };
1041
+ }
1042
+ /**
1043
+ * Executes an async operation with timeout protection.
1044
+ */
1045
+ async execute(operation, options) {
1046
+ const { timeoutMs, operationName } = this.resolveOptions(options);
1047
+ const validationError = this.validateTimeout(timeoutMs, operationName);
1048
+ if (validationError !== null) {
1049
+ return err(validationError);
1050
+ }
1051
+ return this.runGuarded(operation, timeoutMs, operationName, options);
1052
+ }
1053
+ /** Runs the operation with timeout and optional abort signal. */
1054
+ async runGuarded(operation, timeoutMs, operationName, options) {
1055
+ this.logStart(operationName, timeoutMs);
1056
+ const startTime = getTimeProvider().now();
1057
+ const state = { timeoutId: void 0, timedOut: false };
1058
+ try {
1059
+ const result = await this.runWithTimeout(
1060
+ operation,
1061
+ timeoutMs,
1062
+ state,
1063
+ options?.onTimeout,
1064
+ options?.signal
1065
+ );
1066
+ return this.handleSuccess(result, startTime, timeoutMs, operationName);
1067
+ } catch {
1068
+ const cancelled = options?.signal?.aborted === true && !state.timedOut;
1069
+ return err(
1070
+ this.handleFailure(state.timedOut, operationName, timeoutMs, startTime, cancelled)
1071
+ );
1072
+ } finally {
1073
+ if (state.timeoutId !== void 0) {
1074
+ clearTimeout(state.timeoutId);
1075
+ }
1076
+ }
1077
+ }
1078
+ validateTimeout(timeoutMs, operationName) {
1079
+ if (timeoutMs <= 0) {
1080
+ return {
1081
+ code: "INVALID_TIMEOUT",
1082
+ message: `Invalid timeout: ${String(timeoutMs)}ms`,
1083
+ operation: operationName
1084
+ };
1085
+ }
1086
+ return null;
1087
+ }
1088
+ logStart(operationName, timeoutMs) {
1089
+ if (this.enableLogging) {
1090
+ this.logger.debug("Starting guarded operation", { operation: operationName, timeoutMs });
1091
+ }
1092
+ }
1093
+ async runWithTimeout(operation, timeoutMs, state, onTimeout, signal) {
1094
+ const timeoutPromise = new Promise((_resolve, reject) => {
1095
+ state.timeoutId = setTimeout(() => {
1096
+ state.timedOut = true;
1097
+ onTimeout?.();
1098
+ reject(new Error(`Operation timed out after ${String(timeoutMs)}ms`));
1099
+ }, timeoutMs);
1100
+ });
1101
+ const promises = [operation(), timeoutPromise];
1102
+ if (signal !== void 0 && !signal.aborted) {
1103
+ const abortPromise = new Promise((_resolve, reject) => {
1104
+ signal.addEventListener(
1105
+ "abort",
1106
+ () => {
1107
+ reject(new Error("Operation cancelled by client"));
1108
+ },
1109
+ { once: true }
1110
+ );
1111
+ });
1112
+ promises.push(abortPromise);
1113
+ }
1114
+ return Promise.race(promises);
1115
+ }
1116
+ handleSuccess(result, startTime, timeoutMs, operationName) {
1117
+ const durationMs = getTimeProvider().now() - startTime;
1118
+ const nearTimeout = durationMs > timeoutMs * NEAR_TIMEOUT_THRESHOLD;
1119
+ if (nearTimeout && this.enableLogging) {
1120
+ this.logger.warn("Operation completed near timeout threshold", {
1121
+ operation: operationName,
1122
+ durationMs,
1123
+ timeoutMs,
1124
+ thresholdPercent: Math.round(durationMs / timeoutMs * 100)
1125
+ });
1126
+ }
1127
+ if (this.enableLogging) {
1128
+ this.logger.debug("Guarded operation completed", { operation: operationName, durationMs });
1129
+ }
1130
+ return ok({ value: result, durationMs, nearTimeout });
1131
+ }
1132
+ handleFailure(timedOut, operationName, timeoutMs, startTime, cancelled = false) {
1133
+ const durationMs = getTimeProvider().now() - startTime;
1134
+ if (cancelled) {
1135
+ this.logger.info("Operation cancelled by client", {
1136
+ operation: operationName,
1137
+ durationMs
1138
+ });
1139
+ return {
1140
+ code: "OPERATION_CANCELLED",
1141
+ message: `Operation '${operationName}' cancelled by client`,
1142
+ operation: operationName
1143
+ };
1144
+ }
1145
+ if (timedOut) {
1146
+ this.logger.error("Operation timed out", void 0, {
1147
+ operation: operationName,
1148
+ timeoutMs,
1149
+ durationMs
1150
+ });
1151
+ return createTimeoutError(operationName, timeoutMs);
1152
+ }
1153
+ return createGuardError(new Error("Unknown error"), operationName);
1154
+ }
1155
+ /**
1156
+ * Creates a guarded version of an async function.
1157
+ */
1158
+ guard(fn, options) {
1159
+ return async (...args) => {
1160
+ return this.execute(() => fn(...args), options);
1161
+ };
1162
+ }
1163
+ };
1164
+
1165
+ // src/security/access-constraint-deriver/denylist.ts
1166
+ var UNBYPASSABLE_PATH_PATTERNS = [
1167
+ // Environment files
1168
+ ".env",
1169
+ ".env.*",
1170
+ "**/.env",
1171
+ "**/.env.*",
1172
+ // SSH credentials
1173
+ "~/.ssh/**",
1174
+ "**/ssh/id_*",
1175
+ "**/*_rsa",
1176
+ "**/*_ed25519",
1177
+ "**/*.pem",
1178
+ // Cloud credentials
1179
+ "~/.aws/**",
1180
+ "~/.azure/**",
1181
+ "~/.gcp/**",
1182
+ "~/.config/gcloud/**",
1183
+ "~/.kube/config",
1184
+ // Unix secret files
1185
+ "/etc/shadow",
1186
+ "/etc/sudoers",
1187
+ "/etc/sudoers.d/**",
1188
+ // Common secret file patterns
1189
+ "**/secrets.*",
1190
+ "**/credentials.*",
1191
+ "**/private_key.*",
1192
+ "**/id_rsa*"
1193
+ ];
1194
+ var UNBYPASSABLE_TOOL_NAMES = [
1195
+ // Destructive git operations
1196
+ "git_push_force",
1197
+ "git_reset_hard",
1198
+ "git_branch_delete_force",
1199
+ "git_clean_force",
1200
+ // Destructive filesystem
1201
+ "rm_recursive_force",
1202
+ "chmod_recursive",
1203
+ // Identity / auth mutations
1204
+ "ssh_add_key",
1205
+ "gpg_add_key",
1206
+ "npm_publish_force",
1207
+ // Remote destruction
1208
+ "github_repo_delete",
1209
+ "github_org_transfer",
1210
+ "aws_account_close"
1211
+ ];
1212
+ function compileGlobToRegex(pattern) {
1213
+ const pat = pattern.toLowerCase();
1214
+ const escaped = pat.replace(/[\\.+^$()|[\]{}]/g, "\\$&").replace(/\*\*/g, "__DOUBLESTAR__").replace(/\*/g, "[^/]*").replace(/__DOUBLESTAR__/g, ".*");
1215
+ const anchored = escaped.startsWith("~/") ? `(^|/)${escaped.slice(2)}$` : escaped.startsWith("/") ? `^${escaped}$` : `(^|/)${escaped}$`;
1216
+ return new RegExp(anchored);
1217
+ }
1218
+ var COMPILED_PATH_PATTERNS = UNBYPASSABLE_PATH_PATTERNS.map((pattern) => ({
1219
+ pattern,
1220
+ regex: compileGlobToRegex(pattern)
1221
+ }));
1222
+ function isPathDenied(path) {
1223
+ const normalized = path.toLowerCase();
1224
+ return COMPILED_PATH_PATTERNS.some((c) => c.regex.test(normalized));
1225
+ }
1226
+ function isToolDenied(toolName) {
1227
+ return UNBYPASSABLE_TOOL_NAMES.includes(toolName);
1228
+ }
1229
+
1230
+ // src/security/access-constraint-deriver/tool-risk.ts
1231
+ var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
1232
+ // Discovery / listing
1233
+ "list_experts",
1234
+ "list_workflows",
1235
+ // Research reads
1236
+ "research_query",
1237
+ "research_analyze",
1238
+ "research_catalog_review",
1239
+ "research_synthesize",
1240
+ // Memory reads
1241
+ "memory_query",
1242
+ "memory_stats",
1243
+ // Observability
1244
+ "weather_report",
1245
+ "query_trace",
1246
+ "query_task_state",
1247
+ // Async-mode result polling (#3042 / epic #2631)
1248
+ "get_job_result",
1249
+ // Async-mode job discovery (#3046 / epic #2631 Stage 5)
1250
+ "list_jobs",
1251
+ // Codebase intelligence (read-only over local files)
1252
+ "search_codebase",
1253
+ "extract_symbols",
1254
+ // Repo analysis (read-only)
1255
+ "repo_analyze",
1256
+ "repo_security_plan",
1257
+ // Routing recommendation (no side effects — returns recommendation)
1258
+ "delegate_to_model",
1259
+ // Registry import (returns a draft template — does not write)
1260
+ "registry_import"
1261
+ ]);
1262
+ function isRiskyTool(toolName) {
1263
+ return !READ_ONLY_TOOLS2.has(toolName);
1264
+ }
1265
+
1266
+ // src/security/access-constraint-deriver/enforcer.ts
1267
+ function checkAccess(toolName, policy, args) {
1268
+ if (isToolDenied(toolName)) {
1269
+ return {
1270
+ decision: "deny",
1271
+ reason: `tool "${toolName}" is on the unbypassable deny-tool list`,
1272
+ matchedRule: "unbypassable:tool"
1273
+ };
1274
+ }
1275
+ if (typeof args?.path === "string" && args.path.length > 0 && isPathDenied(args.path)) {
1276
+ return {
1277
+ decision: "deny",
1278
+ reason: `path "${args.path}" is on the unbypassable deny-path list`,
1279
+ matchedRule: "unbypassable:path"
1280
+ };
1281
+ }
1282
+ if (policy.allowedTools === "*") return { decision: "allow" };
1283
+ if (policy.allowedTools.includes(toolName)) return { decision: "allow" };
1284
+ return decideOnViolation(toolName, policy.mode);
1285
+ }
1286
+ function decideOnViolation(toolName, mode) {
1287
+ if (mode === "audit") {
1288
+ return {
1289
+ decision: "log-and-allow",
1290
+ warning: `tool "${toolName}" not in derived policy (audit mode)`
1291
+ };
1292
+ }
1293
+ if (mode === "confirm_risky") {
1294
+ if (!isRiskyTool(toolName)) {
1295
+ return {
1296
+ decision: "log-and-allow",
1297
+ warning: `tool "${toolName}" not in derived policy (confirm_risky mode, read-only \u2014 would have required human approval, allowed because read-only)`
1298
+ };
1299
+ }
1300
+ return {
1301
+ decision: "deny",
1302
+ reason: `tool "${toolName}" not in derived policy (confirm_risky mode, risky \u2014 would have required human approval; denied for now, add to allowedTools or run in audit mode to allow)`,
1303
+ matchedRule: "allowedTools:confirm_risky"
1304
+ };
1305
+ }
1306
+ return {
1307
+ decision: "deny",
1308
+ reason: `tool "${toolName}" not in derived policy`,
1309
+ matchedRule: "allowedTools"
1310
+ };
1311
+ }
1312
+
1313
+ // src/security/access-constraint-deriver/mcp-guard.ts
1314
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
1315
+ var accessPolicyStorage = new AsyncLocalStorage2();
1316
+ function withAccessPolicy(policy, fn) {
1317
+ return accessPolicyStorage.run(policy, fn);
1318
+ }
1319
+ function getActivePolicy() {
1320
+ return accessPolicyStorage.getStore();
1321
+ }
1322
+ function denyToToolResult(decision, requestId) {
1323
+ return {
1324
+ isError: true,
1325
+ content: [
1326
+ {
1327
+ type: "text",
1328
+ text: `access denied: ${decision.reason} (rule: ${decision.matchedRule}, request: ${requestId})`
1329
+ }
1330
+ ]
1331
+ };
1332
+ }
1333
+
1334
+ // src/security/access-constraint-deriver/chain-adapter.ts
1335
+ function toGuardArgs(args) {
1336
+ if (typeof args !== "object" || args === null) return void 0;
1337
+ const path = args["path"];
1338
+ return typeof path === "string" && path.length > 0 ? { path } : void 0;
1339
+ }
1340
+ function createAccessPolicyChainMiddleware(toolName) {
1341
+ return async (args, ctx, next) => {
1342
+ const policy = getActivePolicy();
1343
+ if (policy === void 0 || policy.mode === "off") {
1344
+ return next(args, ctx);
1345
+ }
1346
+ const decision = checkAccess(toolName, policy, toGuardArgs(args));
1347
+ if (decision.decision === "allow") {
1348
+ return next(args, ctx);
1349
+ }
1350
+ if (decision.decision === "log-and-allow") {
1351
+ ctx.logger.warn("access-policy: audit violation", {
1352
+ tool: toolName,
1353
+ warning: decision.warning,
1354
+ policySource: policy.source,
1355
+ requestId: ctx.requestContext.requestId
1356
+ });
1357
+ return next(args, ctx);
1358
+ }
1359
+ ctx.logger.info("access-policy: tool call denied", {
1360
+ tool: toolName,
1361
+ reason: decision.reason,
1362
+ matchedRule: decision.matchedRule,
1363
+ policySource: policy.source,
1364
+ mode: policy.mode,
1365
+ requestId: ctx.requestContext.requestId
1366
+ });
1367
+ return denyToToolResult(decision, ctx.requestContext.requestId);
1368
+ };
1369
+ }
1370
+
1371
+ // src/mcp/middleware/middleware-chain.ts
1372
+ function errorResult(category, message, requestId) {
1373
+ return toolStructuredError({
1374
+ errorCategory: category,
1375
+ message: `${message} (request: ${requestId})`
1376
+ });
1377
+ }
1378
+ function createValidationMiddleware(schema) {
1379
+ return async (args, ctx, next) => {
1380
+ const result = validateToolInput(schema, args);
1381
+ if (!result.ok) {
1382
+ ctx.logger.warn("Validation failed", {
1383
+ error: result.error.message
1384
+ });
1385
+ return errorResult(
1386
+ "validation",
1387
+ `Validation error: ${result.error.message}`,
1388
+ ctx.requestContext.requestId
1389
+ );
1390
+ }
1391
+ ctx.validatedArgs = result.value;
1392
+ return next(result.value, ctx);
1393
+ };
1394
+ }
1395
+ function createPolicyMiddleware(firewall, toolName, mode, allowedPaths) {
1396
+ return async (args, ctx, next) => {
1397
+ const policyCtx = createPolicyContext(toolName, args, {
1398
+ mode,
1399
+ ...allowedPaths !== void 0 && { allowedPaths }
1400
+ });
1401
+ const decision = firewall.evaluate(policyCtx);
1402
+ if (!decision.allowed) {
1403
+ ctx.logger.warn("Policy denied", {
1404
+ reason: decision.reason,
1405
+ ruleName: decision.ruleName
1406
+ });
1407
+ return errorResult(
1408
+ "permission",
1409
+ `Policy denied: ${decision.reason}`,
1410
+ ctx.requestContext.requestId
1411
+ );
1412
+ }
1413
+ ctx.logger.debug("Policy check passed", { reason: decision.reason });
1414
+ return next(args, ctx);
1415
+ };
1416
+ }
1417
+ function createRateLimitMiddleware(limiter) {
1418
+ return async (args, ctx, next) => {
1419
+ const acquired = limiter.tryAcquire();
1420
+ if (!acquired) {
1421
+ const state = limiter.getState();
1422
+ ctx.logger.warn("Rate limit exceeded", {
1423
+ nextTokenMs: state.nextTokenMs
1424
+ });
1425
+ return errorResult(
1426
+ "transient",
1427
+ `Rate limit exceeded. Try again in ${String(state.nextTokenMs)}ms`,
1428
+ ctx.requestContext.requestId
1429
+ );
1430
+ }
1431
+ return next(args, ctx);
1432
+ };
1433
+ }
1434
+ function createTimeoutMiddleware(guard, toolName) {
1435
+ return async (args, ctx, next) => {
1436
+ const signal = abortSignalStorage.getStore();
1437
+ const result = await guard.execute(() => next(args, ctx), {
1438
+ operationName: toolName,
1439
+ ...signal !== void 0 ? { signal } : {}
1440
+ });
1441
+ if (!result.ok) {
1442
+ ctx.logger.error("Operation timed out", void 0, {
1443
+ code: result.error.code,
1444
+ timeoutMs: result.error.timeoutMs
1445
+ });
1446
+ return errorResult("transient", result.error.message, ctx.requestContext.requestId);
1447
+ }
1448
+ if (result.value.nearTimeout) {
1449
+ ctx.logger.warn("Operation completed near timeout threshold", {
1450
+ durationMs: result.value.durationMs
1451
+ });
1452
+ }
1453
+ return result.value.value;
1454
+ };
1455
+ }
1456
+ function createAuditMiddleware() {
1457
+ return async (args, ctx, next) => {
1458
+ const startTime = getTimeProvider().now();
1459
+ ctx.logger.info("Tool invocation started");
1460
+ try {
1461
+ const result = await next(args, ctx);
1462
+ const durationMs = getTimeProvider().now() - startTime;
1463
+ if (result.isError === true) {
1464
+ ctx.logger.warn("Tool execution completed with error", { durationMs });
1465
+ } else {
1466
+ ctx.logger.info("Tool execution completed", { durationMs });
1467
+ }
1468
+ return result;
1469
+ } catch (error) {
1470
+ const durationMs = getTimeProvider().now() - startTime;
1471
+ const message = error instanceof Error ? error.message : "Unknown error";
1472
+ ctx.logger.error("Tool execution failed", error instanceof Error ? error : void 0, {
1473
+ durationMs
1474
+ });
1475
+ return errorResult("internal", `Internal error: ${message}`, ctx.requestContext.requestId);
1476
+ }
1477
+ };
1478
+ }
1479
+ function composeMiddleware(middlewares) {
1480
+ return (args, ctx, finalHandler) => {
1481
+ const dispatch = (index, currentArgs) => {
1482
+ if (index >= middlewares.length) {
1483
+ return finalHandler(currentArgs, ctx);
1484
+ }
1485
+ const middleware = middlewares[index];
1486
+ if (middleware === void 0) {
1487
+ return finalHandler(currentArgs, ctx);
1488
+ }
1489
+ return middleware(currentArgs, ctx, (nextArgs) => dispatch(index + 1, nextArgs));
1490
+ };
1491
+ return dispatch(0, args);
1492
+ };
1493
+ }
1494
+ function addAuditMiddleware(middlewares, skip) {
1495
+ if (skip.audit !== true) {
1496
+ middlewares.push(createAuditMiddleware());
1497
+ }
1498
+ }
1499
+ function addRateLimitMiddleware(middlewares, config, skip) {
1500
+ if (skip.rateLimit !== true && config.rateLimiter !== void 0) {
1501
+ const limiter = config.rateLimiter instanceof RateLimiter ? config.rateLimiter : new RateLimiter(config.rateLimiter);
1502
+ middlewares.push(createRateLimitMiddleware(limiter));
1503
+ }
1504
+ }
1505
+ function addValidationMiddleware(middlewares, config, skip) {
1506
+ if (skip.validation !== true && config.schema !== void 0) {
1507
+ middlewares.push(createValidationMiddleware(config.schema));
1508
+ }
1509
+ }
1510
+ function addPolicyMiddleware(middlewares, config, skip) {
1511
+ if (skip.policy !== true && config.policyFirewall !== void 0) {
1512
+ const mode = config.executionMode ?? "read-only";
1513
+ middlewares.push(
1514
+ createPolicyMiddleware(config.policyFirewall, config.toolName, mode, config.allowedPaths)
1515
+ );
1516
+ }
1517
+ }
1518
+ function addTimeoutMiddleware(middlewares, config, skip) {
1519
+ if (skip.timeout !== true && config.timeout !== void 0) {
1520
+ const guard = new TimeoutGuard(config.timeout);
1521
+ middlewares.push(createTimeoutMiddleware(guard, config.toolName));
1522
+ }
1523
+ }
1524
+ function addAccessPolicyMiddleware(middlewares, config, skip) {
1525
+ if (skip.accessPolicy !== true) {
1526
+ middlewares.push(createAccessPolicyChainMiddleware(config.toolName));
1527
+ }
1528
+ }
1529
+ function buildMiddlewareStack(config) {
1530
+ const skip = config.skip ?? {};
1531
+ const middlewares = [];
1532
+ middlewares.push(createMetricsMiddleware());
1533
+ addAuditMiddleware(middlewares, skip);
1534
+ addRateLimitMiddleware(middlewares, config, skip);
1535
+ addValidationMiddleware(middlewares, config, skip);
1536
+ addPolicyMiddleware(middlewares, config, skip);
1537
+ addAccessPolicyMiddleware(middlewares, config, skip);
1538
+ addTimeoutMiddleware(middlewares, config, skip);
1539
+ return middlewares;
1540
+ }
1541
+ function createMiddlewareChain(config) {
1542
+ const logger = config.logger ?? createLogger({ tool: config.toolName });
1543
+ const middlewares = buildMiddlewareStack(config);
1544
+ const composed = composeMiddleware(middlewares);
1545
+ return (handler) => {
1546
+ return async (args) => {
1547
+ const requestContext = createRequestContext({ toolName: config.toolName });
1548
+ const requestLogger = logger.child(contextForLogging(requestContext));
1549
+ const ctx = { requestContext, logger: requestLogger };
1550
+ return composed(args, ctx, (finalArgs, finalCtx) => handler(finalArgs, finalCtx));
1551
+ };
1552
+ };
1553
+ }
1554
+ function withMiddleware(toolName, handler, options) {
1555
+ const config = {
1556
+ toolName,
1557
+ ...options
1558
+ };
1559
+ const chain = createMiddlewareChain(config);
1560
+ const wrappedHandler = (args, ctx) => {
1561
+ if (handler.length >= 2) {
1562
+ return handler(args, ctx);
1563
+ }
1564
+ return handler(args);
1565
+ };
1566
+ return chain(wrappedHandler);
1567
+ }
1568
+
1569
+ // src/mcp/middleware/tool-wrapper.ts
1570
+ var DEFAULT_TIMEOUT_CONFIG = {
1571
+ defaultTimeoutMs: MCP_TIMEOUTS.defaultMs,
1572
+ maxTimeoutMs: MCP_TIMEOUTS.maxMs,
1573
+ enableLogging: true,
1574
+ uriValidation: true
1575
+ };
1576
+ var DEFAULT_TOOL_TIMEOUTS = {
1577
+ ...MCP_TIMEOUTS.perTool
1578
+ };
1579
+ function getToolTimeout(toolName, security, explicitMs) {
1580
+ if (explicitMs !== void 0) {
1581
+ return explicitMs;
1582
+ }
1583
+ const perToolConfig = security?.timeout?.perToolTimeout;
1584
+ const perToolMs = perToolConfig?.[toolName];
1585
+ if (perToolMs !== void 0) {
1586
+ return perToolMs;
1587
+ }
1588
+ const builtInDefault = DEFAULT_TOOL_TIMEOUTS[toolName];
1589
+ if (builtInDefault !== void 0) {
1590
+ return builtInDefault;
1591
+ }
1592
+ return security?.timeout?.defaultTimeoutMs ?? DEFAULT_TIMEOUT_CONFIG.defaultTimeoutMs;
1593
+ }
1594
+ function getTimeoutConfig(security, overrideMs) {
1595
+ const timeoutConfig = security?.timeout ?? DEFAULT_TIMEOUT_CONFIG;
1596
+ return {
1597
+ defaultTimeoutMs: overrideMs ?? timeoutConfig.defaultTimeoutMs,
1598
+ maxTimeoutMs: timeoutConfig.maxTimeoutMs,
1599
+ enableLogging: timeoutConfig.enableLogging
1600
+ };
1601
+ }
1602
+ function wrapToolWithTimeout(toolName, handler, options) {
1603
+ return withMiddleware(toolName, handler, {
1604
+ timeout: getTimeoutConfig(void 0, options?.timeoutMs),
1605
+ logger: options?.logger
1606
+ });
1607
+ }
1608
+ var wrapperLogger = createLogger({ component: "tool-wrapper" });
1609
+ function extractProgressContext(extra) {
1610
+ const sdk = extra;
1611
+ const token = sdk?._meta?.progressToken;
1612
+ const sendFn = sdk?.sendNotification;
1613
+ if (token === void 0 || sendFn === void 0) return void 0;
1614
+ return {
1615
+ progressToken: token,
1616
+ sendNotification: (progress, total) => {
1617
+ const params = {
1618
+ progressToken: token,
1619
+ progress
1620
+ };
1621
+ if (total !== void 0) params["total"] = total;
1622
+ sendFn({ method: "notifications/progress", params }).catch((err2) => {
1623
+ wrapperLogger.debug("Failed to send progress notification", {
1624
+ error: getErrorMessage(err2)
1625
+ });
1626
+ });
1627
+ }
1628
+ };
1629
+ }
1630
+ function runWithContexts(handler, args, progressCtx, signal) {
1631
+ const run = () => handler(args);
1632
+ if (signal !== void 0 && progressCtx !== void 0) {
1633
+ return abortSignalStorage.run(signal, () => progressContextStorage.run(progressCtx, run));
1634
+ }
1635
+ if (signal !== void 0) {
1636
+ return abortSignalStorage.run(signal, run);
1637
+ }
1638
+ if (progressCtx !== void 0) {
1639
+ return progressContextStorage.run(progressCtx, run);
1640
+ }
1641
+ return run();
1642
+ }
1643
+ function toSdkCallback(handler) {
1644
+ return (args, extra) => {
1645
+ const progressCtx = extractProgressContext(extra);
1646
+ const signal = extra?.signal;
1647
+ return runWithContexts(handler, args, progressCtx, signal);
1648
+ };
1649
+ }
1650
+ var MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
1651
+ var TIMEOUT_MISMATCH_TELEMETRY_REL_PATH = "mcp-telemetry/timeout-mismatch-events.jsonl";
1652
+ var ensuredDirs = /* @__PURE__ */ new Set();
1653
+ function appendTimeoutMismatchEvent(event) {
1654
+ try {
1655
+ const path = join(getNexusDataDir(), TIMEOUT_MISMATCH_TELEMETRY_REL_PATH);
1656
+ const dir = dirname(path);
1657
+ if (!ensuredDirs.has(dir)) {
1658
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1659
+ ensuredDirs.add(dir);
1660
+ }
1661
+ appendFileSync(path, JSON.stringify(event) + "\n", "utf-8");
1662
+ } catch (err2) {
1663
+ wrapperLogger.debug("Best-effort timeout-mismatch event recording failed", {
1664
+ error: getErrorMessage(err2)
1665
+ });
1666
+ }
1667
+ }
1668
+ function extractErrorCategoryFromResult(result) {
1669
+ const envelope = result._meta?.["nexus-agents/error"];
1670
+ if (envelope !== null && typeof envelope === "object" && "errorCategory" in envelope) {
1671
+ const cat = envelope.errorCategory;
1672
+ if (typeof cat === "string") return cat;
1673
+ }
1674
+ return void 0;
1675
+ }
1676
+ function extractErrorMessageFromResult(result) {
1677
+ const first = result.content[0];
1678
+ if (first?.type === "text" && typeof first.text === "string") {
1679
+ return first.text.slice(0, 500);
1680
+ }
1681
+ return void 0;
1682
+ }
1683
+ function buildMismatchEvent(ctx, eventId, t0, outcome) {
1684
+ const t1 = Date.now();
1685
+ return {
1686
+ eventId,
1687
+ toolName: ctx.toolName,
1688
+ configuredTimeoutMs: ctx.configuredTimeoutMs,
1689
+ mcpSdkDefaultMs: MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS,
1690
+ startedAt: new Date(t0).toISOString(),
1691
+ endedAt: new Date(t1).toISOString(),
1692
+ durationMs: t1 - t0,
1693
+ outcome: outcome.outcome,
1694
+ ...outcome.errorCategory !== void 0 ? { errorCategory: outcome.errorCategory } : {},
1695
+ ...outcome.errorMessage !== void 0 ? { errorMessage: outcome.errorMessage } : {}
1696
+ };
1697
+ }
1698
+ function classifyResult(result) {
1699
+ if (result.isError !== true) return { outcome: "success" };
1700
+ const errorCategory = extractErrorCategoryFromResult(result);
1701
+ const errorMessage = extractErrorMessageFromResult(result);
1702
+ return {
1703
+ outcome: "error",
1704
+ ...errorCategory !== void 0 ? { errorCategory } : {},
1705
+ ...errorMessage !== void 0 ? { errorMessage } : {}
1706
+ };
1707
+ }
1708
+ async function runMismatchedCall(ctx) {
1709
+ const eventId = randomUUID();
1710
+ const t0 = Date.now();
1711
+ ctx.log.warn(
1712
+ "MCP tool budget exceeds client default and no progressToken received \u2014 request likely to be killed by client before server-side deadline",
1713
+ {
1714
+ tool: ctx.toolName,
1715
+ eventId,
1716
+ configuredTimeoutMs: ctx.configuredTimeoutMs,
1717
+ mcpSdkDefaultMs: MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS,
1718
+ remediation: "Client should pass `onprogress` and `resetTimeoutOnProgress: true` when calling, or extend `options.timeout`. See docs/architecture/MCP_PROTOCOL.md."
1719
+ }
1720
+ );
1721
+ try {
1722
+ const result = await runWithContexts(ctx.handler, ctx.args, ctx.progressCtx, ctx.signal);
1723
+ appendTimeoutMismatchEvent(buildMismatchEvent(ctx, eventId, t0, classifyResult(result)));
1724
+ return result;
1725
+ } catch (err2) {
1726
+ appendTimeoutMismatchEvent(
1727
+ buildMismatchEvent(ctx, eventId, t0, {
1728
+ outcome: "error",
1729
+ errorMessage: getErrorMessage(err2).slice(0, 500)
1730
+ })
1731
+ );
1732
+ throw err2;
1733
+ }
1734
+ }
1735
+ function toSdkCallbackWithBudgetCheck(handler, toolName, configuredTimeoutMs, logger) {
1736
+ const log = logger ?? wrapperLogger;
1737
+ return (args, extra) => {
1738
+ const progressCtx = extractProgressContext(extra);
1739
+ const signal = extra?.signal;
1740
+ const isMismatch = configuredTimeoutMs > MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS && progressCtx === void 0;
1741
+ if (!isMismatch) return runWithContexts(handler, args, progressCtx, signal);
1742
+ return runMismatchedCall({
1743
+ log,
1744
+ handler,
1745
+ args,
1746
+ progressCtx,
1747
+ signal,
1748
+ toolName,
1749
+ configuredTimeoutMs
1750
+ });
1751
+ };
1752
+ }
1753
+
1754
+ // src/mcp/tool-annotations.ts
1755
+ var TOOL_ANNOTATIONS = {
1756
+ // ============================================================================
1757
+ // Orchestration & expert lifecycle
1758
+ // ============================================================================
1759
+ /** Orchestrator coordinates expert agents; spawns workers that may mutate state. */
1760
+ orchestrate: {
1761
+ readOnlyHint: false,
1762
+ destructiveHint: false,
1763
+ idempotentHint: false,
1764
+ openWorldHint: true
1765
+ },
1766
+ /** Creates an expert and adds it to the in-memory expert registry. */
1767
+ create_expert: {
1768
+ readOnlyHint: false,
1769
+ destructiveHint: false,
1770
+ idempotentHint: false,
1771
+ openWorldHint: false
1772
+ },
1773
+ /** Executes a previously created expert; invokes external CLIs. */
1774
+ execute_expert: {
1775
+ readOnlyHint: false,
1776
+ destructiveHint: false,
1777
+ idempotentHint: false,
1778
+ openWorldHint: true
1779
+ },
1780
+ // ============================================================================
1781
+ // Workflow / pipeline execution
1782
+ // ============================================================================
1783
+ /** Runs a workflow template; steps may write to the registry or filesystem. */
1784
+ run_workflow: {
1785
+ readOnlyHint: false,
1786
+ destructiveHint: false,
1787
+ idempotentHint: false,
1788
+ openWorldHint: true
1789
+ },
1790
+ /** Runs a graph-based workflow with checkpointing. */
1791
+ run_graph_workflow: {
1792
+ readOnlyHint: false,
1793
+ destructiveHint: false,
1794
+ idempotentHint: false,
1795
+ openWorldHint: true
1796
+ },
1797
+ /** Runs a pipeline plugin by name. */
1798
+ run_pipeline: {
1799
+ readOnlyHint: false,
1800
+ destructiveHint: false,
1801
+ idempotentHint: false,
1802
+ openWorldHint: true
1803
+ },
1804
+ /** Full dev pipeline: research → plan → vote → implement → QA. */
1805
+ run_dev_pipeline: {
1806
+ readOnlyHint: false,
1807
+ destructiveHint: false,
1808
+ idempotentHint: false,
1809
+ openWorldHint: true
1810
+ },
1811
+ /** Executes an AI-software-factory spec pipeline. */
1812
+ execute_spec: {
1813
+ readOnlyHint: false,
1814
+ destructiveHint: false,
1815
+ idempotentHint: false,
1816
+ openWorldHint: true
1817
+ },
1818
+ // ============================================================================
1819
+ // Voting / decision
1820
+ // ============================================================================
1821
+ /** Records vote outcomes to the audit log; doesn't write to repo state. */
1822
+ consensus_vote: {
1823
+ readOnlyHint: false,
1824
+ destructiveHint: false,
1825
+ idempotentHint: false,
1826
+ openWorldHint: true
1827
+ },
1828
+ /** Per-axis tradeoff vote; records outcome to the store. */
1829
+ supply_chain_tradeoff_panel: {
1830
+ readOnlyHint: false,
1831
+ destructiveHint: false,
1832
+ idempotentHint: false,
1833
+ openWorldHint: true
1834
+ },
1835
+ /** Multi-voter PR review; may write review comments. */
1836
+ pr_review: {
1837
+ readOnlyHint: false,
1838
+ destructiveHint: false,
1839
+ idempotentHint: false,
1840
+ openWorldHint: true
1841
+ },
1842
+ // ============================================================================
1843
+ // Routing & introspection (read-only)
1844
+ // ============================================================================
1845
+ /** Returns a routing recommendation; doesn't execute. */
1846
+ delegate_to_model: {
1847
+ readOnlyHint: true,
1848
+ destructiveHint: false,
1849
+ idempotentHint: true,
1850
+ openWorldHint: false
1851
+ },
1852
+ /** Lists registered expert types. */
1853
+ list_experts: {
1854
+ readOnlyHint: true,
1855
+ destructiveHint: false,
1856
+ idempotentHint: true,
1857
+ openWorldHint: false
1858
+ },
1859
+ /** Lists registered workflow templates. */
1860
+ list_workflows: {
1861
+ readOnlyHint: true,
1862
+ destructiveHint: false,
1863
+ idempotentHint: true,
1864
+ openWorldHint: false
1865
+ },
1866
+ // ============================================================================
1867
+ // Research registry — reads
1868
+ // ============================================================================
1869
+ /** Queries the research registry. */
1870
+ research_query: {
1871
+ readOnlyHint: true,
1872
+ destructiveHint: false,
1873
+ idempotentHint: true,
1874
+ openWorldHint: false
1875
+ },
1876
+ /** Analyzes registry for gaps, trends, coverage. */
1877
+ research_analyze: {
1878
+ readOnlyHint: true,
1879
+ destructiveHint: false,
1880
+ idempotentHint: true,
1881
+ openWorldHint: false
1882
+ },
1883
+ /** Synthesizes registry into topic clusters. */
1884
+ research_synthesize: {
1885
+ readOnlyHint: true,
1886
+ destructiveHint: false,
1887
+ idempotentHint: true,
1888
+ openWorldHint: false
1889
+ },
1890
+ /** Discovers research items via external APIs; doesn't mutate registry. */
1891
+ research_discover: {
1892
+ readOnlyHint: true,
1893
+ destructiveHint: false,
1894
+ idempotentHint: true,
1895
+ openWorldHint: true
1896
+ },
1897
+ // ============================================================================
1898
+ // Research registry — mutations
1899
+ // ============================================================================
1900
+ /** Adds an arXiv paper to the registry. Dedup-checked but first call has effects. */
1901
+ research_add: {
1902
+ readOnlyHint: false,
1903
+ destructiveHint: false,
1904
+ idempotentHint: false,
1905
+ openWorldHint: true
1906
+ },
1907
+ /** Adds a non-paper source to the registry. */
1908
+ research_add_source: {
1909
+ readOnlyHint: false,
1910
+ destructiveHint: false,
1911
+ idempotentHint: false,
1912
+ openWorldHint: true
1913
+ },
1914
+ /** Reviews auto-cataloged research references; may approve/dismiss. */
1915
+ research_catalog_review: {
1916
+ readOnlyHint: false,
1917
+ destructiveHint: false,
1918
+ idempotentHint: false,
1919
+ openWorldHint: false
1920
+ },
1921
+ // ============================================================================
1922
+ // Transient lookups (no registry persistence)
1923
+ // ============================================================================
1924
+ /** Transient OSS search via GitHub API; no persistence. */
1925
+ survey_oss_landscape: {
1926
+ readOnlyHint: true,
1927
+ destructiveHint: false,
1928
+ idempotentHint: true,
1929
+ openWorldHint: true
1930
+ },
1931
+ /** Static lookup against curated vendor seed data. */
1932
+ vendor_publishing_audit: {
1933
+ readOnlyHint: true,
1934
+ destructiveHint: false,
1935
+ idempotentHint: true,
1936
+ openWorldHint: false
1937
+ },
1938
+ /** Diffs two YAML/JSON files; local-only. */
1939
+ compare_data_feeds: {
1940
+ readOnlyHint: true,
1941
+ destructiveHint: false,
1942
+ idempotentHint: true,
1943
+ openWorldHint: false
1944
+ },
1945
+ // ============================================================================
1946
+ // Memory
1947
+ // ============================================================================
1948
+ /** Reads from memory backends. */
1949
+ memory_query: {
1950
+ readOnlyHint: true,
1951
+ destructiveHint: false,
1952
+ idempotentHint: true,
1953
+ openWorldHint: false
1954
+ },
1955
+ /** Returns memory statistics. */
1956
+ memory_stats: {
1957
+ readOnlyHint: true,
1958
+ destructiveHint: false,
1959
+ idempotentHint: true,
1960
+ openWorldHint: false
1961
+ },
1962
+ /** Writes to a memory backend. */
1963
+ memory_write: {
1964
+ readOnlyHint: false,
1965
+ destructiveHint: false,
1966
+ idempotentHint: false,
1967
+ openWorldHint: false
1968
+ },
1969
+ // ============================================================================
1970
+ // Observability
1971
+ // ============================================================================
1972
+ /** Multi-CLI performance weather report; reads outcome store. */
1973
+ weather_report: {
1974
+ readOnlyHint: true,
1975
+ destructiveHint: false,
1976
+ idempotentHint: true,
1977
+ openWorldHint: false
1978
+ },
1979
+ /** Queries execution traces from disk. */
1980
+ query_trace: {
1981
+ readOnlyHint: true,
1982
+ destructiveHint: false,
1983
+ idempotentHint: true,
1984
+ openWorldHint: false
1985
+ },
1986
+ /** Queries structured task-state log. */
1987
+ query_task_state: {
1988
+ readOnlyHint: true,
1989
+ destructiveHint: false,
1990
+ idempotentHint: true,
1991
+ openWorldHint: false
1992
+ },
1993
+ /** Reads job-result sidecar file for an async-mode dispatch (#3042 / #2631). */
1994
+ get_job_result: {
1995
+ readOnlyHint: true,
1996
+ destructiveHint: false,
1997
+ idempotentHint: true,
1998
+ openWorldHint: false
1999
+ },
2000
+ /** Lists async-mode job summaries from the sidecar dir (#3046 / #2631 Stage 5). */
2001
+ list_jobs: {
2002
+ readOnlyHint: true,
2003
+ destructiveHint: false,
2004
+ idempotentHint: true,
2005
+ openWorldHint: false
2006
+ },
2007
+ /** Cancels an in-flight async-mode job (#3042 Stage 1b / #2631). */
2008
+ cancel_job: {
2009
+ // Mutates the sidecar record — NOT read-only.
2010
+ readOnlyHint: false,
2011
+ // Reversibility: cancel writes a new state, but doesn't delete data —
2012
+ // not destructive in the OWASP sense (no irreversible data loss).
2013
+ destructiveHint: false,
2014
+ // Idempotent: repeat cancels are no-ops with explicit outcome envelope.
2015
+ idempotentHint: true,
2016
+ openWorldHint: false
2017
+ },
2018
+ /** Verifies hash chain of audit log files. */
2019
+ verify_audit_chain: {
2020
+ readOnlyHint: true,
2021
+ destructiveHint: false,
2022
+ idempotentHint: true,
2023
+ openWorldHint: false
2024
+ },
2025
+ /** CI infrastructure health check — composes status-page + recent-runs signals (#3076). */
2026
+ ci_health_check: {
2027
+ readOnlyHint: true,
2028
+ destructiveHint: false,
2029
+ idempotentHint: true,
2030
+ // Hits external services (githubstatus.com + GitHub API) — `openWorldHint`.
2031
+ openWorldHint: true
2032
+ },
2033
+ /** Threshold-gated observability loop; may file GitHub issues when fileIssues=true. */
2034
+ improvement_review: {
2035
+ readOnlyHint: false,
2036
+ destructiveHint: false,
2037
+ idempotentHint: false,
2038
+ openWorldHint: true
2039
+ },
2040
+ // ============================================================================
2041
+ // Repository analysis
2042
+ // ============================================================================
2043
+ /** Analyzes a GitHub repository structure; doesn't write. */
2044
+ repo_analyze: {
2045
+ readOnlyHint: true,
2046
+ destructiveHint: false,
2047
+ idempotentHint: true,
2048
+ openWorldHint: true
2049
+ },
2050
+ /** Generates a security-scanning pipeline plan; returns plan, doesn't write it. */
2051
+ repo_security_plan: {
2052
+ readOnlyHint: true,
2053
+ destructiveHint: false,
2054
+ idempotentHint: true,
2055
+ openWorldHint: true
2056
+ },
2057
+ /** Extracts code symbols from source files. */
2058
+ extract_symbols: {
2059
+ readOnlyHint: true,
2060
+ destructiveHint: false,
2061
+ idempotentHint: true,
2062
+ openWorldHint: false
2063
+ },
2064
+ /** Searches codebase for patterns or symbols. */
2065
+ search_codebase: {
2066
+ readOnlyHint: true,
2067
+ destructiveHint: false,
2068
+ idempotentHint: true,
2069
+ openWorldHint: false
2070
+ },
2071
+ // ============================================================================
2072
+ // Issue triage & registry imports
2073
+ // ============================================================================
2074
+ /** Triages a GitHub issue; may write labels/comments when authorized. */
2075
+ issue_triage: {
2076
+ readOnlyHint: false,
2077
+ destructiveHint: false,
2078
+ idempotentHint: false,
2079
+ openWorldHint: true
2080
+ },
2081
+ /** Generates a draft model-registry entry; doesn't write. */
2082
+ registry_import: {
2083
+ readOnlyHint: true,
2084
+ destructiveHint: false,
2085
+ idempotentHint: true,
2086
+ openWorldHint: true
2087
+ }
2088
+ };
2089
+ function getToolAnnotations(name) {
2090
+ const a = TOOL_ANNOTATIONS[name];
2091
+ if (a === void 0) {
2092
+ throw new Error(
2093
+ `getToolAnnotations: no entry for tool "${name}". Add it to TOOL_ANNOTATIONS in src/mcp/tool-annotations.ts (#2648).`
2094
+ );
2095
+ }
2096
+ return a;
2097
+ }
2098
+
2099
+ export {
2100
+ validateToolInput,
2101
+ createValidator,
2102
+ RateLimiter,
2103
+ createDefaultRateLimiter,
2104
+ PolicyError,
2105
+ PolicyConfigSchema,
2106
+ denyMutationsWithoutModeRule,
2107
+ safePathsRule,
2108
+ PolicyFirewall,
2109
+ createDefaultPolicyFirewall,
2110
+ evaluatePolicy,
2111
+ createPolicyContext,
2112
+ actorFromContext,
2113
+ resultToOutcome,
2114
+ logToolInvocationAudit,
2115
+ logPolicyAudit,
2116
+ logRateLimitAudit,
2117
+ toolSuccess,
2118
+ toolSuccessStructured,
2119
+ toolStructuredError,
2120
+ toolError,
2121
+ createSecureHandler,
2122
+ createMcpNotifier,
2123
+ NOOP_NOTIFIER,
2124
+ abortSignalStorage,
2125
+ withProgressHeartbeat,
2126
+ withAccessPolicy,
2127
+ getToolTimeout,
2128
+ wrapToolWithTimeout,
2129
+ toSdkCallback,
2130
+ toSdkCallbackWithBudgetCheck,
2131
+ getToolAnnotations
2132
+ };
2133
+ //# sourceMappingURL=chunk-5LGAPYX7.js.map