@bookedsolid/reagent 0.7.1 → 0.10.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 (127) hide show
  1. package/README.md +277 -140
  2. package/agents/engineering/motion-designer-interactive.md +5 -4
  3. package/agents/engineering/pr-voice-reviewer.md +229 -0
  4. package/agents/engineering/qa-engineer-automation.md +5 -4
  5. package/agents/engineering/qa-engineer-manual.md +5 -4
  6. package/agents/engineering/qa-lead.md +5 -4
  7. package/agents/engineering/security-engineer-appsec.md +5 -4
  8. package/agents/engineering/security-engineer-compliance.md +5 -4
  9. package/agents/engineering/security-qa-engineer.md +5 -4
  10. package/agents/engineering/technical-writer.md +5 -4
  11. package/agents/product-owner.md +152 -0
  12. package/agents/reagent-orchestrator.md +8 -0
  13. package/commands/pm-status.md +230 -0
  14. package/commands/review-pr.md +197 -0
  15. package/dist/cli/commands/catalyze/gap-detector.d.ts.map +1 -1
  16. package/dist/cli/commands/catalyze/gap-detector.js +1 -3
  17. package/dist/cli/commands/catalyze/gap-detector.js.map +1 -1
  18. package/dist/cli/commands/daemon/index.d.ts +5 -0
  19. package/dist/cli/commands/daemon/index.d.ts.map +1 -0
  20. package/dist/cli/commands/daemon/index.js +59 -0
  21. package/dist/cli/commands/daemon/index.js.map +1 -0
  22. package/dist/cli/commands/daemon/restart.d.ts +10 -0
  23. package/dist/cli/commands/daemon/restart.d.ts.map +1 -0
  24. package/dist/cli/commands/daemon/restart.js +20 -0
  25. package/dist/cli/commands/daemon/restart.js.map +1 -0
  26. package/dist/cli/commands/daemon/start.d.ts +2 -0
  27. package/dist/cli/commands/daemon/start.d.ts.map +1 -0
  28. package/dist/cli/commands/daemon/start.js +143 -0
  29. package/dist/cli/commands/daemon/start.js.map +1 -0
  30. package/dist/cli/commands/daemon/status.d.ts +2 -0
  31. package/dist/cli/commands/daemon/status.d.ts.map +1 -0
  32. package/dist/cli/commands/daemon/status.js +90 -0
  33. package/dist/cli/commands/daemon/status.js.map +1 -0
  34. package/dist/cli/commands/daemon/stop.d.ts +2 -0
  35. package/dist/cli/commands/daemon/stop.d.ts.map +1 -0
  36. package/dist/cli/commands/daemon/stop.js +73 -0
  37. package/dist/cli/commands/daemon/stop.js.map +1 -0
  38. package/dist/cli/commands/init/claude-hooks.d.ts +1 -1
  39. package/dist/cli/commands/init/claude-hooks.d.ts.map +1 -1
  40. package/dist/cli/commands/init/claude-hooks.js +10 -4
  41. package/dist/cli/commands/init/claude-hooks.js.map +1 -1
  42. package/dist/cli/commands/init/index.d.ts.map +1 -1
  43. package/dist/cli/commands/init/index.js +5 -1
  44. package/dist/cli/commands/init/index.js.map +1 -1
  45. package/dist/cli/commands/init/policy.d.ts.map +1 -1
  46. package/dist/cli/commands/init/policy.js +21 -0
  47. package/dist/cli/commands/init/policy.js.map +1 -1
  48. package/dist/cli/commands/init/types.d.ts +16 -0
  49. package/dist/cli/commands/init/types.d.ts.map +1 -1
  50. package/dist/cli/index.js +9 -0
  51. package/dist/cli/index.js.map +1 -1
  52. package/dist/config/daemon-loader.d.ts +16 -0
  53. package/dist/config/daemon-loader.d.ts.map +1 -0
  54. package/dist/config/daemon-loader.js +76 -0
  55. package/dist/config/daemon-loader.js.map +1 -0
  56. package/dist/config/gateway-config.d.ts.map +1 -1
  57. package/dist/config/gateway-config.js +6 -0
  58. package/dist/config/gateway-config.js.map +1 -1
  59. package/dist/config/policy-loader.d.ts +27 -0
  60. package/dist/config/policy-loader.d.ts.map +1 -1
  61. package/dist/config/policy-loader.js +103 -10
  62. package/dist/config/policy-loader.js.map +1 -1
  63. package/dist/gateway/circuit-breaker.d.ts +60 -0
  64. package/dist/gateway/circuit-breaker.d.ts.map +1 -0
  65. package/dist/gateway/circuit-breaker.js +104 -0
  66. package/dist/gateway/circuit-breaker.js.map +1 -0
  67. package/dist/gateway/collision-detector.d.ts +31 -0
  68. package/dist/gateway/collision-detector.d.ts.map +1 -0
  69. package/dist/gateway/collision-detector.js +53 -0
  70. package/dist/gateway/collision-detector.js.map +1 -0
  71. package/dist/gateway/middleware/blocked-paths.js +2 -2
  72. package/dist/gateway/middleware/blocked-paths.js.map +1 -1
  73. package/dist/gateway/middleware/circuit-breaker.d.ts +12 -0
  74. package/dist/gateway/middleware/circuit-breaker.d.ts.map +1 -0
  75. package/dist/gateway/middleware/circuit-breaker.js +44 -0
  76. package/dist/gateway/middleware/circuit-breaker.js.map +1 -0
  77. package/dist/gateway/middleware/injection.d.ts +23 -0
  78. package/dist/gateway/middleware/injection.d.ts.map +1 -0
  79. package/dist/gateway/middleware/injection.js +129 -0
  80. package/dist/gateway/middleware/injection.js.map +1 -0
  81. package/dist/gateway/middleware/policy.js +2 -2
  82. package/dist/gateway/middleware/policy.js.map +1 -1
  83. package/dist/gateway/middleware/rate-limit.d.ts +13 -0
  84. package/dist/gateway/middleware/rate-limit.d.ts.map +1 -0
  85. package/dist/gateway/middleware/rate-limit.js +32 -0
  86. package/dist/gateway/middleware/rate-limit.js.map +1 -0
  87. package/dist/gateway/middleware/redact.d.ts.map +1 -1
  88. package/dist/gateway/middleware/redact.js +7 -0
  89. package/dist/gateway/middleware/redact.js.map +1 -1
  90. package/dist/gateway/middleware/result-size-cap.d.ts +14 -0
  91. package/dist/gateway/middleware/result-size-cap.d.ts.map +1 -0
  92. package/dist/gateway/middleware/result-size-cap.js +49 -0
  93. package/dist/gateway/middleware/result-size-cap.js.map +1 -0
  94. package/dist/gateway/native-tools.js +1 -1
  95. package/dist/gateway/native-tools.js.map +1 -1
  96. package/dist/gateway/rate-limiter.d.ts +47 -0
  97. package/dist/gateway/rate-limiter.d.ts.map +1 -0
  98. package/dist/gateway/rate-limiter.js +89 -0
  99. package/dist/gateway/rate-limiter.js.map +1 -0
  100. package/dist/gateway/server.d.ts.map +1 -1
  101. package/dist/gateway/server.js +27 -1
  102. package/dist/gateway/server.js.map +1 -1
  103. package/dist/gateway/tool-proxy.js +1 -1
  104. package/dist/gateway/tool-proxy.js.map +1 -1
  105. package/dist/types/daemon.d.ts +45 -0
  106. package/dist/types/daemon.d.ts.map +1 -0
  107. package/dist/types/daemon.js +2 -0
  108. package/dist/types/daemon.js.map +1 -0
  109. package/dist/types/gateway.d.ts +9 -0
  110. package/dist/types/gateway.d.ts.map +1 -1
  111. package/dist/types/policy.d.ts +1 -0
  112. package/dist/types/policy.d.ts.map +1 -1
  113. package/hooks/_lib/discord.sh +75 -0
  114. package/hooks/blocked-paths-enforcer.sh +0 -1
  115. package/hooks/changeset-security-gate.sh +143 -0
  116. package/hooks/commit-review-gate.sh +12 -4
  117. package/hooks/import-guard.sh +14 -0
  118. package/hooks/network-exfil-guard.sh +20 -2
  119. package/hooks/pr-issue-link-gate.sh +65 -0
  120. package/hooks/push-review-gate.sh +17 -2
  121. package/hooks/rate-limit-guard.sh +26 -2
  122. package/hooks/reagent-notify.sh +65 -0
  123. package/hooks/security-disclosure-gate.sh +146 -0
  124. package/husky/pre-push.sh +84 -0
  125. package/package.json +10 -2
  126. package/profiles/bst-internal.json +12 -2
  127. package/profiles/client-engagement.json +12 -2
