windows-exe-decompiler-mcp-server 0.1.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 (190) hide show
  1. package/CODEX_INSTALLATION.md +69 -0
  2. package/COPILOT_INSTALLATION.md +77 -0
  3. package/LICENSE +21 -0
  4. package/README.md +314 -0
  5. package/bin/windows-exe-decompiler-mcp-server.js +3 -0
  6. package/dist/analysis-provenance.d.ts +184 -0
  7. package/dist/analysis-provenance.js +74 -0
  8. package/dist/analysis-task-runner.d.ts +31 -0
  9. package/dist/analysis-task-runner.js +160 -0
  10. package/dist/artifact-inventory.d.ts +23 -0
  11. package/dist/artifact-inventory.js +175 -0
  12. package/dist/cache-manager.d.ts +128 -0
  13. package/dist/cache-manager.js +454 -0
  14. package/dist/confidence-semantics.d.ts +66 -0
  15. package/dist/confidence-semantics.js +122 -0
  16. package/dist/config.d.ts +335 -0
  17. package/dist/config.js +193 -0
  18. package/dist/database.d.ts +227 -0
  19. package/dist/database.js +601 -0
  20. package/dist/decompiler-worker.d.ts +441 -0
  21. package/dist/decompiler-worker.js +1962 -0
  22. package/dist/dynamic-trace.d.ts +95 -0
  23. package/dist/dynamic-trace.js +629 -0
  24. package/dist/env-validator.d.ts +15 -0
  25. package/dist/env-validator.js +249 -0
  26. package/dist/error-handler.d.ts +28 -0
  27. package/dist/error-handler.example.d.ts +22 -0
  28. package/dist/error-handler.example.js +141 -0
  29. package/dist/error-handler.js +139 -0
  30. package/dist/ghidra-analysis-status.d.ts +49 -0
  31. package/dist/ghidra-analysis-status.js +178 -0
  32. package/dist/ghidra-config.d.ts +134 -0
  33. package/dist/ghidra-config.js +464 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.js +200 -0
  36. package/dist/job-queue.d.ts +169 -0
  37. package/dist/job-queue.js +407 -0
  38. package/dist/logger.d.ts +106 -0
  39. package/dist/logger.js +176 -0
  40. package/dist/policy-guard.d.ts +115 -0
  41. package/dist/policy-guard.js +243 -0
  42. package/dist/process-output.d.ts +15 -0
  43. package/dist/process-output.js +90 -0
  44. package/dist/prompts/function-explanation-review.d.ts +5 -0
  45. package/dist/prompts/function-explanation-review.js +64 -0
  46. package/dist/prompts/semantic-name-review.d.ts +5 -0
  47. package/dist/prompts/semantic-name-review.js +63 -0
  48. package/dist/runtime-correlation.d.ts +34 -0
  49. package/dist/runtime-correlation.js +279 -0
  50. package/dist/runtime-paths.d.ts +3 -0
  51. package/dist/runtime-paths.js +11 -0
  52. package/dist/selection-diff.d.ts +667 -0
  53. package/dist/selection-diff.js +53 -0
  54. package/dist/semantic-name-suggestion-artifacts.d.ts +116 -0
  55. package/dist/semantic-name-suggestion-artifacts.js +314 -0
  56. package/dist/server.d.ts +129 -0
  57. package/dist/server.js +578 -0
  58. package/dist/tools/artifact-read.d.ts +235 -0
  59. package/dist/tools/artifact-read.js +317 -0
  60. package/dist/tools/artifacts-diff.d.ts +728 -0
  61. package/dist/tools/artifacts-diff.js +304 -0
  62. package/dist/tools/artifacts-list.d.ts +515 -0
  63. package/dist/tools/artifacts-list.js +389 -0
  64. package/dist/tools/attack-map.d.ts +290 -0
  65. package/dist/tools/attack-map.js +519 -0
  66. package/dist/tools/cache-observability.d.ts +4 -0
  67. package/dist/tools/cache-observability.js +36 -0
  68. package/dist/tools/code-function-cfg.d.ts +50 -0
  69. package/dist/tools/code-function-cfg.js +102 -0
  70. package/dist/tools/code-function-decompile.d.ts +55 -0
  71. package/dist/tools/code-function-decompile.js +103 -0
  72. package/dist/tools/code-function-disassemble.d.ts +43 -0
  73. package/dist/tools/code-function-disassemble.js +185 -0
  74. package/dist/tools/code-function-explain-apply.d.ts +255 -0
  75. package/dist/tools/code-function-explain-apply.js +225 -0
  76. package/dist/tools/code-function-explain-prepare.d.ts +535 -0
  77. package/dist/tools/code-function-explain-prepare.js +276 -0
  78. package/dist/tools/code-function-explain-review.d.ts +397 -0
  79. package/dist/tools/code-function-explain-review.js +589 -0
  80. package/dist/tools/code-function-rename-apply.d.ts +248 -0
  81. package/dist/tools/code-function-rename-apply.js +220 -0
  82. package/dist/tools/code-function-rename-prepare.d.ts +506 -0
  83. package/dist/tools/code-function-rename-prepare.js +279 -0
  84. package/dist/tools/code-function-rename-review.d.ts +574 -0
  85. package/dist/tools/code-function-rename-review.js +761 -0
  86. package/dist/tools/code-functions-list.d.ts +37 -0
  87. package/dist/tools/code-functions-list.js +91 -0
  88. package/dist/tools/code-functions-rank.d.ts +34 -0
  89. package/dist/tools/code-functions-rank.js +90 -0
  90. package/dist/tools/code-functions-reconstruct.d.ts +2725 -0
  91. package/dist/tools/code-functions-reconstruct.js +2807 -0
  92. package/dist/tools/code-functions-search.d.ts +39 -0
  93. package/dist/tools/code-functions-search.js +90 -0
  94. package/dist/tools/code-reconstruct-export.d.ts +1212 -0
  95. package/dist/tools/code-reconstruct-export.js +4002 -0
  96. package/dist/tools/code-reconstruct-plan.d.ts +274 -0
  97. package/dist/tools/code-reconstruct-plan.js +342 -0
  98. package/dist/tools/dotnet-metadata-extract.d.ts +541 -0
  99. package/dist/tools/dotnet-metadata-extract.js +355 -0
  100. package/dist/tools/dotnet-reconstruct-export.d.ts +567 -0
  101. package/dist/tools/dotnet-reconstruct-export.js +1151 -0
  102. package/dist/tools/dotnet-types-list.d.ts +325 -0
  103. package/dist/tools/dotnet-types-list.js +201 -0
  104. package/dist/tools/dynamic-dependencies.d.ts +115 -0
  105. package/dist/tools/dynamic-dependencies.js +213 -0
  106. package/dist/tools/dynamic-memory-import.d.ts +10 -0
  107. package/dist/tools/dynamic-memory-import.js +567 -0
  108. package/dist/tools/dynamic-trace-import.d.ts +10 -0
  109. package/dist/tools/dynamic-trace-import.js +235 -0
  110. package/dist/tools/entrypoint-fallback-disasm.d.ts +30 -0
  111. package/dist/tools/entrypoint-fallback-disasm.js +89 -0
  112. package/dist/tools/ghidra-analyze.d.ts +88 -0
  113. package/dist/tools/ghidra-analyze.js +208 -0
  114. package/dist/tools/ghidra-health.d.ts +37 -0
  115. package/dist/tools/ghidra-health.js +212 -0
  116. package/dist/tools/ioc-export.d.ts +209 -0
  117. package/dist/tools/ioc-export.js +542 -0
  118. package/dist/tools/packer-detect.d.ts +165 -0
  119. package/dist/tools/packer-detect.js +284 -0
  120. package/dist/tools/pe-exports-extract.d.ts +175 -0
  121. package/dist/tools/pe-exports-extract.js +253 -0
  122. package/dist/tools/pe-fingerprint.d.ts +234 -0
  123. package/dist/tools/pe-fingerprint.js +269 -0
  124. package/dist/tools/pe-imports-extract.d.ts +105 -0
  125. package/dist/tools/pe-imports-extract.js +245 -0
  126. package/dist/tools/report-generate.d.ts +157 -0
  127. package/dist/tools/report-generate.js +457 -0
  128. package/dist/tools/report-summarize.d.ts +2131 -0
  129. package/dist/tools/report-summarize.js +596 -0
  130. package/dist/tools/runtime-detect.d.ts +135 -0
  131. package/dist/tools/runtime-detect.js +247 -0
  132. package/dist/tools/sample-ingest.d.ts +94 -0
  133. package/dist/tools/sample-ingest.js +327 -0
  134. package/dist/tools/sample-profile-get.d.ts +183 -0
  135. package/dist/tools/sample-profile-get.js +121 -0
  136. package/dist/tools/sandbox-execute.d.ts +441 -0
  137. package/dist/tools/sandbox-execute.js +392 -0
  138. package/dist/tools/strings-extract.d.ts +375 -0
  139. package/dist/tools/strings-extract.js +314 -0
  140. package/dist/tools/strings-floss-decode.d.ts +143 -0
  141. package/dist/tools/strings-floss-decode.js +259 -0
  142. package/dist/tools/system-health.d.ts +434 -0
  143. package/dist/tools/system-health.js +446 -0
  144. package/dist/tools/task-cancel.d.ts +21 -0
  145. package/dist/tools/task-cancel.js +70 -0
  146. package/dist/tools/task-status.d.ts +27 -0
  147. package/dist/tools/task-status.js +106 -0
  148. package/dist/tools/task-sweep.d.ts +22 -0
  149. package/dist/tools/task-sweep.js +77 -0
  150. package/dist/tools/tool-help.d.ts +340 -0
  151. package/dist/tools/tool-help.js +261 -0
  152. package/dist/tools/yara-scan.d.ts +554 -0
  153. package/dist/tools/yara-scan.js +313 -0
  154. package/dist/types.d.ts +266 -0
  155. package/dist/types.js +41 -0
  156. package/dist/worker-pool.d.ts +204 -0
  157. package/dist/worker-pool.js +650 -0
  158. package/dist/workflows/deep-static.d.ts +104 -0
  159. package/dist/workflows/deep-static.js +276 -0
  160. package/dist/workflows/function-explanation-review.d.ts +655 -0
  161. package/dist/workflows/function-explanation-review.js +440 -0
  162. package/dist/workflows/reconstruct.d.ts +2053 -0
  163. package/dist/workflows/reconstruct.js +666 -0
  164. package/dist/workflows/semantic-name-review.d.ts +2418 -0
  165. package/dist/workflows/semantic-name-review.js +521 -0
  166. package/dist/workflows/triage.d.ts +659 -0
  167. package/dist/workflows/triage.js +1374 -0
  168. package/dist/workspace-manager.d.ts +150 -0
  169. package/dist/workspace-manager.js +411 -0
  170. package/ghidra_scripts/DecompileFunction.java +487 -0
  171. package/ghidra_scripts/DecompileFunction.py +150 -0
  172. package/ghidra_scripts/ExtractCFG.java +256 -0
  173. package/ghidra_scripts/ExtractCFG.py +233 -0
  174. package/ghidra_scripts/ExtractFunctions.java +442 -0
  175. package/ghidra_scripts/ExtractFunctions.py +101 -0
  176. package/ghidra_scripts/README.md +125 -0
  177. package/ghidra_scripts/SearchFunctionReferences.java +380 -0
  178. package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -0
  179. package/helpers/DotNetMetadataProbe/Program.cs +566 -0
  180. package/install-to-codex.ps1 +178 -0
  181. package/install-to-copilot.ps1 +303 -0
  182. package/package.json +101 -0
  183. package/requirements.txt +9 -0
  184. package/workers/requirements-dynamic.txt +11 -0
  185. package/workers/requirements.txt +8 -0
  186. package/workers/speakeasy_compat.py +175 -0
  187. package/workers/static_worker.py +5183 -0
  188. package/workers/yara_rules/default.yar +33 -0
  189. package/workers/yara_rules/malware_families.yar +93 -0
  190. package/workers/yara_rules/packers.yar +80 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * Job Queue - In-memory task queue with priority support
