@develler/remediation-agent 1.0.2

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 (73) hide show
  1. package/dist/commands/connect.d.ts +17 -0
  2. package/dist/commands/connect.d.ts.map +1 -0
  3. package/dist/commands/connect.js +138 -0
  4. package/dist/commands/connect.js.map +1 -0
  5. package/dist/contracts/agentConnection.d.ts +28 -0
  6. package/dist/contracts/agentConnection.d.ts.map +1 -0
  7. package/dist/contracts/agentConnection.js +3 -0
  8. package/dist/contracts/agentConnection.js.map +1 -0
  9. package/dist/contracts/lifecycleInterceptor.d.ts +22 -0
  10. package/dist/contracts/lifecycleInterceptor.d.ts.map +1 -0
  11. package/dist/contracts/lifecycleInterceptor.js +3 -0
  12. package/dist/contracts/lifecycleInterceptor.js.map +1 -0
  13. package/dist/controllers/extractionController.d.ts +12 -0
  14. package/dist/controllers/extractionController.d.ts.map +1 -0
  15. package/dist/controllers/extractionController.js +91 -0
  16. package/dist/controllers/extractionController.js.map +1 -0
  17. package/dist/controllers/webhookController.d.ts +16 -0
  18. package/dist/controllers/webhookController.d.ts.map +1 -0
  19. package/dist/controllers/webhookController.js +74 -0
  20. package/dist/controllers/webhookController.js.map +1 -0
  21. package/dist/exceptions/ReplayError.d.ts +4 -0
  22. package/dist/exceptions/ReplayError.d.ts.map +1 -0
  23. package/dist/exceptions/ReplayError.js +11 -0
  24. package/dist/exceptions/ReplayError.js.map +1 -0
  25. package/dist/exceptions/SignatureError.d.ts +4 -0
  26. package/dist/exceptions/SignatureError.d.ts.map +1 -0
  27. package/dist/exceptions/SignatureError.js +11 -0
  28. package/dist/exceptions/SignatureError.js.map +1 -0
  29. package/dist/index.d.ts +48 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +98 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/logger.d.ts +7 -0
  34. package/dist/logger.d.ts.map +1 -0
  35. package/dist/logger.js +26 -0
  36. package/dist/logger.js.map +1 -0
  37. package/dist/middleware/remediationInterceptor.d.ts +42 -0
  38. package/dist/middleware/remediationInterceptor.d.ts.map +1 -0
  39. package/dist/middleware/remediationInterceptor.js +144 -0
  40. package/dist/middleware/remediationInterceptor.js.map +1 -0
  41. package/dist/services/AgentConnectionService.d.ts +37 -0
  42. package/dist/services/AgentConnectionService.d.ts.map +1 -0
  43. package/dist/services/AgentConnectionService.js +186 -0
  44. package/dist/services/AgentConnectionService.js.map +1 -0
  45. package/dist/services/AstExtractorService.d.ts +18 -0
  46. package/dist/services/AstExtractorService.d.ts.map +1 -0
  47. package/dist/services/AstExtractorService.js +166 -0
  48. package/dist/services/AstExtractorService.js.map +1 -0
  49. package/dist/services/CircuitBreakerService.d.ts +29 -0
  50. package/dist/services/CircuitBreakerService.d.ts.map +1 -0
  51. package/dist/services/CircuitBreakerService.js +93 -0
  52. package/dist/services/CircuitBreakerService.js.map +1 -0
  53. package/dist/services/InstructionCacheService.d.ts +28 -0
  54. package/dist/services/InstructionCacheService.d.ts.map +1 -0
  55. package/dist/services/InstructionCacheService.js +79 -0
  56. package/dist/services/InstructionCacheService.js.map +1 -0
  57. package/dist/services/MaskingEngine.d.ts +27 -0
  58. package/dist/services/MaskingEngine.d.ts.map +1 -0
  59. package/dist/services/MaskingEngine.js +88 -0
  60. package/dist/services/MaskingEngine.js.map +1 -0
  61. package/dist/types/extraction.d.ts +27 -0
  62. package/dist/types/extraction.d.ts.map +1 -0
  63. package/dist/types/extraction.js +16 -0
  64. package/dist/types/extraction.js.map +1 -0
  65. package/dist/types/instructions.d.ts +58 -0
  66. package/dist/types/instructions.d.ts.map +1 -0
  67. package/dist/types/instructions.js +203 -0
  68. package/dist/types/instructions.js.map +1 -0
  69. package/dist/types/wireProtocol.d.ts +117 -0
  70. package/dist/types/wireProtocol.d.ts.map +1 -0
  71. package/dist/types/wireProtocol.js +8 -0
  72. package/dist/types/wireProtocol.js.map +1 -0
  73. package/package.json +46 -0
