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.
- package/CODEX_INSTALLATION.md +69 -0
- package/COPILOT_INSTALLATION.md +77 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/bin/windows-exe-decompiler-mcp-server.js +3 -0
- package/dist/analysis-provenance.d.ts +184 -0
- package/dist/analysis-provenance.js +74 -0
- package/dist/analysis-task-runner.d.ts +31 -0
- package/dist/analysis-task-runner.js +160 -0
- package/dist/artifact-inventory.d.ts +23 -0
- package/dist/artifact-inventory.js +175 -0
- package/dist/cache-manager.d.ts +128 -0
- package/dist/cache-manager.js +454 -0
- package/dist/confidence-semantics.d.ts +66 -0
- package/dist/confidence-semantics.js +122 -0
- package/dist/config.d.ts +335 -0
- package/dist/config.js +193 -0
- package/dist/database.d.ts +227 -0
- package/dist/database.js +601 -0
- package/dist/decompiler-worker.d.ts +441 -0
- package/dist/decompiler-worker.js +1962 -0
- package/dist/dynamic-trace.d.ts +95 -0
- package/dist/dynamic-trace.js +629 -0
- package/dist/env-validator.d.ts +15 -0
- package/dist/env-validator.js +249 -0
- package/dist/error-handler.d.ts +28 -0
- package/dist/error-handler.example.d.ts +22 -0
- package/dist/error-handler.example.js +141 -0
- package/dist/error-handler.js +139 -0
- package/dist/ghidra-analysis-status.d.ts +49 -0
- package/dist/ghidra-analysis-status.js +178 -0
- package/dist/ghidra-config.d.ts +134 -0
- package/dist/ghidra-config.js +464 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +200 -0
- package/dist/job-queue.d.ts +169 -0
- package/dist/job-queue.js +407 -0
- package/dist/logger.d.ts +106 -0
- package/dist/logger.js +176 -0
- package/dist/policy-guard.d.ts +115 -0
- package/dist/policy-guard.js +243 -0
- package/dist/process-output.d.ts +15 -0
- package/dist/process-output.js +90 -0
- package/dist/prompts/function-explanation-review.d.ts +5 -0
- package/dist/prompts/function-explanation-review.js +64 -0
- package/dist/prompts/semantic-name-review.d.ts +5 -0
- package/dist/prompts/semantic-name-review.js +63 -0
- package/dist/runtime-correlation.d.ts +34 -0
- package/dist/runtime-correlation.js +279 -0
- package/dist/runtime-paths.d.ts +3 -0
- package/dist/runtime-paths.js +11 -0
- package/dist/selection-diff.d.ts +667 -0
- package/dist/selection-diff.js +53 -0
- package/dist/semantic-name-suggestion-artifacts.d.ts +116 -0
- package/dist/semantic-name-suggestion-artifacts.js +314 -0
- package/dist/server.d.ts +129 -0
- package/dist/server.js +578 -0
- package/dist/tools/artifact-read.d.ts +235 -0
- package/dist/tools/artifact-read.js +317 -0
- package/dist/tools/artifacts-diff.d.ts +728 -0
- package/dist/tools/artifacts-diff.js +304 -0
- package/dist/tools/artifacts-list.d.ts +515 -0
- package/dist/tools/artifacts-list.js +389 -0
- package/dist/tools/attack-map.d.ts +290 -0
- package/dist/tools/attack-map.js +519 -0
- package/dist/tools/cache-observability.d.ts +4 -0
- package/dist/tools/cache-observability.js +36 -0
- package/dist/tools/code-function-cfg.d.ts +50 -0
- package/dist/tools/code-function-cfg.js +102 -0
- package/dist/tools/code-function-decompile.d.ts +55 -0
- package/dist/tools/code-function-decompile.js +103 -0
- package/dist/tools/code-function-disassemble.d.ts +43 -0
- package/dist/tools/code-function-disassemble.js +185 -0
- package/dist/tools/code-function-explain-apply.d.ts +255 -0
- package/dist/tools/code-function-explain-apply.js +225 -0
- package/dist/tools/code-function-explain-prepare.d.ts +535 -0
- package/dist/tools/code-function-explain-prepare.js +276 -0
- package/dist/tools/code-function-explain-review.d.ts +397 -0
- package/dist/tools/code-function-explain-review.js +589 -0
- package/dist/tools/code-function-rename-apply.d.ts +248 -0
- package/dist/tools/code-function-rename-apply.js +220 -0
- package/dist/tools/code-function-rename-prepare.d.ts +506 -0
- package/dist/tools/code-function-rename-prepare.js +279 -0
- package/dist/tools/code-function-rename-review.d.ts +574 -0
- package/dist/tools/code-function-rename-review.js +761 -0
- package/dist/tools/code-functions-list.d.ts +37 -0
- package/dist/tools/code-functions-list.js +91 -0
- package/dist/tools/code-functions-rank.d.ts +34 -0
- package/dist/tools/code-functions-rank.js +90 -0
- package/dist/tools/code-functions-reconstruct.d.ts +2725 -0
- package/dist/tools/code-functions-reconstruct.js +2807 -0
- package/dist/tools/code-functions-search.d.ts +39 -0
- package/dist/tools/code-functions-search.js +90 -0
- package/dist/tools/code-reconstruct-export.d.ts +1212 -0
- package/dist/tools/code-reconstruct-export.js +4002 -0
- package/dist/tools/code-reconstruct-plan.d.ts +274 -0
- package/dist/tools/code-reconstruct-plan.js +342 -0
- package/dist/tools/dotnet-metadata-extract.d.ts +541 -0
- package/dist/tools/dotnet-metadata-extract.js +355 -0
- package/dist/tools/dotnet-reconstruct-export.d.ts +567 -0
- package/dist/tools/dotnet-reconstruct-export.js +1151 -0
- package/dist/tools/dotnet-types-list.d.ts +325 -0
- package/dist/tools/dotnet-types-list.js +201 -0
- package/dist/tools/dynamic-dependencies.d.ts +115 -0
- package/dist/tools/dynamic-dependencies.js +213 -0
- package/dist/tools/dynamic-memory-import.d.ts +10 -0
- package/dist/tools/dynamic-memory-import.js +567 -0
- package/dist/tools/dynamic-trace-import.d.ts +10 -0
- package/dist/tools/dynamic-trace-import.js +235 -0
- package/dist/tools/entrypoint-fallback-disasm.d.ts +30 -0
- package/dist/tools/entrypoint-fallback-disasm.js +89 -0
- package/dist/tools/ghidra-analyze.d.ts +88 -0
- package/dist/tools/ghidra-analyze.js +208 -0
- package/dist/tools/ghidra-health.d.ts +37 -0
- package/dist/tools/ghidra-health.js +212 -0
- package/dist/tools/ioc-export.d.ts +209 -0
- package/dist/tools/ioc-export.js +542 -0
- package/dist/tools/packer-detect.d.ts +165 -0
- package/dist/tools/packer-detect.js +284 -0
- package/dist/tools/pe-exports-extract.d.ts +175 -0
- package/dist/tools/pe-exports-extract.js +253 -0
- package/dist/tools/pe-fingerprint.d.ts +234 -0
- package/dist/tools/pe-fingerprint.js +269 -0
- package/dist/tools/pe-imports-extract.d.ts +105 -0
- package/dist/tools/pe-imports-extract.js +245 -0
- package/dist/tools/report-generate.d.ts +157 -0
- package/dist/tools/report-generate.js +457 -0
- package/dist/tools/report-summarize.d.ts +2131 -0
- package/dist/tools/report-summarize.js +596 -0
- package/dist/tools/runtime-detect.d.ts +135 -0
- package/dist/tools/runtime-detect.js +247 -0
- package/dist/tools/sample-ingest.d.ts +94 -0
- package/dist/tools/sample-ingest.js +327 -0
- package/dist/tools/sample-profile-get.d.ts +183 -0
- package/dist/tools/sample-profile-get.js +121 -0
- package/dist/tools/sandbox-execute.d.ts +441 -0
- package/dist/tools/sandbox-execute.js +392 -0
- package/dist/tools/strings-extract.d.ts +375 -0
- package/dist/tools/strings-extract.js +314 -0
- package/dist/tools/strings-floss-decode.d.ts +143 -0
- package/dist/tools/strings-floss-decode.js +259 -0
- package/dist/tools/system-health.d.ts +434 -0
- package/dist/tools/system-health.js +446 -0
- package/dist/tools/task-cancel.d.ts +21 -0
- package/dist/tools/task-cancel.js +70 -0
- package/dist/tools/task-status.d.ts +27 -0
- package/dist/tools/task-status.js +106 -0
- package/dist/tools/task-sweep.d.ts +22 -0
- package/dist/tools/task-sweep.js +77 -0
- package/dist/tools/tool-help.d.ts +340 -0
- package/dist/tools/tool-help.js +261 -0
- package/dist/tools/yara-scan.d.ts +554 -0
- package/dist/tools/yara-scan.js +313 -0
- package/dist/types.d.ts +266 -0
- package/dist/types.js +41 -0
- package/dist/worker-pool.d.ts +204 -0
- package/dist/worker-pool.js +650 -0
- package/dist/workflows/deep-static.d.ts +104 -0
- package/dist/workflows/deep-static.js +276 -0
- package/dist/workflows/function-explanation-review.d.ts +655 -0
- package/dist/workflows/function-explanation-review.js +440 -0
- package/dist/workflows/reconstruct.d.ts +2053 -0
- package/dist/workflows/reconstruct.js +666 -0
- package/dist/workflows/semantic-name-review.d.ts +2418 -0
- package/dist/workflows/semantic-name-review.js +521 -0
- package/dist/workflows/triage.d.ts +659 -0
- package/dist/workflows/triage.js +1374 -0
- package/dist/workspace-manager.d.ts +150 -0
- package/dist/workspace-manager.js +411 -0
- package/ghidra_scripts/DecompileFunction.java +487 -0
- package/ghidra_scripts/DecompileFunction.py +150 -0
- package/ghidra_scripts/ExtractCFG.java +256 -0
- package/ghidra_scripts/ExtractCFG.py +233 -0
- package/ghidra_scripts/ExtractFunctions.java +442 -0
- package/ghidra_scripts/ExtractFunctions.py +101 -0
- package/ghidra_scripts/README.md +125 -0
- package/ghidra_scripts/SearchFunctionReferences.java +380 -0
- package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -0
- package/helpers/DotNetMetadataProbe/Program.cs +566 -0
- package/install-to-codex.ps1 +178 -0
- package/install-to-copilot.ps1 +303 -0
- package/package.json +101 -0
- package/requirements.txt +9 -0
- package/workers/requirements-dynamic.txt +11 -0
- package/workers/requirements.txt +8 -0
- package/workers/speakeasy_compat.py +175 -0
- package/workers/static_worker.py +5183 -0
- package/workers/yara_rules/default.yar +33 -0
- package/workers/yara_rules/malware_families.yar +93 -0
- 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
|
package/dist/logger.d.ts
ADDED
|
@@ -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
|