@jshookmcp/jshook 0.1.5 → 0.1.7
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/README.md +72 -40
- package/README.zh.md +77 -40
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +13 -1
- package/dist/index.js +0 -0
- package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
- package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
- package/dist/modules/browser/BrowserModeManager.js +96 -10
- package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
- package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
- package/dist/modules/browser/TabRegistry.js +3 -2
- package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
- package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
- package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
- package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
- package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
- package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
- package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
- package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
- package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
- package/dist/modules/monitor/PerformanceMonitor.js +64 -32
- package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
- package/dist/modules/process/LinuxProcessManager.js +7 -3
- package/dist/modules/process/MacProcessManager.d.ts +3 -1
- package/dist/modules/process/MacProcessManager.js +7 -3
- package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
- package/dist/modules/process/ProcessManager.impl.js +54 -13
- package/dist/modules/process/index.d.ts +3 -1
- package/dist/modules/process/index.js +2 -2
- package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
- package/dist/modules/process/memory/AuditTrail.js +44 -0
- package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
- package/dist/modules/process/memory/linux/mapsParser.js +28 -0
- package/dist/modules/process/memory/regions.enumerate.js +45 -1
- package/dist/modules/process/memory/regions.protection.js +48 -2
- package/dist/modules/process/memory/scanner.d.ts +4 -1
- package/dist/modules/process/memory/scanner.js +225 -24
- package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
- package/dist/native/NativeMemoryManager.impl.js +72 -24
- package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
- package/dist/native/NativeMemoryManager.utils.js +44 -1
- package/dist/server/MCPServer.search.d.ts +3 -0
- package/dist/server/MCPServer.search.js +21 -2
- package/dist/server/ToolCallContextGuard.d.ts +2 -0
- package/dist/server/ToolCallContextGuard.js +29 -14
- package/dist/server/ToolSearch.js +11 -5
- package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
- package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
- package/dist/server/domains/process/definitions.js +20 -7
- package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
- package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
- package/dist/server/domains/process/manifest.js +1 -0
- package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
- package/dist/server/registry/discovery.js +17 -12
- package/dist/server/registry/index.js +10 -2
- package/dist/utils/TokenBudgetManager.d.ts +1 -0
- package/dist/utils/TokenBudgetManager.js +22 -0
- package/package.json +28 -41
|
@@ -8,6 +8,7 @@ export declare class NetworkMonitor {
|
|
|
8
8
|
private responses;
|
|
9
9
|
private readonly MAX_NETWORK_RECORDS;
|
|
10
10
|
private readonly MAX_INJECTED_RECORDS;
|
|
11
|
+
private readonly JS_RESPONSE_CONCURRENCY;
|
|
11
12
|
private responseBodyCache;
|
|
12
13
|
private readonly MAX_BODY_CACHE_ENTRIES;
|
|
13
14
|
private networkListeners;
|
|
@@ -55,6 +55,7 @@ export class NetworkMonitor {
|
|
|
55
55
|
responses = new Map();
|
|
56
56
|
MAX_NETWORK_RECORDS = 500;
|
|
57
57
|
MAX_INJECTED_RECORDS = 500;
|
|
58
|
+
JS_RESPONSE_CONCURRENCY = 6;
|
|
58
59
|
responseBodyCache = new Map();
|
|
59
60
|
MAX_BODY_CACHE_ENTRIES = 200;
|
|
60
61
|
networkListeners = {};
|
|
@@ -313,24 +314,30 @@ export class NetworkMonitor {
|
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
async getAllJavaScriptResponses() {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
if (response.mimeType.includes('javascript') ||
|
|
317
|
+
const candidates = Array.from(this.responses.entries()).filter(([, response]) => {
|
|
318
|
+
return (response.mimeType.includes('javascript') ||
|
|
319
319
|
response.url.endsWith('.js') ||
|
|
320
|
-
response.url.includes('.js?'))
|
|
320
|
+
response.url.includes('.js?'));
|
|
321
|
+
});
|
|
322
|
+
const jsResponses = [];
|
|
323
|
+
for (let i = 0; i < candidates.length; i += this.JS_RESPONSE_CONCURRENCY) {
|
|
324
|
+
const batch = candidates.slice(i, i + this.JS_RESPONSE_CONCURRENCY);
|
|
325
|
+
const batchResults = await Promise.all(batch.map(async ([requestId, response]) => {
|
|
321
326
|
const bodyResult = await this.getResponseBody(requestId);
|
|
322
|
-
if (bodyResult) {
|
|
323
|
-
|
|
324
|
-
? Buffer.from(bodyResult.body, 'base64').toString('utf-8')
|
|
325
|
-
: bodyResult.body;
|
|
326
|
-
jsResponses.push({
|
|
327
|
-
url: response.url,
|
|
328
|
-
content,
|
|
329
|
-
size: content.length,
|
|
330
|
-
requestId,
|
|
331
|
-
});
|
|
327
|
+
if (!bodyResult) {
|
|
328
|
+
return null;
|
|
332
329
|
}
|
|
333
|
-
|
|
330
|
+
const content = bodyResult.base64Encoded
|
|
331
|
+
? Buffer.from(bodyResult.body, 'base64').toString('utf-8')
|
|
332
|
+
: bodyResult.body;
|
|
333
|
+
return {
|
|
334
|
+
url: response.url,
|
|
335
|
+
content,
|
|
336
|
+
size: content.length,
|
|
337
|
+
requestId,
|
|
338
|
+
};
|
|
339
|
+
}));
|
|
340
|
+
jsResponses.push(...batchResults.filter((value) => value !== null));
|
|
334
341
|
}
|
|
335
342
|
logger.info(`Collected ${jsResponses.length} JavaScript responses`);
|
|
336
343
|
return jsResponses;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { setImmediate as waitForImmediate } from 'node:timers/promises';
|
|
2
3
|
import { logger } from '../../utils/logger.js';
|
|
3
4
|
import { PrerequisiteError } from '../../errors/PrerequisiteError.js';
|
|
4
5
|
import { cdpLimit } from '../../utils/concurrency.js';
|
|
@@ -65,6 +66,62 @@ function isCDPHeapSamplingNode(value) {
|
|
|
65
66
|
function isCDPHeapSamplingPayload(value) {
|
|
66
67
|
return isRecord(value) && isRecord(value.profile) && isCDPHeapSamplingNode(value.profile.head);
|
|
67
68
|
}
|
|
69
|
+
async function yieldToEventLoop() {
|
|
70
|
+
await waitForImmediate();
|
|
71
|
+
}
|
|
72
|
+
function countTraceEvents(traceData) {
|
|
73
|
+
const eventPattern = /"ph"\s*:/g;
|
|
74
|
+
let count = 0;
|
|
75
|
+
while (eventPattern.exec(traceData) !== null) {
|
|
76
|
+
count++;
|
|
77
|
+
}
|
|
78
|
+
return count;
|
|
79
|
+
}
|
|
80
|
+
function insertTopAllocation(topAllocations, candidate, topN) {
|
|
81
|
+
if (topN <= 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (topAllocations.length === topN &&
|
|
85
|
+
candidate.selfSize <= topAllocations[topAllocations.length - 1].selfSize) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let insertIndex = topAllocations.findIndex((entry) => candidate.selfSize > entry.selfSize);
|
|
89
|
+
if (insertIndex === -1) {
|
|
90
|
+
insertIndex = topAllocations.length;
|
|
91
|
+
}
|
|
92
|
+
topAllocations.splice(insertIndex, 0, candidate);
|
|
93
|
+
if (topAllocations.length > topN) {
|
|
94
|
+
topAllocations.length = topN;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function collectTopHeapAllocations(root, topN) {
|
|
98
|
+
const stack = [root];
|
|
99
|
+
const topAllocations = [];
|
|
100
|
+
let sampleCount = 0;
|
|
101
|
+
while (stack.length > 0) {
|
|
102
|
+
const node = stack.pop();
|
|
103
|
+
if (!node) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (node.callFrame) {
|
|
107
|
+
sampleCount++;
|
|
108
|
+
insertTopAllocation(topAllocations, {
|
|
109
|
+
functionName: node.callFrame.functionName || '(anonymous)',
|
|
110
|
+
url: node.callFrame.url || '',
|
|
111
|
+
selfSize: node.selfSize || 0,
|
|
112
|
+
}, topN);
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(node.children)) {
|
|
115
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
116
|
+
const child = node.children[i];
|
|
117
|
+
if (child) {
|
|
118
|
+
stack.push(child);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { sampleCount, topAllocations };
|
|
124
|
+
}
|
|
68
125
|
export class PerformanceMonitor {
|
|
69
126
|
collector;
|
|
70
127
|
cdpSession = null;
|
|
@@ -306,14 +363,7 @@ export class PerformanceMonitor {
|
|
|
306
363
|
traceData = traceChunks.join('');
|
|
307
364
|
}
|
|
308
365
|
this.tracingEnabled = false;
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
const parsed = JSON.parse(traceData);
|
|
312
|
-
eventCount = Array.isArray(parsed) ? parsed.length : (parsed.traceEvents?.length ?? 0);
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
eventCount = (traceData.match(/"ph":/g) || []).length;
|
|
316
|
-
}
|
|
366
|
+
const eventCount = countTraceEvents(traceData);
|
|
317
367
|
let savedPath;
|
|
318
368
|
if (options?.artifactPath) {
|
|
319
369
|
await writeFile(options.artifactPath, traceData, 'utf-8');
|
|
@@ -364,28 +414,10 @@ export class PerformanceMonitor {
|
|
|
364
414
|
await cdp.send('HeapProfiler.disable');
|
|
365
415
|
this.heapSamplingEnabled = false;
|
|
366
416
|
const topN = options?.topN ?? 20;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
if (node.callFrame) {
|
|
373
|
-
allNodes.push({
|
|
374
|
-
functionName: node.callFrame.functionName || '(anonymous)',
|
|
375
|
-
url: node.callFrame.url || '',
|
|
376
|
-
selfSize: node.selfSize || 0,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
if (node.children) {
|
|
380
|
-
for (const child of node.children) {
|
|
381
|
-
walkNodes(child);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
walkNodes(profile.head);
|
|
386
|
-
allNodes.sort((a, b) => b.selfSize - a.selfSize);
|
|
387
|
-
const topAllocations = allNodes.slice(0, topN);
|
|
388
|
-
const profileJson = JSON.stringify(profile, null, 2);
|
|
417
|
+
await yieldToEventLoop();
|
|
418
|
+
const { sampleCount, topAllocations } = collectTopHeapAllocations(profile.head, topN);
|
|
419
|
+
await yieldToEventLoop();
|
|
420
|
+
const profileJson = JSON.stringify(profile);
|
|
389
421
|
let savedPath;
|
|
390
422
|
if (options?.artifactPath) {
|
|
391
423
|
await writeFile(options.artifactPath, profileJson, 'utf-8');
|
|
@@ -400,10 +432,10 @@ export class PerformanceMonitor {
|
|
|
400
432
|
await writeFile(absolutePath, profileJson, 'utf-8');
|
|
401
433
|
savedPath = displayPath;
|
|
402
434
|
}
|
|
403
|
-
logger.success('Heap sampling profile saved', { sampleCount
|
|
435
|
+
logger.success('Heap sampling profile saved', { sampleCount, path: savedPath });
|
|
404
436
|
return {
|
|
405
437
|
artifactPath: savedPath,
|
|
406
|
-
sampleCount
|
|
438
|
+
sampleCount,
|
|
407
439
|
topAllocations,
|
|
408
440
|
};
|
|
409
441
|
});
|
|
@@ -19,7 +19,9 @@ export declare class LinuxProcessManager {
|
|
|
19
19
|
commandLine?: string;
|
|
20
20
|
parentPid?: number;
|
|
21
21
|
}>;
|
|
22
|
-
checkDebugPort(pid: number
|
|
22
|
+
checkDebugPort(pid: number, options?: {
|
|
23
|
+
commandLine?: string;
|
|
24
|
+
}): Promise<number | null>;
|
|
23
25
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<ProcessInfo | null>;
|
|
24
26
|
killProcess(pid: number): Promise<boolean>;
|
|
25
27
|
isRunningOnWayland(): boolean;
|
|
@@ -212,10 +212,10 @@ export class LinuxProcessManager {
|
|
|
212
212
|
return {};
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
async checkDebugPort(pid) {
|
|
215
|
+
async checkDebugPort(pid, options) {
|
|
216
216
|
try {
|
|
217
217
|
pid = safePid(pid);
|
|
218
|
-
const
|
|
218
|
+
const commandLine = options?.commandLine ?? (await this.getProcessCommandLine(pid)).commandLine;
|
|
219
219
|
if (commandLine) {
|
|
220
220
|
const match = commandLine.match(/--remote-debugging-port=(\d+)/);
|
|
221
221
|
if (match && match[1]) {
|
|
@@ -244,7 +244,11 @@ export class LinuxProcessManager {
|
|
|
244
244
|
});
|
|
245
245
|
child.unref();
|
|
246
246
|
await new Promise(resolve => setTimeout(resolve, PROCESS_LAUNCH_WAIT_MS));
|
|
247
|
-
|
|
247
|
+
if (!child.pid) {
|
|
248
|
+
logger.error(`Failed to spawn process: PID is undefined for ${executablePath}`);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const process = await this.getProcessByPid(child.pid);
|
|
248
252
|
logger.info(`Launched process with debug port ${debugPort}:`, {
|
|
249
253
|
pid: child.pid,
|
|
250
254
|
executable: executablePath,
|
|
@@ -18,7 +18,9 @@ export declare class MacProcessManager {
|
|
|
18
18
|
commandLine?: string;
|
|
19
19
|
parentPid?: number;
|
|
20
20
|
}>;
|
|
21
|
-
checkDebugPort(pid: number
|
|
21
|
+
checkDebugPort(pid: number, options?: {
|
|
22
|
+
commandLine?: string;
|
|
23
|
+
}): Promise<number | null>;
|
|
22
24
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<ProcessInfo | null>;
|
|
23
25
|
killProcess(pid: number): Promise<boolean>;
|
|
24
26
|
}
|
|
@@ -261,10 +261,10 @@ export class MacProcessManager {
|
|
|
261
261
|
return {};
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
-
async checkDebugPort(pid) {
|
|
264
|
+
async checkDebugPort(pid, options) {
|
|
265
265
|
try {
|
|
266
266
|
pid = safePid(pid);
|
|
267
|
-
const
|
|
267
|
+
const commandLine = options?.commandLine ?? (await this.getProcessCommandLine(pid)).commandLine;
|
|
268
268
|
if (commandLine) {
|
|
269
269
|
const match = commandLine.match(/--remote-debugging-port=(\d+)/);
|
|
270
270
|
if (match && match[1]) {
|
|
@@ -293,7 +293,11 @@ export class MacProcessManager {
|
|
|
293
293
|
});
|
|
294
294
|
child.unref();
|
|
295
295
|
await new Promise(resolve => setTimeout(resolve, PROCESS_LAUNCH_WAIT_MS));
|
|
296
|
-
|
|
296
|
+
if (!child.pid) {
|
|
297
|
+
logger.error(`Failed to spawn process: PID is undefined for ${executablePath}`);
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const process = await this.getProcessByPid(child.pid);
|
|
297
301
|
logger.info(`Launched process with debug port ${debugPort}:`, {
|
|
298
302
|
pid: child.pid,
|
|
299
303
|
executable: executablePath,
|
|
@@ -5,8 +5,10 @@ export declare class ProcessManager {
|
|
|
5
5
|
private powershellPath;
|
|
6
6
|
private scriptLoader;
|
|
7
7
|
private browserDiscovery;
|
|
8
|
+
private processCache;
|
|
8
9
|
constructor();
|
|
9
10
|
findProcesses(pattern: string): Promise<ProcessInfo[]>;
|
|
11
|
+
private computeProcessDiff;
|
|
10
12
|
getProcessByPid(pid: number): Promise<ProcessInfo | null>;
|
|
11
13
|
getProcessWindows(pid: number): Promise<WindowInfo[]>;
|
|
12
14
|
findChromiumProcesses(config?: TargetAppConfig): Promise<ChromiumProcess>;
|
|
@@ -15,7 +17,9 @@ export declare class ProcessManager {
|
|
|
15
17
|
commandLine?: string;
|
|
16
18
|
parentPid?: number;
|
|
17
19
|
}>;
|
|
18
|
-
checkDebugPort(pid: number
|
|
20
|
+
checkDebugPort(pid: number, options?: {
|
|
21
|
+
commandLine?: string;
|
|
22
|
+
}): Promise<number | null>;
|
|
19
23
|
private findPidByListeningPort;
|
|
20
24
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<ProcessInfo | null>;
|
|
21
25
|
injectDll(_pid: number, _dllPath: string): Promise<boolean>;
|
|
@@ -8,6 +8,7 @@ import { findChromiumProcessesWithConfig } from '../process/ProcessManager.chrom
|
|
|
8
8
|
import { DEFAULT_CHROMIUM_CONFIG, } from '../process/ProcessManager.types.js';
|
|
9
9
|
export { DEFAULT_CHROMIUM_CONFIG, } from '../process/ProcessManager.types.js';
|
|
10
10
|
const execAsync = promisify(exec);
|
|
11
|
+
const PROCESS_SNAPSHOT_CACHE_TTL_MS = 3000;
|
|
11
12
|
function sanitizePsPattern(s) {
|
|
12
13
|
return String(s || '').replace(/[`$"'{}();|<>@#%!\\\n\r]/g, '');
|
|
13
14
|
}
|
|
@@ -21,6 +22,7 @@ export class ProcessManager {
|
|
|
21
22
|
powershellPath = 'powershell.exe';
|
|
22
23
|
scriptLoader;
|
|
23
24
|
browserDiscovery;
|
|
25
|
+
processCache = new Map();
|
|
24
26
|
constructor() {
|
|
25
27
|
this.scriptLoader = new ScriptLoader();
|
|
26
28
|
this.browserDiscovery = new BrowserDiscovery();
|
|
@@ -29,6 +31,12 @@ export class ProcessManager {
|
|
|
29
31
|
async findProcesses(pattern) {
|
|
30
32
|
try {
|
|
31
33
|
const normalizedPattern = sanitizePsPattern(String(pattern || '').trim());
|
|
34
|
+
const cacheKey = normalizedPattern.toLowerCase() || '*';
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const cachedEntry = this.processCache.get(cacheKey);
|
|
37
|
+
if (cachedEntry && cachedEntry.expiresAt > now) {
|
|
38
|
+
return cachedEntry.snapshot;
|
|
39
|
+
}
|
|
32
40
|
let psCommand;
|
|
33
41
|
if (normalizedPattern) {
|
|
34
42
|
psCommand = `Get-Process -Name "*${normalizedPattern.replace(/"/g, '""')}*" -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path, MainWindowTitle, MainWindowHandle, CPU, WorkingSet64 | ConvertTo-Json -Compress`;
|
|
@@ -37,20 +45,30 @@ export class ProcessManager {
|
|
|
37
45
|
psCommand = `Get-Process -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path, MainWindowTitle, MainWindowHandle, CPU, WorkingSet64 | ConvertTo-Json -Compress`;
|
|
38
46
|
}
|
|
39
47
|
const { stdout } = await execAsync(`${this.powershellPath} -NoProfile -Command "${psCommand}"`, { maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES });
|
|
40
|
-
const processes = [];
|
|
41
48
|
const lines = stdout.trim();
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
const processes = [];
|
|
50
|
+
if (lines && lines !== 'null') {
|
|
51
|
+
const data = JSON.parse(lines);
|
|
52
|
+
const procList = Array.isArray(data) ? data : [data];
|
|
53
|
+
for (const proc of procList) {
|
|
54
|
+
processes.push({
|
|
55
|
+
pid: proc.Id,
|
|
56
|
+
name: proc.ProcessName,
|
|
57
|
+
executablePath: proc.Path,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
44
60
|
}
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
processes.push({
|
|
49
|
-
pid: proc.Id,
|
|
50
|
-
name: proc.ProcessName,
|
|
51
|
-
executablePath: proc.Path,
|
|
52
|
-
});
|
|
61
|
+
const byPid = new Map();
|
|
62
|
+
for (const process of processes) {
|
|
63
|
+
byPid.set(process.pid, process);
|
|
53
64
|
}
|
|
65
|
+
const lastDelta = this.computeProcessDiff(cachedEntry?.byPid ?? new Map(), byPid);
|
|
66
|
+
this.processCache.set(cacheKey, {
|
|
67
|
+
expiresAt: now + PROCESS_SNAPSHOT_CACHE_TTL_MS,
|
|
68
|
+
snapshot: processes,
|
|
69
|
+
byPid,
|
|
70
|
+
lastDelta,
|
|
71
|
+
});
|
|
54
72
|
const patternStr = normalizedPattern.length > 0 ? `'${normalizedPattern}'` : 'all';
|
|
55
73
|
logger.info(`Found ${processes.length} processes matching ${patternStr}`);
|
|
56
74
|
return processes;
|
|
@@ -60,6 +78,29 @@ export class ProcessManager {
|
|
|
60
78
|
return [];
|
|
61
79
|
}
|
|
62
80
|
}
|
|
81
|
+
computeProcessDiff(previousByPid, nextByPid) {
|
|
82
|
+
const added = [];
|
|
83
|
+
const removed = [];
|
|
84
|
+
const changed = [];
|
|
85
|
+
for (const [pid, nextProcess] of nextByPid) {
|
|
86
|
+
const previousProcess = previousByPid.get(pid);
|
|
87
|
+
if (!previousProcess) {
|
|
88
|
+
added.push(nextProcess);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (previousProcess.pid !== nextProcess.pid ||
|
|
92
|
+
previousProcess.name !== nextProcess.name ||
|
|
93
|
+
previousProcess.executablePath !== nextProcess.executablePath) {
|
|
94
|
+
changed.push({ before: previousProcess, after: nextProcess });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const [pid, previousProcess] of previousByPid) {
|
|
98
|
+
if (!nextByPid.has(pid)) {
|
|
99
|
+
removed.push(previousProcess);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { added, removed, changed };
|
|
103
|
+
}
|
|
63
104
|
async getProcessByPid(pid) {
|
|
64
105
|
try {
|
|
65
106
|
pid = safePid(pid);
|
|
@@ -142,10 +183,10 @@ export class ProcessManager {
|
|
|
142
183
|
return {};
|
|
143
184
|
}
|
|
144
185
|
}
|
|
145
|
-
async checkDebugPort(pid) {
|
|
186
|
+
async checkDebugPort(pid, options) {
|
|
146
187
|
try {
|
|
147
188
|
pid = safePid(pid);
|
|
148
|
-
const
|
|
189
|
+
const commandLine = options?.commandLine ?? (await this.getProcessCommandLine(pid)).commandLine;
|
|
149
190
|
if (commandLine) {
|
|
150
191
|
const match = commandLine.match(/--remote-debugging-port=(\d+)/);
|
|
151
192
|
if (match && match[1]) {
|
|
@@ -22,7 +22,9 @@ export declare class UnifiedProcessManager {
|
|
|
22
22
|
findProcesses(pattern: string): Promise<import("../process/ProcessManager.js").ProcessInfo[]>;
|
|
23
23
|
getProcessByPid(pid: number): Promise<import("../process/ProcessManager.js").ProcessInfo | null>;
|
|
24
24
|
getProcessWindows(pid: number): Promise<import("../process/ProcessManager.js").WindowInfo[]>;
|
|
25
|
-
checkDebugPort(pid: number
|
|
25
|
+
checkDebugPort(pid: number, options?: {
|
|
26
|
+
commandLine?: string;
|
|
27
|
+
}): Promise<number | null>;
|
|
26
28
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<import("../process/ProcessManager.js").ProcessInfo | null>;
|
|
27
29
|
killProcess(pid: number): Promise<boolean>;
|
|
28
30
|
getProcessCommandLine(pid: number): Promise<{
|
|
@@ -58,8 +58,8 @@ export class UnifiedProcessManager {
|
|
|
58
58
|
async getProcessWindows(pid) {
|
|
59
59
|
return this.manager.getProcessWindows(pid);
|
|
60
60
|
}
|
|
61
|
-
async checkDebugPort(pid) {
|
|
62
|
-
return this.manager.checkDebugPort(pid);
|
|
61
|
+
async checkDebugPort(pid, options) {
|
|
62
|
+
return this.manager.checkDebugPort(pid, options);
|
|
63
63
|
}
|
|
64
64
|
async launchWithDebug(executablePath, debugPort, args) {
|
|
65
65
|
return this.manager.launchWithDebug(executablePath, debugPort, args);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface AuditEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
operation: string;
|
|
4
|
+
pid: number | null;
|
|
5
|
+
address: string | null;
|
|
6
|
+
size: number | null;
|
|
7
|
+
result: 'success' | 'failure';
|
|
8
|
+
error?: string;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
user: string;
|
|
11
|
+
pattern?: string;
|
|
12
|
+
resultsCount?: number;
|
|
13
|
+
dllPath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class MemoryAuditTrail {
|
|
16
|
+
private buffer;
|
|
17
|
+
private head;
|
|
18
|
+
private count;
|
|
19
|
+
private readonly capacity;
|
|
20
|
+
constructor(capacity?: number);
|
|
21
|
+
record(entry: Omit<AuditEntry, 'timestamp' | 'user'>): void;
|
|
22
|
+
exportJson(): string;
|
|
23
|
+
clear(): void;
|
|
24
|
+
size(): number;
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class MemoryAuditTrail {
|
|
2
|
+
buffer;
|
|
3
|
+
head = 0;
|
|
4
|
+
count = 0;
|
|
5
|
+
capacity;
|
|
6
|
+
constructor(capacity = 5000) {
|
|
7
|
+
this.capacity = Number.isInteger(capacity) && capacity > 0 ? capacity : 5000;
|
|
8
|
+
this.buffer = [];
|
|
9
|
+
}
|
|
10
|
+
record(entry) {
|
|
11
|
+
const fullEntry = {
|
|
12
|
+
...entry,
|
|
13
|
+
timestamp: new Date().toISOString(),
|
|
14
|
+
user: process.env.USERNAME || process.env.USER || 'unknown',
|
|
15
|
+
};
|
|
16
|
+
if (this.count < this.capacity) {
|
|
17
|
+
const writeIndex = (this.head + this.count) % this.capacity;
|
|
18
|
+
this.buffer[writeIndex] = fullEntry;
|
|
19
|
+
this.count += 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.buffer[this.head] = fullEntry;
|
|
23
|
+
this.head = (this.head + 1) % this.capacity;
|
|
24
|
+
}
|
|
25
|
+
exportJson() {
|
|
26
|
+
const entries = [];
|
|
27
|
+
for (let i = 0; i < this.count; i += 1) {
|
|
28
|
+
const index = (this.head + i) % this.capacity;
|
|
29
|
+
const entry = this.buffer[index];
|
|
30
|
+
if (entry) {
|
|
31
|
+
entries.push(entry);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return JSON.stringify(entries, null, 2);
|
|
35
|
+
}
|
|
36
|
+
clear() {
|
|
37
|
+
this.buffer = [];
|
|
38
|
+
this.head = 0;
|
|
39
|
+
this.count = 0;
|
|
40
|
+
}
|
|
41
|
+
size() {
|
|
42
|
+
return this.count;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface LinuxMemoryRegion {
|
|
2
|
+
start: bigint;
|
|
3
|
+
end: bigint;
|
|
4
|
+
permissions: {
|
|
5
|
+
read: boolean;
|
|
6
|
+
write: boolean;
|
|
7
|
+
exec: boolean;
|
|
8
|
+
private: boolean;
|
|
9
|
+
};
|
|
10
|
+
offset: bigint;
|
|
11
|
+
dev: string;
|
|
12
|
+
inode: number;
|
|
13
|
+
pathname: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseProcMaps(content: string): LinuxMemoryRegion[];
|
|
16
|
+
export declare function formatLinuxProtection(perms: LinuxMemoryRegion['permissions']): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const PROC_MAPS_LINE_RE = /^([0-9a-f]+)-([0-9a-f]+)\s+([r-][w-][x-][ps])\s+([0-9a-f]+)\s+(\S+)\s+(\d+)\s*(.*)$/i;
|
|
2
|
+
export function parseProcMaps(content) {
|
|
3
|
+
const regions = [];
|
|
4
|
+
for (const line of content.split(/\r?\n/)) {
|
|
5
|
+
const match = line.trimEnd().match(PROC_MAPS_LINE_RE);
|
|
6
|
+
if (!match)
|
|
7
|
+
continue;
|
|
8
|
+
const perms = match[3];
|
|
9
|
+
regions.push({
|
|
10
|
+
start: BigInt(`0x${match[1]}`),
|
|
11
|
+
end: BigInt(`0x${match[2]}`),
|
|
12
|
+
permissions: {
|
|
13
|
+
read: perms[0] === 'r',
|
|
14
|
+
write: perms[1] === 'w',
|
|
15
|
+
exec: perms[2] === 'x',
|
|
16
|
+
private: perms[3] === 'p',
|
|
17
|
+
},
|
|
18
|
+
offset: BigInt(`0x${match[4]}`),
|
|
19
|
+
dev: match[5],
|
|
20
|
+
inode: parseInt(match[6], 10),
|
|
21
|
+
pathname: match[7]?.trim() ?? '',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return regions;
|
|
25
|
+
}
|
|
26
|
+
export function formatLinuxProtection(perms) {
|
|
27
|
+
return `${perms.read ? 'r' : '-'}${perms.write ? 'w' : '-'}${perms.exec ? 'x' : '-'}`;
|
|
28
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
1
2
|
import { logger } from '../../../utils/logger.js';
|
|
2
3
|
import { execAsync, executePowerShellScript } from '../../process/memory/types.js';
|
|
4
|
+
import { parseProcMaps, formatLinuxProtection } from './linux/mapsParser.js';
|
|
5
|
+
import { nativeMemoryManager } from '../../../native/NativeMemoryManager.js';
|
|
6
|
+
import { isKoffiAvailable } from '../../../native/NativeMemoryManager.utils.js';
|
|
3
7
|
function buildEnumerateRegionsScript(pid) {
|
|
4
8
|
return `
|
|
5
9
|
Add-Type @"
|
|
@@ -111,8 +115,28 @@ try {
|
|
|
111
115
|
`.trim();
|
|
112
116
|
}
|
|
113
117
|
export async function enumerateRegions(platform, pid) {
|
|
118
|
+
if (platform === 'linux') {
|
|
119
|
+
try {
|
|
120
|
+
const mapsContent = readFileSync(`/proc/${pid}/maps`, 'utf-8');
|
|
121
|
+
const readableRegions = parseProcMaps(mapsContent).filter(region => region.permissions.read);
|
|
122
|
+
const regions = readableRegions
|
|
123
|
+
.map(region => ({
|
|
124
|
+
baseAddress: `0x${region.start.toString(16)}`,
|
|
125
|
+
size: Number(region.end - region.start),
|
|
126
|
+
state: 'COMMIT',
|
|
127
|
+
protection: formatLinuxProtection(region.permissions),
|
|
128
|
+
isReadable: true,
|
|
129
|
+
type: region.pathname || 'anonymous',
|
|
130
|
+
}));
|
|
131
|
+
return { success: true, regions };
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
logger.error('Linux region enumeration failed:', error);
|
|
135
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
114
138
|
if (platform !== 'win32' && platform !== 'darwin') {
|
|
115
|
-
return { success: false, error: 'Region enumeration currently only implemented for Windows and macOS' };
|
|
139
|
+
return { success: false, error: 'Region enumeration currently only implemented for Windows, Linux, and macOS' };
|
|
116
140
|
}
|
|
117
141
|
if (platform === 'darwin') {
|
|
118
142
|
try {
|
|
@@ -148,6 +172,26 @@ export async function enumerateRegions(platform, pid) {
|
|
|
148
172
|
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
149
173
|
}
|
|
150
174
|
}
|
|
175
|
+
if (isKoffiAvailable()) {
|
|
176
|
+
try {
|
|
177
|
+
const nativeResult = await nativeMemoryManager.enumerateRegions(pid);
|
|
178
|
+
if (nativeResult.success) {
|
|
179
|
+
return nativeResult;
|
|
180
|
+
}
|
|
181
|
+
logger.warn('Native Windows region enumeration failed, falling back to PowerShell', {
|
|
182
|
+
pid,
|
|
183
|
+
error: nativeResult.error,
|
|
184
|
+
nativeAvailable: isKoffiAvailable(),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
logger.warn('Native Windows region enumeration threw, falling back to PowerShell', {
|
|
189
|
+
pid,
|
|
190
|
+
error: error instanceof Error ? error.message : String(error),
|
|
191
|
+
nativeAvailable: isKoffiAvailable(),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
151
195
|
try {
|
|
152
196
|
const psScript = buildEnumerateRegionsScript(pid);
|
|
153
197
|
const { stdout } = await executePowerShellScript(psScript, {
|