@@ -0,0 +1,16 @@
1
+ import type { DaemonConfig } from '../types/daemon.js';
2
+ /**
3
+ * Async daemon config loader.
4
+ *
5
+ * Reads ~/.reagent/daemon.yaml and returns a validated DaemonConfig.
6
+ * Falls back to defaults if the file is absent — the daemon does not require
7
+ * the config file to exist.
8
+ */
9
+ export declare function loadDaemonConfigAsync(): Promise<DaemonConfig>;
10
+ /**
11
+ * Synchronous daemon config loader — for CLI startup paths that must be sync.
12
+ *
13
+ * Falls back to defaults if ~/.reagent/daemon.yaml is absent.
14
+ */
15
+ export declare function loadDaemonConfig(): DaemonConfig;
16
+ //# sourceMappingURL=daemon-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-loader.d.ts","sourceRoot":"","sources":["../../src/config/daemon-loader.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AA+CvD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,YAAY,CAAC,CAYnE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,CAS/C"}
@@ -0,0 +1,76 @@
1
+ import fs from 'node:fs';
2
+ import fsPromises from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { parse as parseYaml } from 'yaml';
6
+ import { z } from 'zod';
7
+ /** Path to the global daemon config: ~/.reagent/daemon.yaml */
8
+ function getDaemonConfigPath() {
9
+ return path.join(os.homedir(), '.reagent', 'daemon.yaml');
10
+ }
11
+ const DaemonAuthSchema = z.object({
12
+ api_keys: z.array(z.string()).optional(),
13
+ });
14
+ const DaemonConfigSchema = z.object({
15
+ port: z.number().int().min(1).max(65535).default(7777),
16
+ bind: z.string().default('127.0.0.1'),
17
+ session_ttl_minutes: z.number().int().min(1).default(30),
18
+ log_level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
19
+ auth: DaemonAuthSchema.optional(),
20
+ });
21
+ /** Default config used when ~/.reagent/daemon.yaml is absent. */
22
+ const DAEMON_DEFAULTS = {
23
+ port: 7777,
24
+ bind: '127.0.0.1',
25
+ session_ttl_minutes: 30,
26
+ log_level: 'info',
27
+ };
28
+ function parseRawDaemonConfig(raw, configPath) {
29
+ let parsed;
30
+ try {
31
+ parsed = parseYaml(raw);
32
+ }
33
+ catch (yamlErr) {
34
+ throw new Error(`Failed to parse daemon YAML at ${configPath}: ${yamlErr instanceof Error ? yamlErr.message : yamlErr}`);
35
+ }
36
+ try {
37
+ // parseYaml returns null for an empty file — treat as empty object so defaults apply
38
+ return DaemonConfigSchema.parse(parsed ?? {});
39
+ }
40
+ catch (zodErr) {
41
+ throw new Error(`Invalid daemon config schema at ${configPath}: ${zodErr instanceof Error ? zodErr.message : zodErr}`);
42
+ }
43
+ }
44
+ /**
45
+ * Async daemon config loader.
46
+ *
47
+ * Reads ~/.reagent/daemon.yaml and returns a validated DaemonConfig.
48
+ * Falls back to defaults if the file is absent — the daemon does not require
49
+ * the config file to exist.
50
+ */
51
+ export async function loadDaemonConfigAsync() {
52
+ const configPath = getDaemonConfigPath();
53
+ let raw;
54
+ try {
55
+ raw = await fsPromises.readFile(configPath, 'utf8');
56
+ }
57
+ catch {
58
+ // File absent — return defaults
59
+ return { ...DAEMON_DEFAULTS };
60
+ }
61
+ return parseRawDaemonConfig(raw, configPath);
62
+ }
63
+ /**
64
+ * Synchronous daemon config loader — for CLI startup paths that must be sync.
65
+ *
66
+ * Falls back to defaults if ~/.reagent/daemon.yaml is absent.
67
+ */
68
+ export function loadDaemonConfig() {
69
+ const configPath = getDaemonConfigPath();
70
+ if (!fs.existsSync(configPath)) {
71
+ return { ...DAEMON_DEFAULTS };
72
+ }
73
+ const raw = fs.readFileSync(configPath, 'utf8');
74
+ return parseRawDaemonConfig(raw, configPath);
75
+ }
76
+ //# sourceMappingURL=daemon-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-loader.js","sourceRoot":"","sources":["../../src/config/daemon-loader.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-config.d.ts","sourceRoot":"","sources":["../../src/config/gateway-config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAsCvD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAmChE"}
1
+ {"version":3,"file":"gateway-config.d.ts","sourceRoot":"","sources":["../../src/config/gateway-config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA6CvD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAmChE"}
@@ -12,10 +12,16 @@ const DownstreamServerSchema = z.object({
12
12
  args: z.array(z.string()).default([]),
13
13
  env: z.record(z.string()).optional(),
14
14
  tool_overrides: z.record(ToolOverrideSchema).optional(),
15
+ max_concurrent_calls: z.number().int().min(0).optional(),
16
+ calls_per_minute: z.number().int().min(0).optional(),
17
+ });
18
+ const GatewayOptionsSchema = z.object({
19
+ max_result_size_kb: z.number().int().min(1).optional(),
15
20
  });
16
21
  const GatewayConfigSchema = z.object({
17
22
  version: z.string(),
18
23
  servers: z.record(DownstreamServerSchema),
24
+ gateway: GatewayOptionsSchema.optional(),
19
25
  });
20
26
  /**
21
27
  * Resolve `${ENV_VAR}` references in string values.
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-config.js","sourceRoot":"","sources":["../../src/config/gateway-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gateway-config.js","sourceRoot":"","sources":["../../src/config/gateway-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGzC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;IACnC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACpC,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,QAAQ,EAAE;IACvD,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACxD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CACrD,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CACvD,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,sBAAsB,CAAC;IACzC,OAAO,EAAE,oBAAoB,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAC;AAEH;;GAEG;AACH,SAAS,cAAc,CAAC,GAA2B;IACjD,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,MAAM,EAAE,OAAe,EAAE,EAAE;YAC1E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACtC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,OAAO,CAAC,KAAK,CACX,kCAAkC,OAAO,wCAAwC,GAAG,mCAAmC,CACxH,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,IAAI,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;IAElE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,6BAA6B,UAAU,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAEhD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,OAAO,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,mCAAmC,UAAU,KAAK,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CACzG,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,oCAAoC,UAAU,KAAK,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CACvG,CAAC;IACJ,CAAC;IAED,wCAAwC;IACxC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YACf,MAAM,CAAC,GAAG,GAAG,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,3 +1,30 @@
1
1
  import type { Policy } from '../types/index.js';
2
+ /**
3
+ * Async policy loader with TTL cache and mtime-based invalidation.
4
+ *
5
+ * Cache behavior:
6
+ * - On each call, stat the file to get current mtime.
7
+ * - If mtime changed since the cached entry, invalidate immediately regardless of TTL.
8
+ * - If the entry is older than the TTL, re-read from disk.
9
+ * - Otherwise, return the cached entry.
10
+ *
11
+ * TTL is configurable via the REAGENT_POLICY_CACHE_TTL_MS environment variable.
12
+ *
13
+ * PERFORMANCE: fs.promises.readFile avoids blocking the event loop on every tool invocation.
14
+ * SECURITY: mtime invalidation ensures a tightened policy takes effect on the next call.
15
+ * CONCURRENCY: inflightReads map guarantees at most one disk read per baseDir at a time.
16
+ */
17
+ export declare function loadPolicyAsync(baseDir: string): Promise<Policy>;
18
+ /**
19
+ * Synchronous policy loader — retained for CLI startup paths that must be sync.
20
+ * Does NOT use the cache — always reads from disk.
21
+ *
22
+ * Prefer loadPolicyAsync for middleware and any async context.
23
+ */
2
24
  export declare function loadPolicy(baseDir: string): Policy;
25
+ /**
26
+ * Invalidate the cache for a given baseDir.
27
+ * Exposed for testing — production code should rely on TTL and mtime invalidation.
28
+ */
29
+ export declare function invalidatePolicyCache(baseDir?: string): void;
3
30
  //# sourceMappingURL=policy-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy-loader.d.ts","sourceRoot":"","sources":["../../src/config/policy-loader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAuBhD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoClD"}
1
+ {"version":3,"file":"policy-loader.d.ts","sourceRoot":"","sources":["../../src/config/policy-loader.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AA+FhD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BtE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAM5D"}
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import fsPromises from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import { parse as parseYaml } from 'yaml';
4
5
  import { z } from 'zod';
@@ -22,12 +23,28 @@ const PolicySchema = z.object({
22
23
  blocked_paths: z.array(z.string()),
23
24
  notification_channel: z.string().default(''),
24
25
  });
25
- export function loadPolicy(baseDir) {
26
- const policyPath = path.join(baseDir, '.reagent', 'policy.yaml');
27
- if (!fs.existsSync(policyPath)) {
28
- throw new Error(`Policy file not found: ${policyPath}`);
26
+ /** Default TTL for the policy cache — 30 seconds. Configurable via env var. */
27
+ const DEFAULT_CACHE_TTL_MS = 30_000;
28
+ /**
29
+ * Module-level cache: one entry per baseDir (singleton per process).
30
+ * SECURITY: Cache is never used to serve a more permissive policy than the file on disk.
31
+ * mtime-based invalidation ensures policy tightening takes effect before TTL expires.
32
+ */
33
+ const policyCache = new Map();
34
+ // Coalesce concurrent cache misses — two callers racing to populate the same
35
+ // key should share one disk read, not stomp each other. On a gateway under load
36
+ // this is the difference between an occasional wasted syscall and a data race
37
+ // on a security boundary.
38
+ const inflightReads = new Map();
39
+ function applyMaxCeiling(policy) {
40
+ // SECURITY: Enforce max_autonomy_level ceiling — clamp if autonomy_level exceeds it.
41
+ if (LEVEL_ORDER[policy.autonomy_level] > LEVEL_ORDER[policy.max_autonomy_level]) {
42
+ console.error(`[reagent] WARNING: autonomy_level ${policy.autonomy_level} exceeds max_autonomy_level ${policy.max_autonomy_level} — clamping to ${policy.max_autonomy_level}`);
43
+ return { ...policy, autonomy_level: policy.max_autonomy_level };
29
44
  }
30
- const raw = fs.readFileSync(policyPath, 'utf8');
45
+ return policy;
46
+ }
47
+ function parseRawPolicy(raw, policyPath) {
31
48
  let parsed;
32
49
  try {
33
50
  parsed = parseYaml(raw);
@@ -42,11 +59,87 @@ export function loadPolicy(baseDir) {
42
59
  catch (zodErr) {
43
60
  throw new Error(`Invalid policy schema at ${policyPath}: ${zodErr instanceof Error ? zodErr.message : zodErr}`);
44
61
  }
45
- // SECURITY: Enforce max_autonomy_level ceiling — clamp if autonomy_level exceeds it.
46
- if (LEVEL_ORDER[policy.autonomy_level] > LEVEL_ORDER[policy.max_autonomy_level]) {
47
- console.error(`[reagent] WARNING: autonomy_level ${policy.autonomy_level} exceeds max_autonomy_level ${policy.max_autonomy_level} — clamping to ${policy.max_autonomy_level}`);
48
- policy = { ...policy, autonomy_level: policy.max_autonomy_level };
49
- }
62
+ return applyMaxCeiling(policy);
63
+ }
64
+ /**
65
+ * Reads and parses the policy file from disk, then populates the cache.
66
+ * Kept separate so the inflight map can hold a single promise per baseDir
67
+ * and multiple concurrent callers can join it rather than each issuing their
68
+ * own stat+read pair.
69
+ */
70
+ async function readPolicyFromDisk(baseDir, policyPath, currentMtime) {
71
+ const raw = await fsPromises.readFile(policyPath, 'utf8');
72
+ const policy = parseRawPolicy(raw, policyPath);
73
+ policyCache.set(baseDir, { policy, cachedAt: Date.now(), mtimeMs: currentMtime });
50
74
  return policy;
51
75
  }
76
+ /**
77
+ * Async policy loader with TTL cache and mtime-based invalidation.
78
+ *
79
+ * Cache behavior:
80
+ * - On each call, stat the file to get current mtime.
81
+ * - If mtime changed since the cached entry, invalidate immediately regardless of TTL.
82
+ * - If the entry is older than the TTL, re-read from disk.
83
+ * - Otherwise, return the cached entry.
84
+ *
85
+ * TTL is configurable via the REAGENT_POLICY_CACHE_TTL_MS environment variable.
86
+ *
87
+ * PERFORMANCE: fs.promises.readFile avoids blocking the event loop on every tool invocation.
88
+ * SECURITY: mtime invalidation ensures a tightened policy takes effect on the next call.
89
+ * CONCURRENCY: inflightReads map guarantees at most one disk read per baseDir at a time.
90
+ */
91
+ export async function loadPolicyAsync(baseDir) {
92
+ const policyPath = path.join(baseDir, '.reagent', 'policy.yaml');
93
+ const ttlMs = Number(process.env.REAGENT_POLICY_CACHE_TTL_MS ?? DEFAULT_CACHE_TTL_MS);
94
+ const now = Date.now();
95
+ // Check mtime before consulting the cache — a changed file always invalidates.
96
+ let currentMtime;
97
+ try {
98
+ const stat = await fsPromises.stat(policyPath);
99
+ currentMtime = stat.mtimeMs;
100
+ }
101
+ catch {
102
+ throw new Error(`Policy file not found: ${policyPath}`);
103
+ }
104
+ const cached = policyCache.get(baseDir);
105
+ if (cached !== undefined && cached.mtimeMs === currentMtime && now - cached.cachedAt < ttlMs) {
106
+ return cached.policy;
107
+ }
108
+ // If another caller already kicked off a read for this baseDir, join it rather
109
+ // than issuing a second syscall and racing on the cache write.
110
+ const inflight = inflightReads.get(baseDir);
111
+ if (inflight)
112
+ return inflight;
113
+ const read = readPolicyFromDisk(baseDir, policyPath, currentMtime).finally(() => {
114
+ inflightReads.delete(baseDir);
115
+ });
116
+ inflightReads.set(baseDir, read);
117
+ return read;
118
+ }
119
+ /**
120
+ * Synchronous policy loader — retained for CLI startup paths that must be sync.
121
+ * Does NOT use the cache — always reads from disk.
122
+ *
123
+ * Prefer loadPolicyAsync for middleware and any async context.
124
+ */
125
+ export function loadPolicy(baseDir) {
126
+ const policyPath = path.join(baseDir, '.reagent', 'policy.yaml');
127
+ if (!fs.existsSync(policyPath)) {
128
+ throw new Error(`Policy file not found: ${policyPath}`);
129
+ }
130
+ const raw = fs.readFileSync(policyPath, 'utf8');
131
+ return parseRawPolicy(raw, policyPath);
132
+ }
133
+ /**
134
+ * Invalidate the cache for a given baseDir.
135
+ * Exposed for testing — production code should rely on TTL and mtime invalidation.
136
+ */
137
+ export function invalidatePolicyCache(baseDir) {
138
+ if (baseDir === undefined) {
139
+ policyCache.clear();
140
+ }
141
+ else {
142
+ policyCache.delete(baseDir);
143
+ }
144
+ }
52
145
  //# sourceMappingURL=policy-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy-loader.js","sourceRoot":"","sources":["../../src/config/policy-loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"policy-loader.js","sourceRoot":"","sources":["../../src/config/policy-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ export type CircuitState = 'closed' | 'open' | 'half-open';
2
+ export interface CircuitBreakerOptions {
3
+ /** Consecutive failures before opening the circuit. Default: 5 */
4
+ failureThreshold?: number;
5
+ /** Milliseconds to wait in open state before moving to half-open. Default: 30_000 */
6
+ cooldownMs?: number;
7
+ }
8
+ export interface CircuitStatus {
9
+ state: CircuitState;
10
+ serverName: string;
11
+ /** ISO timestamp of when the circuit will attempt recovery (only set when open) */
12
+ retryAt?: string;
13
+ }
14
+ interface CircuitEntry {
15
+ state: CircuitState;
16
+ consecutiveFailures: number;
17
+ openedAt: number | null;
18
+ failureThreshold: number;
19
+ cooldownMs: number;
20
+ }
21
+ /**
22
+ * Per-server circuit breaker.
23
+ *
24
+ * State machine:
25
+ * closed → open after N consecutive failures
26
+ * open → half-open after cooldown period
27
+ * half-open → closed on next success
28
+ * half-open → open on next failure
29
+ *
30
+ * State is kept in memory — resets to closed on gateway restart.
31
+ */
32
+ export declare class CircuitBreaker {
33
+ private circuits;
34
+ private defaultOptions;
35
+ constructor(defaults?: CircuitBreakerOptions);
36
+ private getOrCreate;
37
+ /**
38
+ * Check whether a call to `serverName` is allowed.
39
+ *
40
+ * Returns null if the call may proceed, or a CircuitStatus with the
41
+ * current state if the circuit is open (or still deciding in half-open).
42
+ *
43
+ * Side effect: transitions open → half-open if cooldown has elapsed.
44
+ */
45
+ isAllowed(serverName: string): CircuitStatus | null;
46
+ /**
47
+ * Record a successful call. Transitions half-open → closed.
48
+ * In closed state, resets the consecutive failure counter.
49
+ */
50
+ recordSuccess(serverName: string): void;
51
+ /**
52
+ * Record a failed call. Increments failure counter.
53
+ * Transitions closed → open when threshold is reached, or half-open → open immediately.
54
+ */
55
+ recordFailure(serverName: string): void;
56
+ /** Expose internal state for testing */
57
+ getCircuit(serverName: string): CircuitEntry | undefined;
58
+ }
59
+ export {};
60
+ //# sourceMappingURL=circuit-breaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/gateway/circuit-breaker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,cAAc,CAAkC;gBAE5C,QAAQ,GAAE,qBAA0B;IAOhD,OAAO,CAAC,WAAW;IAenB;;;;;;;OAOG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IA6BnD;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAYvC;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAoBvC,wCAAwC;IACxC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGzD"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Per-server circuit breaker.
3
+ *
4
+ * State machine:
5
+ * closed → open after N consecutive failures
6
+ * open → half-open after cooldown period
7
+ * half-open → closed on next success
8
+ * half-open → open on next failure
9
+ *
10
+ * State is kept in memory — resets to closed on gateway restart.
11
+ */
12
+ export class CircuitBreaker {
13
+ circuits = new Map();
14
+ defaultOptions;
15
+ constructor(defaults = {}) {
16
+ this.defaultOptions = {
17
+ failureThreshold: defaults.failureThreshold ?? 5,
18
+ cooldownMs: defaults.cooldownMs ?? 30_000,
19
+ };
20
+ }
21
+ getOrCreate(serverName) {
22
+ let entry = this.circuits.get(serverName);
23
+ if (!entry) {
24
+ entry = {
25
+ state: 'closed',
26
+ consecutiveFailures: 0,
27
+ openedAt: null,
28
+ failureThreshold: this.defaultOptions.failureThreshold,
29
+ cooldownMs: this.defaultOptions.cooldownMs,
30
+ };
31
+ this.circuits.set(serverName, entry);
32
+ }
33
+ return entry;
34
+ }
35
+ /**
36
+ * Check whether a call to `serverName` is allowed.
37
+ *
38
+ * Returns null if the call may proceed, or a CircuitStatus with the
39
+ * current state if the circuit is open (or still deciding in half-open).
40
+ *
41
+ * Side effect: transitions open → half-open if cooldown has elapsed.
42
+ */
43
+ isAllowed(serverName) {
44
+ const entry = this.getOrCreate(serverName);
45
+ if (entry.state === 'closed')
46
+ return null;
47
+ if (entry.state === 'open') {
48
+ const elapsed = Date.now() - (entry.openedAt ?? 0);
49
+ if (elapsed >= entry.cooldownMs) {
50
+ // Move to half-open so the next call acts as a probe
51
+ entry.state = 'half-open';
52
+ entry.consecutiveFailures = 0;
53
+ console.error(`[reagent] circuit-breaker: "${serverName}" transitioned open → half-open (probing recovery)`);
54
+ return null; // Let the call through as the probe
55
+ }
56
+ const retryAt = new Date((entry.openedAt ?? 0) + entry.cooldownMs).toISOString();
57
+ return {
58
+ state: 'open',
59
+ serverName,
60
+ retryAt,
61
+ };
62
+ }
63
+ // half-open: allow exactly one probe call through
64
+ return null;
65
+ }
66
+ /**
67
+ * Record a successful call. Transitions half-open → closed.
68
+ * In closed state, resets the consecutive failure counter.
69
+ */
70
+ recordSuccess(serverName) {
71
+ const entry = this.getOrCreate(serverName);
72
+ if (entry.state === 'half-open') {
73
+ entry.state = 'closed';
74
+ entry.consecutiveFailures = 0;
75
+ entry.openedAt = null;
76
+ console.error(`[reagent] circuit-breaker: "${serverName}" recovered — circuit closed`);
77
+ }
78
+ else if (entry.state === 'closed') {
79
+ entry.consecutiveFailures = 0;
80
+ }
81
+ }
82
+ /**
83
+ * Record a failed call. Increments failure counter.
84
+ * Transitions closed → open when threshold is reached, or half-open → open immediately.
85
+ */
86
+ recordFailure(serverName) {
87
+ const entry = this.getOrCreate(serverName);
88
+ if (entry.state === 'open')
89
+ return; // Already open — nothing to do
90
+ entry.consecutiveFailures++;
91
+ const shouldOpen = entry.state === 'half-open' || entry.consecutiveFailures >= entry.failureThreshold;
92
+ if (shouldOpen) {
93
+ entry.state = 'open';
94
+ entry.openedAt = Date.now();
95
+ const retryAt = new Date(entry.openedAt + entry.cooldownMs).toISOString();
96
+ console.error(`[reagent] circuit-breaker: "${serverName}" OPENED after ${entry.consecutiveFailures} failure(s) — will retry at ${retryAt}`);
97
+ }
98
+ }
99
+ /** Expose internal state for testing */
100
+ getCircuit(serverName) {
101
+ return this.circuits.get(serverName);
102
+ }
103
+ }
104
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/gateway/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { ClientManager } from './client-manager.js';
2
+ export interface CollisionEntry {
3
+ toolName: string;
4
+ /** Server that "owns" the original name */
5
+ primaryServer: string;
6
+ /** Server whose tool is shadowed and will be prefixed */
7
+ shadowedServer: string;
8
+ /** The namespaced name the shadowed tool will be registered under */
9
+ namespacedName: string;
10
+ }
11
+ export interface CollisionReport {
12
+ collisions: CollisionEntry[];
13
+ /**
14
+ * Maps `serverName/toolName` → registered name.
15
+ * For primary tools: registered name equals toolName.
16
+ * For shadowed tools: registered name is `serverName/toolName`.
17
+ */
18
+ nameMap: Map<string, string>;
19
+ }
20
+ /**
21
+ * Detects tool name collisions across all downstream servers.
22
+ *
23
+ * When two servers expose the same bare tool name, the first server encountered
24
+ * wins and keeps the original name. The second server's tool gets prefixed as
25
+ * `{serverName}/{toolName}`. This is deterministic (Map insertion order).
26
+ *
27
+ * Runs before the gateway starts accepting connections so admins know about
28
+ * shadowed tools at startup rather than silently losing one.
29
+ */
30
+ export declare function detectToolCollisions(clientManager: ClientManager): Promise<CollisionReport>;
31
+ //# sourceMappingURL=collision-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collision-detector.d.ts","sourceRoot":"","sources":["../../src/gateway/collision-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B;;;;OAIG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAiDjG"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Detects tool name collisions across all downstream servers.
3
+ *
4
+ * When two servers expose the same bare tool name, the first server encountered
5
+ * wins and keeps the original name. The second server's tool gets prefixed as
6
+ * `{serverName}/{toolName}`. This is deterministic (Map insertion order).
7
+ *
8
+ * Runs before the gateway starts accepting connections so admins know about
9
+ * shadowed tools at startup rather than silently losing one.
10
+ */
11
+ export async function detectToolCollisions(clientManager) {
12
+ // toolName → first server to register it
13
+ const seen = new Map();
14
+ const collisions = [];
15
+ // key = `serverName::toolName`, value = what name it will be registered as
16
+ const nameMap = new Map();
17
+ for (const [serverName, managed] of clientManager.getAllClients()) {
18
+ let tools;
19
+ try {
20
+ const result = await managed.client.listTools();
21
+ tools = result.tools;
22
+ }
23
+ catch (err) {
24
+ console.error(`[reagent] collision-detector: could not list tools from "${serverName}":`, err instanceof Error ? err.message : err);
25
+ continue;
26
+ }
27
+ for (const tool of tools) {
28
+ const key = `${serverName}::${tool.name}`;
29
+ const prior = seen.get(tool.name);
30
+ if (prior !== undefined) {
31
+ // This tool name was already claimed by `prior` server
32
+ const namespacedName = `${serverName}/${tool.name}`;
33
+ collisions.push({
34
+ toolName: tool.name,
35
+ primaryServer: prior,
36
+ shadowedServer: serverName,
37
+ namespacedName,
38
+ });
39
+ nameMap.set(key, namespacedName);
40
+ // Warn with enough detail that an admin can act on it
41
+ console.error(`[reagent] WARN [GHSA-4j9r] tool name collision: "${tool.name}" is exposed by ` +
42
+ `both "${prior}" (primary, keeps original name) and "${serverName}" ` +
43
+ `(shadowed, will be registered as "${namespacedName}")`);
44
+ }
45
+ else {
46
+ seen.set(tool.name, serverName);
47
+ nameMap.set(key, tool.name);
48
+ }
49
+ }
50
+ }
51
+ return { collisions, nameMap };
52
+ }
53
+ //# sourceMappingURL=collision-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collision-detector.js","sourceRoot":"","sources":["../../src/gateway/collision-detector.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,aAA4B;IACrE,yCAAyC;IACzC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,UAAU,GAAqB,EAAE,CAAC;IACxC,2EAA2E;IAC3E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1C,KAAK,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,aAAa,CAAC,aAAa,EAAE,EAAE,CAAC;QAClE,IAAI,KAA8B,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YAChD,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACX,4DAA4D,UAAU,IAAI,EAC1E,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CACzC,CAAC;YACF,SAAS;QACX,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,GAAG,UAAU,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAElC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,uDAAuD;gBACvD,MAAM,cAAc,GAAG,GAAG,UAAU,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACpD,UAAU,CAAC,IAAI,CAAC;oBACd,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,aAAa,EAAE,KAAK;oBACpB,cAAc,EAAE,UAAU;oBAC1B,cAAc;iBACf,CAAC,CAAC;gBACH,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;gBAEjC,sDAAsD;gBACtD,OAAO,CAAC,KAAK,CACX,oDAAoD,IAAI,CAAC,IAAI,kBAAkB;oBAC7E,SAAS,KAAK,yCAAyC,UAAU,IAAI;oBACrE,qCAAqC,cAAc,IAAI,CAC1D,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { InvocationStatus } from '../../types/index.js';
3
- import { loadPolicy } from '../../config/policy-loader.js';
3
+ import { loadPolicyAsync } from '../../config/policy-loader.js';
4
4
  /**
5
5
  * Pre-execution middleware: denies tool invocations whose arguments
6
6
  * reference paths that are in the policy's blocked_paths list.
@@ -16,7 +16,7 @@ export function createBlockedPathsMiddleware(initialPolicy, baseDir) {
16
16
  let blockedPaths = initialPolicy.blocked_paths;
17
17
  if (baseDir) {
18
18
  try {
19
- const policy = loadPolicy(baseDir);
19
+ const policy = await loadPolicyAsync(baseDir);
20
20
  blockedPaths = policy.blocked_paths;
21
21
  }
22
22
  catch {
@@ -1 +1 @@
1
- {"version":3,"file":"blocked-paths.js","sourceRoot":"","sources":["../../../src/gateway/middleware/blocked-paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"blocked-paths.js","sourceRoot":"","sources":["../../../src/gateway/middleware/blocked-paths.ts"],"names":[],"mappings":"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"}