3
+ *
4
+ * Implements requirements 21.1 and 21.2:
5
+ * - Task enqueueing with unique job_id
6
+ * - Priority-based task ordering
7
+ * - Job status tracking and cancellation
8
+ */
9
+ import { randomUUID } from 'crypto';
10
+ import { EventEmitter } from 'events';
11
+ // Re-export types for convenience
12
+ export { JobPriority } from './types.js';
13
+ /**
14
+ * In-memory job queue with priority support
15
+ *
16
+ * Features:
17
+ * - Priority-based ordering
18
+ * - Job status tracking
19
+ * - Cancellation support
20
+ * - Event-based completion notifications
21
+ */
22
+ export class JobQueue extends EventEmitter {
23
+ jobs = new Map();
24
+ queue = [];
25
+ defaultRetryPolicy = {
26
+ maxRetries: 3,
27
+ backoffMs: 1000,
28
+ retryableErrors: ['E_TIMEOUT', 'E_RESOURCE_EXHAUSTED', 'E_WORKER_UNAVAILABLE']
29
+ };
30
+ /**
31
+ * Enqueue a new job
32
+ *
33
+ * @param job - Job configuration (id will be generated if not provided)
34
+ * @returns Job ID
35
+ */
36
+ enqueue(job) {
37
+ const jobId = randomUUID();
38
+ const fullJob = {
39
+ ...job,
40
+ id: jobId,
41
+ createdAt: new Date().toISOString(),
42
+ attempts: 0,
43
+ retryPolicy: job.retryPolicy || this.defaultRetryPolicy
44
+ };
45
+ const entry = {
46
+ job: fullJob,
47
+ status: 'queued'
48
+ };
49
+ this.jobs.set(jobId, entry);
50
+ this.queue.push(fullJob);
51
+ // Sort queue by priority (descending)
52
+ this.sortQueue();
53
+ this.emit('job:enqueued', jobId);
54
+ return jobId;
55
+ }
56
+ /**
57
+ * Get job status
58
+ *
59
+ * @param jobId - Job identifier
60
+ * @returns Job status or undefined if not found
61
+ */
62
+ getStatus(jobId) {
63
+ const entry = this.jobs.get(jobId);
64
+ if (!entry) {
65
+ return undefined;
66
+ }
67
+ return {
68
+ id: jobId,
69
+ status: entry.status,
70
+ progress: entry.progress,
71
+ startedAt: entry.startedAt,
72
+ finishedAt: entry.finishedAt,
73
+ error: entry.error
74
+ };
75
+ }
76
+ /**
77
+ * Cancel a job
78
+ *
79
+ * @param jobId - Job identifier
80
+ * @returns True if job was cancelled, false if not found or already completed
81
+ */
82
+ cancel(jobId, reason) {
83
+ const entry = this.jobs.get(jobId);
84
+ if (!entry) {
85
+ return false;
86
+ }
87
+ // Can only cancel queued or running jobs
88
+ if (entry.status !== 'queued' && entry.status !== 'running') {
89
+ return false;
90
+ }
91
+ // Remove from queue if still queued
92
+ if (entry.status === 'queued') {
93
+ this.queue = this.queue.filter(j => j.id !== jobId);
94
+ }
95
+ // Update status
96
+ entry.status = 'cancelled';
97
+ entry.finishedAt = new Date().toISOString();
98
+ entry.cancelReason = reason;
99
+ entry.error = reason ? `Cancelled: ${reason}` : 'Cancelled by user';
100
+ this.emit('job:cancelled', jobId, reason);
101
+ return true;
102
+ }
103
+ /**
104
+ * Register a completion callback for a job
105
+ *
106
+ * @param jobId - Job identifier
107
+ * @param callback - Callback function to invoke when job completes
108
+ */
109
+ onComplete(jobId, callback) {
110
+ const handler = (completedJobId, result) => {
111
+ if (completedJobId === jobId) {
112
+ callback(result);
113
+ this.removeListener('job:completed', handler);
114
+ this.removeListener('job:failed', handler);
115
+ }
116
+ };
117
+ this.on('job:completed', handler);
118
+ this.on('job:failed', handler);
119
+ }
120
+ /**
121
+ * Get the next job from the queue (highest priority)
122
+ *
123
+ * @returns Next job or undefined if queue is empty
124
+ */
125
+ dequeue() {
126
+ const job = this.queue.shift();
127
+ if (job) {
128
+ const entry = this.jobs.get(job.id);
129
+ if (entry) {
130
+ entry.status = 'running';
131
+ entry.startedAt = new Date().toISOString();
132
+ this.emit('job:started', job.id);
133
+ }
134
+ }
135
+ return job;
136
+ }
137
+ /**
138
+ * Mark a job as completed
139
+ *
140
+ * Requirements: 21.4, 21.5, 28.2 - Job completion with retry logic
141
+ *
142
+ * @param jobId - Job identifier
143
+ * @param result - Job execution result
144
+ */
145
+ complete(jobId, result) {
146
+ const entry = this.jobs.get(jobId);
147
+ if (!entry) {
148
+ return;
149
+ }
150
+ // Check if job should be retried on failure
151
+ if (!result.ok && this.shouldRetry(entry.job, result)) {
152
+ this.retryJob(entry.job);
153
+ return;
154
+ }
155
+ // Mark as completed or failed (no more retries)
156
+ entry.status = result.ok ? 'completed' : 'failed';
157
+ entry.finishedAt = new Date().toISOString();
158
+ entry.result = result;
159
+ if (!result.ok) {
160
+ entry.error = result.errors.join('; ');
161
+ }
162
+ const eventName = result.ok ? 'job:completed' : 'job:failed';
163
+ this.emit(eventName, jobId, result);
164
+ }
165
+ /**
166
+ * Check if a failed job should be retried
167
+ *
168
+ * Requirements: 21.5, 28.2 - Retry policy evaluation
169
+ *
170
+ * @param job - Job that failed
171
+ * @param result - Job execution result
172
+ * @returns True if job should be retried
173
+ */
174
+ shouldRetry(job, result) {
175
+ // Check if we've exceeded max retries
176
+ if (job.attempts >= job.retryPolicy.maxRetries) {
177
+ return false;
178
+ }
179
+ // Check if any error is retryable
180
+ const hasRetryableError = result.errors.some(error => job.retryPolicy.retryableErrors.some(retryableError => error.includes(retryableError)));
181
+ return hasRetryableError;
182
+ }
183
+ /**
184
+ * Retry a failed job with exponential backoff
185
+ *
186
+ * Requirements: 21.5, 28.2 - Exponential backoff retry
187
+ *
188
+ * @param job - Job to retry
189
+ */
190
+ retryJob(job) {
191
+ // Increment attempts counter
192
+ job.attempts += 1;
193
+ // Calculate exponential backoff delay
194
+ const backoffMs = this.calculateBackoff(job);
195
+ // Schedule retry after backoff delay
196
+ setTimeout(() => {
197
+ this.requeue(job);
198
+ this.emit('job:retry', job.id, job.attempts, backoffMs);
199
+ }, backoffMs);
200
+ // Emit retry scheduled event
201
+ this.emit('job:retry-scheduled', job.id, job.attempts, backoffMs);
202
+ }
203
+ /**
204
+ * Calculate exponential backoff delay
205
+ *
206
+ * Requirements: 21.5, 28.2 - Exponential backoff calculation
207
+ *
208
+ * Formula: baseBackoff * (2 ^ (attempts - 1))
209
+ *
210
+ * @param job - Job to calculate backoff for
211
+ * @returns Backoff delay in milliseconds
212
+ */
213
+ calculateBackoff(job) {
214
+ const baseBackoff = job.retryPolicy.backoffMs;
215
+ const exponentialFactor = Math.pow(2, job.attempts - 1);
216
+ return baseBackoff * exponentialFactor;
217
+ }
218
+ /**
219
+ * Update job progress
220
+ *
221
+ * @param jobId - Job identifier
222
+ * @param progress - Progress percentage (0-100)
223
+ */
224
+ updateProgress(jobId, progress) {
225
+ const entry = this.jobs.get(jobId);
226
+ if (entry && entry.status === 'running') {
227
+ entry.progress = Math.max(0, Math.min(100, progress));
228
+ this.emit('job:progress', jobId, entry.progress);
229
+ }
230
+ }
231
+ /**
232
+ * Get queue length
233
+ *
234
+ * @returns Number of jobs in queue (not including running jobs)
235
+ */
236
+ getQueueLength() {
237
+ return this.queue.length;
238
+ }
239
+ /**
240
+ * Get all jobs with a specific status
241
+ *
242
+ * @param status - Job status to filter by
243
+ * @returns Array of job statuses
244
+ */
245
+ getJobsByStatus(status) {
246
+ const results = [];
247
+ for (const [jobId, entry] of this.jobs.entries()) {
248
+ if (entry.status === status) {
249
+ results.push({
250
+ id: jobId,
251
+ status: entry.status,
252
+ progress: entry.progress,
253
+ startedAt: entry.startedAt,
254
+ finishedAt: entry.finishedAt,
255
+ error: entry.error
256
+ });
257
+ }
258
+ }
259
+ return results;
260
+ }
261
+ /**
262
+ * Get full job status list with lightweight execution context.
263
+ */
264
+ listStatuses(status) {
265
+ const rows = [];
266
+ for (const [jobId, entry] of this.jobs.entries()) {
267
+ if (status && entry.status !== status) {
268
+ continue;
269
+ }
270
+ rows.push({
271
+ id: jobId,
272
+ status: entry.status,
273
+ progress: entry.progress,
274
+ startedAt: entry.startedAt,
275
+ finishedAt: entry.finishedAt,
276
+ error: entry.error,
277
+ tool: entry.job.tool,
278
+ sampleId: entry.job.sampleId,
279
+ attempts: entry.job.attempts,
280
+ timeout: entry.job.timeout,
281
+ createdAt: entry.job.createdAt,
282
+ cancelReason: entry.cancelReason,
283
+ });
284
+ }
285
+ rows.sort((a, b) => new Date(b.createdAt).getTime() -
286
+ new Date(a.createdAt).getTime());
287
+ return rows;
288
+ }
289
+ /**
290
+ * Get job result
291
+ *
292
+ * @param jobId - Job identifier
293
+ * @returns Job result or undefined if not found or not completed
294
+ */
295
+ getResult(jobId) {
296
+ const entry = this.jobs.get(jobId);
297
+ return entry?.result;
298
+ }
299
+ /**
300
+ * Clear completed jobs older than specified age
301
+ *
302
+ * @param maxAgeMs - Maximum age in milliseconds
303
+ * @returns Number of jobs cleared
304
+ */
305
+ clearOldJobs(maxAgeMs) {
306
+ const now = Date.now();
307
+ let cleared = 0;
308
+ for (const [jobId, entry] of this.jobs.entries()) {
309
+ if (entry.status === 'completed' || entry.status === 'failed' || entry.status === 'cancelled') {
310
+ if (entry.finishedAt) {
311
+ const finishedTime = new Date(entry.finishedAt).getTime();
312
+ if (now - finishedTime > maxAgeMs) {
313
+ this.jobs.delete(jobId);
314
+ cleared++;
315
+ }
316
+ }
317
+ }
318
+ }
319
+ return cleared;
320
+ }
321
+ /**
322
+ * Mark stale running jobs as failed.
323
+ * Emits `job:reaped` with affected job ids for observability.
324
+ */
325
+ reapStaleRunningJobs(maxRuntimeMs, nowMs = Date.now()) {
326
+ const reaped = [];
327
+ for (const [jobId, entry] of this.jobs.entries()) {
328
+ if (entry.status !== 'running' || !entry.startedAt) {
329
+ continue;
330
+ }
331
+ const startedAtMs = new Date(entry.startedAt).getTime();
332
+ if (!Number.isFinite(startedAtMs)) {
333
+ continue;
334
+ }
335
+ const elapsed = nowMs - startedAtMs;
336
+ if (elapsed <= maxRuntimeMs) {
337
+ continue;
338
+ }
339
+ entry.status = 'failed';
340
+ entry.finishedAt = new Date(nowMs).toISOString();
341
+ entry.error = `E_TIMEOUT: stale running job reaped after ${elapsed}ms`;
342
+ if (!entry.result) {
343
+ entry.result = {
344
+ jobId,
345
+ ok: false,
346
+ data: undefined,
347
+ errors: [entry.error],
348
+ warnings: [],
349
+ artifacts: [],
350
+ metrics: {
351
+ elapsedMs: elapsed,
352
+ peakRssMb: 0,
353
+ },
354
+ };
355
+ }
356
+ reaped.push(jobId);
357
+ }
358
+ if (reaped.length > 0) {
359
+ this.emit('job:reaped', reaped, maxRuntimeMs);
360
+ }
361
+ return reaped;
362
+ }
363
+ /**
364
+ * Get total number of jobs tracked
365
+ *
366
+ * @returns Total job count
367
+ */
368
+ getTotalJobs() {
369
+ return this.jobs.size;
370
+ }
371
+ /**
372
+ * Re-enqueue a job for retry (used by retry mechanism)
373
+ *
374
+ * Requirements: 21.5, 28.2 - Failure retry mechanism
375
+ *
376
+ * @param job - Job to re-enqueue (with updated attempts count)
377
+ */
378
+ requeue(job) {
379
+ // Update the job entry
380
+ const entry = this.jobs.get(job.id);
381
+ if (entry) {
382
+ entry.job = job;
383
+ entry.status = 'queued';
384
+ entry.error = undefined;
385
+ entry.startedAt = undefined;
386
+ entry.finishedAt = undefined;
387
+ }
388
+ // Add back to queue
389
+ this.queue.push(job);
390
+ this.sortQueue();
391
+ this.emit('job:requeued', job.id, job.attempts);
392
+ }
393
+ /**
394
+ * Sort queue by priority (descending)
395
+ */
396
+ sortQueue() {
397
+ this.queue.sort((a, b) => {
398
+ // Higher priority first
399
+ if (a.priority !== b.priority) {
400
+ return b.priority - a.priority;
401
+ }
402
+ // If same priority, FIFO (earlier created first)
403
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
404
+ });
405
+ }
406
+ }
407
+ //# sourceMappingURL=job-queue.js.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 结构化日志模块
3
+ *
4
+ * 使用 pino 库实现结构化日志记录,支持:
5
+ * - 操作日志
6
+ * - 错误日志(包含完整堆栈)
7
+ * - 性能指标日志
8
+ * - 审计日志
9
+ *
10
+ * 验收标准:
11
+ * - 需求 30.4: 使用 pino 库记录结构化日志
12
+ * - 需求 30.5: 记录完整的错误堆栈和上下文信息
13
+ */
14
+ import pino from 'pino';
15
+ /**
16
+ * 日志级别
17
+ */
18
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
19
+ /**
20
+ * 性能指标
21
+ */
22
+ export interface PerformanceMetrics {
23
+ elapsedMs: number;
24
+ peakRssMb?: number;
25
+ cpuPercent?: number;
26
+ }
27
+ /**
28
+ * 操作上下文
29
+ */
30
+ export interface OperationContext {
31
+ operation: string;
32
+ sampleId?: string;
33
+ toolName?: string;
34
+ userId?: string;
35
+ [key: string]: unknown;
36
+ }
37
+ /**
38
+ * 审计事件
39
+ */
40
+ export interface AuditEvent {
41
+ operation: string;
42
+ user?: string;
43
+ sampleId?: string;
44
+ decision: 'allow' | 'deny' | 'partial';
45
+ reason?: string;
46
+ metadata?: Record<string, unknown>;
47
+ }
48
+ /**
49
+ * 全局 logger 实例
50
+ */
51
+ export declare const logger: pino.Logger<never, boolean>;
52
+ /**
53
+ * 创建子 logger(带上下文)
54
+ */
55
+ export declare function createChildLogger(context: Record<string, unknown>): pino.Logger<never, boolean>;
56
+ /**
57
+ * 记录操作开始
58
+ */
59
+ export declare function logOperationStart(context: OperationContext): void;
60
+ /**
61
+ * 记录操作完成
62
+ */
63
+ export declare function logOperationComplete(context: OperationContext, metrics: PerformanceMetrics): void;
64
+ /**
65
+ * 记录操作失败
66
+ */
67
+ export declare function logOperationError(context: OperationContext, error: Error, metrics?: Partial<PerformanceMetrics>): void;
68
+ /**
69
+ * 记录审计事件
70
+ */
71
+ export declare function logAudit(event: AuditEvent): void;
72
+ /**
73
+ * 记录性能指标
74
+ */
75
+ export declare function logMetrics(operation: string, metrics: PerformanceMetrics, context?: Record<string, unknown>): void;
76
+ /**
77
+ * 记录警告
78
+ */
79
+ export declare function logWarning(message: string, context?: Record<string, unknown>): void;
80
+ /**
81
+ * 记录调试信息
82
+ */
83
+ export declare function logDebug(message: string, context?: Record<string, unknown>): void;
84
+ /**
85
+ * 记录错误(通用)
86
+ */
87
+ export declare function logError(error: Error, context?: Record<string, unknown>): void;
88
+ /**
89
+ * 创建性能计时器
90
+ */
91
+ export declare function createTimer(): {
92
+ /**
93
+ * 结束计时并返回性能指标
94
+ */
95
+ end(): PerformanceMetrics;
96
+ };
97
+ /**
98
+ * 包装异步操作,自动记录日志和性能指标
99
+ */
100
+ export declare function withLogging<T>(context: OperationContext, fn: () => Promise<T>): Promise<T>;
101
+ /**
102
+ * 包装同步操作,自动记录日志和性能指标
103
+ */
104
+ export declare function withLoggingSync<T>(context: OperationContext, fn: () => T): T;
105
+ export default logger;
106
+ //# sourceMappingURL=logger.d.ts.map
package/dist/logger.js ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * 结构化日志模块
3
+ *
4
+ * 使用 pino 库实现结构化日志记录,支持:
5
+ * - 操作日志
6
+ * - 错误日志(包含完整堆栈)
7
+ * - 性能指标日志
8
+ * - 审计日志
9
+ *
10
+ * 验收标准:
11
+ * - 需求 30.4: 使用 pino 库记录结构化日志
12
+ * - 需求 30.5: 记录完整的错误堆栈和上下文信息
13
+ */
14
+ import pino from 'pino';
15
+ import { config } from './config.js';
16
+ /**
17
+ * 创建 pino logger 实例
18
+ *
19
+ * CRITICAL: All logs must go to stderr to avoid interfering with MCP protocol on stdout
20
+ */
21
+ function createLogger() {
22
+ // Create destination that writes to stderr (fd 2)
23
+ const destination = pino.destination({ dest: 2, sync: false });
24
+ return pino({
25
+ level: config.logging.level || 'info',
26
+ // 基础字段
27
+ base: {
28
+ pid: process.pid,
29
+ hostname: undefined, // 不记录 hostname 以减少日志大小
30
+ },
31
+ // 时间戳格式
32
+ timestamp: pino.stdTimeFunctions.isoTime,
33
+ // 序列化错误对象
34
+ serializers: {
35
+ err: pino.stdSerializers.err,
36
+ error: pino.stdSerializers.err,
37
+ },
38
+ }, destination);
39
+ }
40
+ /**
41
+ * 全局 logger 实例
42
+ */
43
+ export const logger = createLogger();
44
+ /**
45
+ * 创建子 logger(带上下文)
46
+ */
47
+ export function createChildLogger(context) {
48
+ return logger.child(context);
49
+ }
50
+ /**
51
+ * 记录操作开始
52
+ */
53
+ export function logOperationStart(context) {
54
+ logger.info(context, `Operation started: ${context.operation}`);
55
+ }
56
+ /**
57
+ * 记录操作完成
58
+ */
59
+ export function logOperationComplete(context, metrics) {
60
+ logger.info({ ...context, metrics }, `Operation completed: ${context.operation} (${metrics.elapsedMs}ms)`);
61
+ }
62
+ /**
63
+ * 记录操作失败
64
+ */
65
+ export function logOperationError(context, error, metrics) {
66
+ logger.error({
67
+ ...context,
68
+ err: error,
69
+ metrics,
70
+ // 确保记录完整的错误堆栈
71
+ stack: error.stack,
72
+ errorName: error.name,
73
+ errorMessage: error.message,
74
+ }, `Operation failed: ${context.operation} - ${error.message}`);
75
+ }
76
+ /**
77
+ * 记录审计事件
78
+ */
79
+ export function logAudit(event) {
80
+ logger.info({
81
+ audit: true,
82
+ ...event,
83
+ }, `Audit: ${event.operation} - ${event.decision}`);
84
+ }
85
+ /**
86
+ * 记录性能指标
87
+ */
88
+ export function logMetrics(operation, metrics, context) {
89
+ logger.info({
90
+ metrics: true,
91
+ operation,
92
+ ...metrics,
93
+ ...context,
94
+ }, `Metrics: ${operation} - ${metrics.elapsedMs}ms`);
95
+ }
96
+ /**
97
+ * 记录警告
98
+ */
99
+ export function logWarning(message, context) {
100
+ logger.warn(context, message);
101
+ }
102
+ /**
103
+ * 记录调试信息
104
+ */
105
+ export function logDebug(message, context) {
106
+ logger.debug(context, message);
107
+ }
108
+ /**
109
+ * 记录错误(通用)
110
+ */
111
+ export function logError(error, context) {
112
+ logger.error({
113
+ ...context,
114
+ err: error,
115
+ stack: error.stack,
116
+ errorName: error.name,
117
+ errorMessage: error.message,
118
+ }, error.message);
119
+ }
120
+ /**
121
+ * 创建性能计时器
122
+ */
123
+ export function createTimer() {
124
+ const startTime = Date.now();
125
+ return {
126
+ /**
127
+ * 结束计时并返回性能指标
128
+ */
129
+ end() {
130
+ const endTime = Date.now();
131
+ const endMemory = process.memoryUsage();
132
+ return {
133
+ elapsedMs: endTime - startTime,
134
+ peakRssMb: Math.round(endMemory.rss / 1024 / 1024),
135
+ };
136
+ },
137
+ };
138
+ }
139
+ /**
140
+ * 包装异步操作,自动记录日志和性能指标
141
+ */
142
+ export async function withLogging(context, fn) {
143
+ const timer = createTimer();
144
+ logOperationStart(context);
145
+ try {
146
+ const result = await fn();
147
+ const metrics = timer.end();
148
+ logOperationComplete(context, metrics);
149
+ return result;
150
+ }
151
+ catch (error) {
152
+ const metrics = timer.end();
153
+ logOperationError(context, error, metrics);
154
+ throw error;
155
+ }
156
+ }
157
+ /**
158
+ * 包装同步操作,自动记录日志和性能指标
159
+ */
160
+ export function withLoggingSync(context, fn) {
161
+ const timer = createTimer();
162
+ logOperationStart(context);
163
+ try {
164
+ const result = fn();
165
+ const metrics = timer.end();
166
+ logOperationComplete(context, metrics);
167
+ return result;
168
+ }
169
+ catch (error) {
170
+ const metrics = timer.end();
171
+ logOperationError(context, error, metrics);
172
+ throw error;
173
+ }
174
+ }
175
+ export default logger;
176
+ //# sourceMappingURL=logger.js.map