package/dist/index.js ADDED
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ /**
3
+ * Develler — Node.js/Express Agent
4
+ *
5
+ * Public API. Import and call createRemediationMiddleware() in your Express app:
6
+ *
7
+ * import { createRemediationMiddleware } from 'develler-remediation-agent';
8
+ *
9
+ * const remediation = await createRemediationMiddleware();
10
+ * app.use(remediation.handler());
11
+ *
12
+ * // Optional: mount the webhook receiver so the SaaS can push instructions.
13
+ * app.use(remediation.webhookRouter());
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.AstExtractorService = exports.MaskingEngine = exports.ReplayError = exports.SignatureError = exports.InstructionCollection = exports.RuntimeInstruction = exports.MaskRule = void 0;
20
+ exports.createRemediationMiddleware = createRemediationMiddleware;
21
+ const ioredis_1 = __importDefault(require("ioredis"));
22
+ const express_1 = require("express");
23
+ const fs_1 = require("fs");
24
+ const path_1 = require("path");
25
+ const CircuitBreakerService_js_1 = require("./services/CircuitBreakerService.js");
26
+ const InstructionCacheService_js_1 = require("./services/InstructionCacheService.js");
27
+ const MaskingEngine_js_1 = require("./services/MaskingEngine.js");
28
+ const AgentConnectionService_js_1 = require("./services/AgentConnectionService.js");
29
+ const remediationInterceptor_js_1 = require("./middleware/remediationInterceptor.js");
30
+ const webhookController_js_1 = require("./controllers/webhookController.js");
31
+ var instructions_js_1 = require("./types/instructions.js");
32
+ Object.defineProperty(exports, "MaskRule", { enumerable: true, get: function () { return instructions_js_1.MaskRule; } });
33
+ Object.defineProperty(exports, "RuntimeInstruction", { enumerable: true, get: function () { return instructions_js_1.RuntimeInstruction; } });
34
+ Object.defineProperty(exports, "InstructionCollection", { enumerable: true, get: function () { return instructions_js_1.InstructionCollection; } });
35
+ var SignatureError_js_1 = require("./exceptions/SignatureError.js");
36
+ Object.defineProperty(exports, "SignatureError", { enumerable: true, get: function () { return SignatureError_js_1.SignatureError; } });
37
+ var ReplayError_js_1 = require("./exceptions/ReplayError.js");
38
+ Object.defineProperty(exports, "ReplayError", { enumerable: true, get: function () { return ReplayError_js_1.ReplayError; } });
39
+ var MaskingEngine_js_2 = require("./services/MaskingEngine.js");
40
+ Object.defineProperty(exports, "MaskingEngine", { enumerable: true, get: function () { return MaskingEngine_js_2.MaskingEngine; } });
41
+ var AstExtractorService_js_1 = require("./services/AstExtractorService.js");
42
+ Object.defineProperty(exports, "AstExtractorService", { enumerable: true, get: function () { return AstExtractorService_js_1.AstExtractorService; } });
43
+ // ---------------------------------------------------------------------------
44
+ // Factory
45
+ // ---------------------------------------------------------------------------
46
+ async function createRemediationMiddleware(cfg = {}) {
47
+ const enabled = cfg.enabled ?? (process.env['REMEDIATION_INTERCEPTOR_ENABLED'] !== 'false');
48
+ const cbTtl = cfg.cbTtlSeconds ?? parseInt(process.env['REMEDIATION_CB_TTL'] ?? '60', 10);
49
+ const webhookPath = cfg.webhookPath ?? (process.env['REMEDIATION_WEBHOOK_PATH'] ?? '/api/remediation/v1/webhook');
50
+ const webhookOn = cfg.webhookEnabled ?? (process.env['REMEDIATION_WEBHOOK_ENABLED'] !== 'false');
51
+ const redisUrl = cfg.redisUrl ?? (process.env['REDIS_URL'] ?? process.env['REMEDIATION_REDIS_URL'] ?? 'redis://127.0.0.1:6379');
52
+ const connection = cfg.connection ?? loadConnectionConfig();
53
+ const redis = new ioredis_1.default(redisUrl, { lazyConnect: false, enableOfflineQueue: false });
54
+ const cache = new InstructionCacheService_js_1.InstructionCacheService(redis, connection.client_id);
55
+ const breaker = new CircuitBreakerService_js_1.CircuitBreakerService(redis, connection.client_id, cbTtl);
56
+ const masker = new MaskingEngine_js_1.MaskingEngine();
57
+ const agentConn = new AgentConnectionService_js_1.AgentConnectionService(redis, cache, connection);
58
+ const interceptor = new remediationInterceptor_js_1.RemediationInterceptor(agentConn, masker, breaker, enabled);
59
+ return {
60
+ handler() {
61
+ return interceptor.handler();
62
+ },
63
+ webhookRouter() {
64
+ const router = (0, express_1.Router)();
65
+ router.use(require('express').json());
66
+ (0, webhookController_js_1.registerWebhookRoute)(router, agentConn, connection, webhookPath, webhookOn);
67
+ return router;
68
+ },
69
+ async resetCircuitBreaker() {
70
+ await interceptor.resetCircuitBreaker();
71
+ },
72
+ };
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ function loadConnectionConfig() {
76
+ const configPath = (0, path_1.join)(process.cwd(), '.remediation-connection.json');
77
+ if ((0, fs_1.existsSync)(configPath)) {
78
+ return JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
79
+ }
80
+ // Fallback to env vars (set these in your CI/CD secrets instead of a file).
81
+ const clientId = process.env['REMEDIATION_CLIENT_ID'] ?? '';
82
+ const token = process.env['REMEDIATION_TOKEN'] ?? '';
83
+ const saasUrl = (process.env['REMEDIATION_SAAS_URL'] ?? '').replace(/\/+$/, '');
84
+ if (!clientId || !token || !saasUrl) {
85
+ throw new Error('Remediation Engine: no connection config found. ' +
86
+ 'Run `npx remediation-connect` or set REMEDIATION_CLIENT_ID, REMEDIATION_TOKEN, REMEDIATION_SAAS_URL.');
87
+ }
88
+ return {
89
+ client_id: clientId,
90
+ token,
91
+ saas_url: saasUrl,
92
+ channel_type: 'polling',
93
+ poll_url: '/api/remediation/v1/instructions/pending',
94
+ poll_interval_seconds: 30,
95
+ connected_at: new Date().toISOString(),
96
+ };
97
+ }
98
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Dedicated logger for the Remediation Engine agent.
3
+ * Writes to a separate file so agent logs never mix into the host app's log stream.
4
+ * No PII-adjacent data is ever logged — only instruction IDs and error messages.
5
+ */
6
+ export declare const logger: import("winston").Logger;
7
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,MAAM,0BAkBjB,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ const winston_1 = require("winston");
5
+ const path_1 = require("path");
6
+ /**
7
+ * Dedicated logger for the Remediation Engine agent.
8
+ * Writes to a separate file so agent logs never mix into the host app's log stream.
9
+ * No PII-adjacent data is ever logged — only instruction IDs and error messages.
10
+ */
11
+ exports.logger = (0, winston_1.createLogger)({
12
+ level: process.env['REMEDIATION_LOG_LEVEL'] ?? 'error',
13
+ format: winston_1.format.combine(winston_1.format.timestamp(), winston_1.format.errors({ stack: true }), winston_1.format.json()),
14
+ defaultMeta: { service: 'remediation-agent' },
15
+ transports: [
16
+ new winston_1.transports.File({
17
+ filename: (0, path_1.join)(process.cwd(), 'logs', 'remediation.log'),
18
+ maxsize: 10 * 1024 * 1024, // 10 MB
19
+ maxFiles: 5,
20
+ tailable: true,
21
+ }),
22
+ ],
23
+ // Silent by default in test environments.
24
+ silent: process.env['NODE_ENV'] === 'test',
25
+ });
26
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":";;;AAAA,qCAA2D;AAC3D,+BAA4B;AAE5B;;;;GAIG;AACU,QAAA,MAAM,GAAG,IAAA,sBAAY,EAAC;IACjC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,OAAO;IACtD,MAAM,EAAE,gBAAM,CAAC,OAAO,CACpB,gBAAM,CAAC,SAAS,EAAE,EAClB,gBAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAC9B,gBAAM,CAAC,IAAI,EAAE,CACd;IACD,WAAW,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE;IAC7C,UAAU,EAAE;QACV,IAAI,oBAAU,CAAC,IAAI,CAAC;YAClB,QAAQ,EAAE,IAAA,WAAI,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,iBAAiB,CAAC;YACxD,OAAO,EAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAG,QAAQ;YACrC,QAAQ,EAAE,CAAC;YACX,QAAQ,EAAE,IAAI;SACf,CAAC;KACH;IACD,0CAA0C;IAC1C,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,MAAM;CAC3C,CAAC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { Request, RequestHandler, Response } from 'express';
2
+ import type { AgentConnectionInterface } from '../contracts/agentConnection.js';
3
+ import type { LifecycleInterceptorInterface } from '../contracts/lifecycleInterceptor.js';
4
+ import type { MaskRule } from '../types/instructions.js';
5
+ import { CircuitBreakerService } from '../services/CircuitBreakerService.js';
6
+ import { MaskingEngine } from '../services/MaskingEngine.js';
7
+ /**
8
+ * Production-grade in-memory interceptor for Express (Mode B).
9
+ *
10
+ * Handle flow (mirrors the PHP RemediationInterceptor exactly):
11
+ * 1. Circuit breaker open? → call next() immediately, zero overhead.
12
+ * 2. Load active instructions from Redis.
13
+ * 3. Route-block match? → respond with 503/custom, do not call next().
14
+ * 4. Override res.json() BEFORE calling next() so the route handler's
15
+ * response is captured before it reaches the socket.
16
+ * 5. Inside the override: inject headers, deep-clone, mask PII, swap.
17
+ * 6. Call next().
18
+ *
19
+ * On ANY exception in steps 2–5: trip circuit breaker, log silently,
20
+ * call next() with the original res.json untouched. Never a partial mask.
21
+ *
22
+ * Node.js-specific challenge solved here:
23
+ * Express does not buffer outbound response bodies. We intercept res.json()
24
+ * and res.send() before next() is called, so that when the downstream
25
+ * handler calls res.json(data), our closure runs first.
26
+ */
27
+ export declare class RemediationInterceptor implements LifecycleInterceptorInterface {
28
+ private readonly connection;
29
+ private readonly masker;
30
+ private readonly breaker;
31
+ private readonly enabled;
32
+ constructor(connection: AgentConnectionInterface, masker: MaskingEngine, breaker: CircuitBreakerService, enabled: boolean);
33
+ /** Returns an Express RequestHandler — the function registered via app.use(). */
34
+ handler(): RequestHandler;
35
+ interceptRequest(req: Request, res: Response): Promise<boolean>;
36
+ interceptRequest(req: Request, res: Response, block: unknown): Promise<boolean>;
37
+ applyMaskingRules(data: unknown, maskRules: MaskRule[]): unknown;
38
+ isCircuitBreakerOpen(): Promise<boolean>;
39
+ tripCircuitBreaker(reason: Error): Promise<void>;
40
+ resetCircuitBreaker(): Promise<void>;
41
+ }
42
+ //# sourceMappingURL=remediationInterceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remediationInterceptor.d.ts","sourceRoot":"","sources":["../../src/middleware/remediationInterceptor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,sCAAsC,CAAC;AAC1F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,sBAAuB,YAAW,6BAA6B;IAExE,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAHP,UAAU,EAAE,wBAAwB,EACpC,MAAM,EAAM,aAAa,EACzB,OAAO,EAAK,qBAAqB,EACjC,OAAO,EAAK,OAAO;IAGtC,iFAAiF;IACjF,OAAO,IAAI,cAAc;IAiFnB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAC/D,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BrF,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO;IAI1D,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAIxC,kBAAkB,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;CAG3C"}
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RemediationInterceptor = void 0;
4
+ /**
5
+ * Production-grade in-memory interceptor for Express (Mode B).
6
+ *
7
+ * Handle flow (mirrors the PHP RemediationInterceptor exactly):
8
+ * 1. Circuit breaker open? → call next() immediately, zero overhead.
9
+ * 2. Load active instructions from Redis.
10
+ * 3. Route-block match? → respond with 503/custom, do not call next().
11
+ * 4. Override res.json() BEFORE calling next() so the route handler's
12
+ * response is captured before it reaches the socket.
13
+ * 5. Inside the override: inject headers, deep-clone, mask PII, swap.
14
+ * 6. Call next().
15
+ *
16
+ * On ANY exception in steps 2–5: trip circuit breaker, log silently,
17
+ * call next() with the original res.json untouched. Never a partial mask.
18
+ *
19
+ * Node.js-specific challenge solved here:
20
+ * Express does not buffer outbound response bodies. We intercept res.json()
21
+ * and res.send() before next() is called, so that when the downstream
22
+ * handler calls res.json(data), our closure runs first.
23
+ */
24
+ class RemediationInterceptor {
25
+ connection;
26
+ masker;
27
+ breaker;
28
+ enabled;
29
+ constructor(connection, masker, breaker, enabled) {
30
+ this.connection = connection;
31
+ this.masker = masker;
32
+ this.breaker = breaker;
33
+ this.enabled = enabled;
34
+ }
35
+ /** Returns an Express RequestHandler — the function registered via app.use(). */
36
+ handler() {
37
+ return async (req, res, next) => {
38
+ if (!this.enabled) {
39
+ next();
40
+ return;
41
+ }
42
+ // Step 1 — circuit breaker gate.
43
+ if (await this.isCircuitBreakerOpen()) {
44
+ next();
45
+ return;
46
+ }
47
+ // Step 2 — load instructions from Redis.
48
+ let instructions;
49
+ try {
50
+ instructions = await this.connection.getActiveInstructions();
51
+ }
52
+ catch (err) {
53
+ await this.tripCircuitBreaker(err instanceof Error ? err : new Error(String(err)));
54
+ next();
55
+ return;
56
+ }
57
+ if (instructions.isEmpty()) {
58
+ next();
59
+ return;
60
+ }
61
+ // Step 3 — route-block check (before running the application handler).
62
+ let blocked = false;
63
+ try {
64
+ blocked = !(await this.interceptRequest(req, res, instructions.matchingRouteBlock(req) !== null
65
+ ? instructions.matchingRouteBlock(req)
66
+ : null));
67
+ }
68
+ catch (err) {
69
+ await this.tripCircuitBreaker(err instanceof Error ? err : new Error(String(err)));
70
+ next();
71
+ return;
72
+ }
73
+ if (blocked)
74
+ return; // Response already sent by interceptRequest.
75
+ // Step 4 — intercept res.json() before calling next().
76
+ // We override the method here so that when the downstream route handler
77
+ // calls res.json(body), our version executes instead.
78
+ const originalJson = res.json.bind(res);
79
+ const maskRules = instructions.maskRules();
80
+ const headers = instructions.injectHeaders();
81
+ const hasMasks = instructions.hasMaskRules();
82
+ res.json = (body) => {
83
+ try {
84
+ // Step 5a — inject headers.
85
+ for (const [name, value] of Object.entries(headers)) {
86
+ res.setHeader(name, value);
87
+ }
88
+ // Step 5b — deep-clone, mask, swap.
89
+ if (hasMasks && body !== undefined) {
90
+ const masked = this.applyMaskingRules(body, maskRules);
91
+ return originalJson(masked);
92
+ }
93
+ }
94
+ catch (err) {
95
+ // Trip breaker async; do not await here (we are inside a sync method).
96
+ void this.tripCircuitBreaker(err instanceof Error ? err : new Error(String(err)));
97
+ // Return original body — never a partial mask.
98
+ return originalJson(body);
99
+ }
100
+ return originalJson(body);
101
+ };
102
+ // Step 6 — run the application handler.
103
+ next();
104
+ };
105
+ }
106
+ async interceptRequest(req, res, block) {
107
+ // When called from the handler with a pre-resolved block:
108
+ if (block !== undefined) {
109
+ const routeBlock = block;
110
+ if (routeBlock !== null) {
111
+ res.status(routeBlock.responseStatus).json(routeBlock.responseBody);
112
+ return false; // blocked
113
+ }
114
+ return true; // pass through
115
+ }
116
+ // When called directly (interface compliance):
117
+ try {
118
+ const instructions = await this.connection.getActiveInstructions();
119
+ const matched = instructions.matchingRouteBlock(req);
120
+ if (matched !== null) {
121
+ res.status(matched.responseStatus).json(matched.responseBody);
122
+ return false;
123
+ }
124
+ return true;
125
+ }
126
+ catch {
127
+ return true; // fail-safe pass-through
128
+ }
129
+ }
130
+ applyMaskingRules(data, maskRules) {
131
+ return this.masker.apply(data, maskRules);
132
+ }
133
+ async isCircuitBreakerOpen() {
134
+ return this.breaker.isOpen();
135
+ }
136
+ async tripCircuitBreaker(reason) {
137
+ await this.breaker.trip(reason);
138
+ }
139
+ async resetCircuitBreaker() {
140
+ await this.breaker.reset();
141
+ }
142
+ }
143
+ exports.RemediationInterceptor = RemediationInterceptor;
144
+ //# sourceMappingURL=remediationInterceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remediationInterceptor.js","sourceRoot":"","sources":["../../src/middleware/remediationInterceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Redis } from 'ioredis';
2
+ import type { AgentConnectionInterface } from '../contracts/agentConnection.js';
3
+ import type { ConnectionConfig, WireEnvelope } from '../types/wireProtocol.js';
4
+ import { InstructionCollection, RuntimeInstruction } from '../types/instructions.js';
5
+ import { InstructionCacheService } from './InstructionCacheService.js';
6
+ /**
7
+ * Implements AgentConnectionInterface for Node.js.
8
+ * Exact behavioural equivalent of the PHP AgentConnectionService.
9
+ *
10
+ * Key differences vs PHP:
11
+ * - All methods are async (ioredis returns Promises).
12
+ * - HMAC: crypto.createHmac + crypto.timingSafeEqual (replaces hash_hmac + hash_equals).
13
+ * - Nonce dedup: ioredis SET NX EX (same semantics as PHP Redis::set NX).
14
+ * - base64url: Buffer.toString('base64url') — available in Node ≥ 16.
15
+ */
16
+ export declare class AgentConnectionService implements AgentConnectionInterface {
17
+ private readonly redis;
18
+ private readonly cache;
19
+ private readonly config;
20
+ constructor(redis: Redis, cache: InstructionCacheService, config: ConnectionConfig);
21
+ verifyEnvelope(raw: unknown): Promise<WireEnvelope>;
22
+ cacheInstruction(instruction: RuntimeInstruction): Promise<void>;
23
+ getActiveInstructions(): Promise<InstructionCollection>;
24
+ acknowledgeInstruction(instructionId: string): Promise<void>;
25
+ pollPendingInstructions(): Promise<InstructionCollection>;
26
+ private assertProtocolVersion;
27
+ private assertIssuedAtWindow;
28
+ private assertClientId;
29
+ private assertNonceUnique;
30
+ /**
31
+ * Recompute HMAC-SHA256 over the canonical string and timing-safe compare.
32
+ * Equivalent to PHP hash_hmac('sha256', ...) + hash_equals().
33
+ */
34
+ private assertHmac;
35
+ private authHeaders;
36
+ }
37
+ //# sourceMappingURL=AgentConnectionService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentConnectionService.d.ts","sourceRoot":"","sources":["../../src/services/AgentConnectionService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAiC,MAAM,0BAA0B,CAAC;AAC9G,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AASvE;;;;;;;;;GASG;AACH,qBAAa,sBAAuB,YAAW,wBAAwB;IAEnE,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAFN,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,uBAAuB,EAC9B,MAAM,EAAE,gBAAgB;IAOrC,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;IAgBnD,gBAAgB,CAAC,WAAW,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE,qBAAqB,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAIvD,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe5D,uBAAuB,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAsC/D,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,cAAc;YAMR,iBAAiB;IAoB/B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAiClB,OAAO,CAAC,WAAW;CAMpB"}
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AgentConnectionService = void 0;
7
+ const crypto_1 = require("crypto");
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const instructions_js_1 = require("../types/instructions.js");
10
+ const SignatureError_js_1 = require("../exceptions/SignatureError.js");
11
+ const ReplayError_js_1 = require("../exceptions/ReplayError.js");
12
+ const logger_js_1 = require("../logger.js");
13
+ const PROTOCOL_MAJOR = 1;
14
+ const NONCE_TTL = 300;
15
+ const ISSUED_AT_TOLERANCE = 300;
16
+ /**
17
+ * Implements AgentConnectionInterface for Node.js.
18
+ * Exact behavioural equivalent of the PHP AgentConnectionService.
19
+ *
20
+ * Key differences vs PHP:
21
+ * - All methods are async (ioredis returns Promises).
22
+ * - HMAC: crypto.createHmac + crypto.timingSafeEqual (replaces hash_hmac + hash_equals).
23
+ * - Nonce dedup: ioredis SET NX EX (same semantics as PHP Redis::set NX).
24
+ * - base64url: Buffer.toString('base64url') — available in Node ≥ 16.
25
+ */
26
+ class AgentConnectionService {
27
+ redis;
28
+ cache;
29
+ config;
30
+ constructor(redis, cache, config) {
31
+ this.redis = redis;
32
+ this.cache = cache;
33
+ this.config = config;
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // AgentConnectionInterface
37
+ // ---------------------------------------------------------------------------
38
+ async verifyEnvelope(raw) {
39
+ if (typeof raw !== 'object' || raw === null) {
40
+ throw new SignatureError_js_1.SignatureError('Envelope must be a non-null object.');
41
+ }
42
+ const envelope = raw;
43
+ this.assertProtocolVersion(envelope);
44
+ this.assertIssuedAtWindow(envelope);
45
+ this.assertClientId(envelope);
46
+ await this.assertNonceUnique(envelope);
47
+ this.assertHmac(envelope);
48
+ return raw;
49
+ }
50
+ async cacheInstruction(instruction) {
51
+ await this.cache.store(instruction);
52
+ }
53
+ async getActiveInstructions() {
54
+ return this.cache.getActive();
55
+ }
56
+ async acknowledgeInstruction(instructionId) {
57
+ try {
58
+ await axios_1.default.post(`${this.config.saas_url}/api/remediation/v1/instructions/${instructionId}/acknowledge`, {}, { headers: this.authHeaders(), timeout: 5000 });
59
+ }
60
+ catch (err) {
61
+ logger_js_1.logger.warn('RemediationEngine: acknowledge failed.', {
62
+ instructionId,
63
+ error: err instanceof Error ? err.message : String(err),
64
+ });
65
+ }
66
+ }
67
+ async pollPendingInstructions() {
68
+ try {
69
+ const response = await axios_1.default.get(`${this.config.saas_url}${this.config.poll_url}`, { headers: this.authHeaders(), timeout: 10000 });
70
+ const instructions = [];
71
+ for (const raw of response.data.instructions ?? []) {
72
+ try {
73
+ const envelope = await this.verifyEnvelope(raw);
74
+ const instruction = instructions_js_1.RuntimeInstruction.fromWirePayload(envelope.payload);
75
+ await this.cacheInstruction(instruction);
76
+ await this.acknowledgeInstruction(instruction.instructionId);
77
+ instructions.push(instruction);
78
+ }
79
+ catch (err) {
80
+ logger_js_1.logger.error('RemediationEngine: poll envelope verification failed.', {
81
+ error: err instanceof Error ? err.message : String(err),
82
+ });
83
+ }
84
+ }
85
+ return new instructions_js_1.InstructionCollection(instructions);
86
+ }
87
+ catch (err) {
88
+ logger_js_1.logger.error('RemediationEngine: poll request failed.', {
89
+ error: err instanceof Error ? err.message : String(err),
90
+ });
91
+ return instructions_js_1.InstructionCollection.empty();
92
+ }
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Verification helpers
96
+ // ---------------------------------------------------------------------------
97
+ assertProtocolVersion(envelope) {
98
+ const version = String(envelope['protocol_version'] ?? '');
99
+ const major = parseInt(version.split('.')[0] ?? '0', 10);
100
+ if (major !== PROTOCOL_MAJOR) {
101
+ throw new SignatureError_js_1.SignatureError(`Unsupported protocol major version: ${version}`);
102
+ }
103
+ }
104
+ assertIssuedAtWindow(envelope) {
105
+ const issuedAt = Number(envelope['issued_at'] ?? 0);
106
+ const delta = Math.abs(Math.floor(Date.now() / 1000) - issuedAt);
107
+ if (delta > ISSUED_AT_TOLERANCE) {
108
+ throw new SignatureError_js_1.SignatureError(`Envelope issued_at out of tolerance window (${delta}s).`);
109
+ }
110
+ }
111
+ assertClientId(envelope) {
112
+ if (envelope['client_id'] !== this.config.client_id) {
113
+ throw new SignatureError_js_1.SignatureError('client_id mismatch.');
114
+ }
115
+ }
116
+ async assertNonceUnique(envelope) {
117
+ const nonce = String(envelope['nonce'] ?? '');
118
+ const clientId = String(envelope['client_id'] ?? '');
119
+ const nonceKey = `remediation:nonce:${clientId}:${nonce}`;
120
+ try {
121
+ // SET NX EX — atomic, identical semantics to PHP Redis::set NX
122
+ const result = await this.redis.set(nonceKey, '1', 'EX', NONCE_TTL, 'NX');
123
+ if (result === null) {
124
+ throw new ReplayError_js_1.ReplayError(`Replayed nonce detected: ${nonce}`);
125
+ }
126
+ }
127
+ catch (err) {
128
+ if (err instanceof ReplayError_js_1.ReplayError)
129
+ throw err;
130
+ // Redis unavailable — skip dedup (circuit breaker handles safety).
131
+ logger_js_1.logger.warn('RemediationEngine: nonce Redis unavailable, skipping dedup.', {
132
+ error: err instanceof Error ? err.message : String(err),
133
+ });
134
+ }
135
+ }
136
+ /**
137
+ * Recompute HMAC-SHA256 over the canonical string and timing-safe compare.
138
+ * Equivalent to PHP hash_hmac('sha256', ...) + hash_equals().
139
+ */
140
+ assertHmac(envelope) {
141
+ const payload = (envelope['payload'] ?? {});
142
+ const canonical = [
143
+ String(envelope['protocol_version'] ?? ''),
144
+ String(envelope['message_id'] ?? ''),
145
+ String(envelope['message_type'] ?? ''),
146
+ String(envelope['client_id'] ?? ''),
147
+ String(envelope['issued_at'] ?? ''),
148
+ String(envelope['nonce'] ?? ''),
149
+ Buffer.from(JSON.stringify(sortKeysDeep(payload))).toString('base64url'),
150
+ ].join('.');
151
+ const expected = (0, crypto_1.createHmac)('sha256', this.config.token)
152
+ .update(canonical)
153
+ .digest('hex');
154
+ const received = String(envelope['hmac_sha256'] ?? '').toLowerCase();
155
+ if (expected.length !== received.length) {
156
+ throw new SignatureError_js_1.SignatureError('HMAC verification failed.');
157
+ }
158
+ // crypto.timingSafeEqual requires same-length Buffers.
159
+ const expectedBuf = Buffer.from(expected);
160
+ const receivedBuf = Buffer.from(received);
161
+ if (!(0, crypto_1.timingSafeEqual)(expectedBuf, receivedBuf)) {
162
+ throw new SignatureError_js_1.SignatureError('HMAC verification failed.');
163
+ }
164
+ }
165
+ // ---------------------------------------------------------------------------
166
+ authHeaders() {
167
+ return {
168
+ 'X-Remediation-Client-Id': this.config.client_id,
169
+ 'X-Remediation-Token': this.config.token,
170
+ };
171
+ }
172
+ }
173
+ exports.AgentConnectionService = AgentConnectionService;
174
+ // ---------------------------------------------------------------------------
175
+ // Utilities
176
+ // ---------------------------------------------------------------------------
177
+ function sortKeysDeep(obj) {
178
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj))
179
+ return obj;
180
+ const sorted = {};
181
+ for (const key of Object.keys(obj).sort()) {
182
+ sorted[key] = sortKeysDeep(obj[key]);
183
+ }
184
+ return sorted;
185
+ }
186
+ //# sourceMappingURL=AgentConnectionService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentConnectionService.js","sourceRoot":"","sources":["../../src/services/AgentConnectionService.ts"],"names":[],"mappings":";;;;;;AAAA,mCAAqD;AAErD,kDAA0B;AAG1B,8DAAqF;AAErF,uEAAiE;AACjE,iEAA2D;AAC3D,4CAAsC;AAEtC,MAAM,cAAc,GAAQ,CAAC,CAAC;AAC9B,MAAM,SAAS,GAAa,GAAG,CAAC;AAChC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAa,sBAAsB;IAEd;IACA;IACA;IAHnB,YACmB,KAAY,EACZ,KAA8B,EAC9B,MAAwB;QAFxB,UAAK,GAAL,KAAK,CAAO;QACZ,UAAK,GAAL,KAAK,CAAyB;QAC9B,WAAM,GAAN,MAAM,CAAkB;IACxC,CAAC;IAEJ,8EAA8E;IAC9E,2BAA2B;IAC3B,8EAA8E;IAE9E,KAAK,CAAC,cAAc,CAAC,GAAY;QAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAC5C,MAAM,IAAI,kCAAc,CAAC,qCAAqC,CAAC,CAAC;QAClE,CAAC;QAED,MAAM,QAAQ,GAAG,GAA8B,CAAC;QAEhD,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAE1B,OAAO,GAAmB,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,WAA+B;QACpD,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,qBAAqB;QACzB,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,sBAAsB,CAAC,aAAqB;QAChD,IAAI,CAAC;YACH,MAAM,eAAK,CAAC,IAAI,CACd,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,oCAAoC,aAAa,cAAc,EACtF,EAAE,EACF,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAC/C,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kBAAM,CAAC,IAAI,CAAC,wCAAwC,EAAE;gBACpD,aAAa;gBACb,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,eAAK,CAAC,GAAG,CAC9B,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,EAChD,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAChD,CAAC;YAEF,MAAM,YAAY,GAAyB,EAAE,CAAC;YAE9C,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;gBACnD,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAM,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;oBACnD,MAAM,WAAW,GAAG,oCAAkB,CAAC,eAAe,CACpD,QAAQ,CAAC,OAAwC,CAClD,CAAC;oBACF,MAAM,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;oBACzC,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;oBAC7D,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACjC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,kBAAM,CAAC,KAAK,CAAC,uDAAuD,EAAE;wBACpE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;qBACxD,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,OAAO,IAAI,uCAAqB,CAAC,YAAY,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kBAAM,CAAC,KAAK,CAAC,yCAAyC,EAAE;gBACtD,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,OAAO,uCAAqB,CAAC,KAAK,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,uBAAuB;IACvB,8EAA8E;IAEtE,qBAAqB,CAAC,QAAiC;QAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAK,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3D,IAAI,KAAK,KAAK,cAAc,EAAE,CAAC;YAC7B,MAAM,IAAI,kCAAc,CAAC,uCAAuC,OAAO,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,QAAiC;QAC5D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,MAAM,KAAK,GAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;QACpE,IAAI,KAAK,GAAG,mBAAmB,EAAE,CAAC;YAChC,MAAM,IAAI,kCAAc,CAAC,+CAA+C,KAAK,KAAK,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,QAAiC;QACtD,IAAI,QAAQ,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACpD,MAAM,IAAI,kCAAc,CAAC,qBAAqB,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,QAAiC;QAC/D,MAAM,KAAK,GAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAQ,EAAE,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAG,qBAAqB,QAAQ,IAAI,KAAK,EAAE,CAAC;QAE1D,IAAI,CAAC;YACH,+DAA+D;YAC/D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YAC1E,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,MAAM,IAAI,4BAAW,CAAC,4BAA4B,KAAK,EAAE,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,4BAAW;gBAAE,MAAM,GAAG,CAAC;YAC1C,mEAAmE;YACnE,kBAAM,CAAC,IAAI,CAAC,6DAA6D,EAAE;gBACzE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,UAAU,CAAC,QAAiC;QAClD,MAAM,OAAO,GAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,CAA4B,CAAC;QACxE,MAAM,SAAS,GAAG;YAChB,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC;YAC1C,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAU,EAAE,CAAC;YAC1C,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAQ,EAAE,CAAC;YAC1C,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAW,EAAE,CAAC;YAC1C,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAW,EAAE,CAAC;YAC1C,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAe,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;SACzE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEZ,MAAM,QAAQ,GAAG,IAAA,mBAAU,EAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;aACrD,MAAM,CAAC,SAAS,CAAC;aACjB,MAAM,CAAC,KAAK,CAAC,CAAC;QAEjB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAErE,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;YACxC,MAAM,IAAI,kCAAc,CAAC,2BAA2B,CAAC,CAAC;QACxD,CAAC;QAED,uDAAuD;QACvD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE1C,IAAI,CAAC,IAAA,wBAAe,EAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,kCAAc,CAAC,2BAA2B,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,8EAA8E;IAEtE,WAAW;QACjB,OAAO;YACL,yBAAyB,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;YAChD,qBAAqB,EAAM,IAAI,CAAC,MAAM,CAAC,KAAK;SAC7C,CAAC;IACJ,CAAC;CACF;AA7KD,wDA6KC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IAE9E,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAa,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ExtractionResult, ExtractionTarget } from '../types/extraction.js';
2
+ /**
3
+ * AST-based extractor for TypeScript/JavaScript source files.
4
+ *
5
+ * Uses the TypeScript compiler API (the same `typescript` package that's
6
+ * already present in any TS project) to locate and extract symbol source.
7
+ * Falls back to a regex-based line scanner when the TS compiler is not
8
+ * available (plain JS projects or environments without `typescript`).
9
+ */
10
+ export declare class AstExtractorService {
11
+ extract(absoluteFilePath: string, target: ExtractionTarget): ExtractionResult | null;
12
+ private extractWithTs;
13
+ private findNode;
14
+ private nodePosition;
15
+ private extractDocBlock;
16
+ private extractWithLineScan;
17
+ }
18
+ //# sourceMappingURL=AstExtractorService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AstExtractorService.d.ts","sourceRoot":"","sources":["../../src/services/AstExtractorService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAGjF;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,IAAI;IAoBpF,OAAO,CAAC,aAAa;IAkCrB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,mBAAmB;CAoC5B"}