@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.
Files changed (61) hide show
  1. package/README.md +72 -40
  2. package/README.zh.md +77 -40
  3. package/dist/constants.d.ts +1 -0
  4. package/dist/constants.js +13 -1
  5. package/dist/index.js +0 -0
  6. package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
  7. package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
  8. package/dist/modules/browser/BrowserModeManager.js +96 -10
  9. package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
  10. package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
  11. package/dist/modules/browser/TabRegistry.js +3 -2
  12. package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
  13. package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
  14. package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
  15. package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
  16. package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
  17. package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
  18. package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
  19. package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
  20. package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
  21. package/dist/modules/monitor/PerformanceMonitor.js +64 -32
  22. package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
  23. package/dist/modules/process/LinuxProcessManager.js +7 -3
  24. package/dist/modules/process/MacProcessManager.d.ts +3 -1
  25. package/dist/modules/process/MacProcessManager.js +7 -3
  26. package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
  27. package/dist/modules/process/ProcessManager.impl.js +54 -13
  28. package/dist/modules/process/index.d.ts +3 -1
  29. package/dist/modules/process/index.js +2 -2
  30. package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
  31. package/dist/modules/process/memory/AuditTrail.js +44 -0
  32. package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
  33. package/dist/modules/process/memory/linux/mapsParser.js +28 -0
  34. package/dist/modules/process/memory/regions.enumerate.js +45 -1
  35. package/dist/modules/process/memory/regions.protection.js +48 -2
  36. package/dist/modules/process/memory/scanner.d.ts +4 -1
  37. package/dist/modules/process/memory/scanner.js +225 -24
  38. package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
  39. package/dist/native/NativeMemoryManager.impl.js +72 -24
  40. package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
  41. package/dist/native/NativeMemoryManager.utils.js +44 -1
  42. package/dist/server/MCPServer.search.d.ts +3 -0
  43. package/dist/server/MCPServer.search.js +21 -2
  44. package/dist/server/ToolCallContextGuard.d.ts +2 -0
  45. package/dist/server/ToolCallContextGuard.js +29 -14
  46. package/dist/server/ToolSearch.js +11 -5
  47. package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
  48. package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
  49. package/dist/server/domains/process/definitions.js +20 -7
  50. package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
  51. package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
  52. package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
  53. package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
  54. package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
  55. package/dist/server/domains/process/manifest.js +1 -0
  56. package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
  57. package/dist/server/registry/discovery.js +17 -12
  58. package/dist/server/registry/index.js +10 -2
  59. package/dist/utils/TokenBudgetManager.d.ts +1 -0
  60. package/dist/utils/TokenBudgetManager.js +22 -0
  61. package/package.json +28 -41
@@ -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 buildProtectionCheckScript(pid, address) {
4
8
  return `
5
9
  Add-Type @"
@@ -110,8 +114,28 @@ try {
110
114
  `.trim();
111
115
  }
112
116
  export async function checkMemoryProtection(platform, pid, address) {
113
- if (platform !== 'win32' && platform !== 'darwin') {
114
- return { success: false, error: 'Memory protection check currently only implemented for Windows and macOS' };
117
+ const addrNum = BigInt(address.startsWith('0x') ? address : `0x${address}`);
118
+ if (platform === 'linux') {
119
+ try {
120
+ const mapsContent = readFileSync(`/proc/${pid}/maps`, 'utf-8');
121
+ const regions = parseProcMaps(mapsContent);
122
+ const region = regions.find(r => addrNum >= r.start && addrNum < r.end);
123
+ if (!region) {
124
+ return { success: false, error: `Address ${address} not found in any memory region` };
125
+ }
126
+ return {
127
+ success: true,
128
+ protection: formatLinuxProtection(region.permissions),
129
+ isReadable: region.permissions.read,
130
+ isWritable: region.permissions.write,
131
+ isExecutable: region.permissions.exec,
132
+ regionStart: `0x${region.start.toString(16)}`,
133
+ regionSize: Number(region.end - region.start),
134
+ };
135
+ }
136
+ catch (error) {
137
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
138
+ }
115
139
  }
116
140
  if (platform === 'darwin') {
117
141
  try {
@@ -145,6 +169,28 @@ export async function checkMemoryProtection(platform, pid, address) {
145
169
  return { success: false, error: error instanceof Error ? error.message : String(error) };
146
170
  }
147
171
  }
172
+ if (isKoffiAvailable()) {
173
+ try {
174
+ const nativeResult = await nativeMemoryManager.checkMemoryProtection(pid, address);
175
+ if (nativeResult.success) {
176
+ return nativeResult;
177
+ }
178
+ logger.warn('Native Windows memory protection check failed, falling back to PowerShell', {
179
+ pid,
180
+ address,
181
+ error: nativeResult.error,
182
+ nativeAvailable: isKoffiAvailable(),
183
+ });
184
+ }
185
+ catch (error) {
186
+ logger.warn('Native Windows memory protection check threw, falling back to PowerShell', {
187
+ pid,
188
+ address,
189
+ error: error instanceof Error ? error.message : String(error),
190
+ nativeAvailable: isKoffiAvailable(),
191
+ });
192
+ }
193
+ }
148
194
  try {
149
195
  const addrNum = parseInt(address, 16);
150
196
  if (isNaN(addrNum)) {
@@ -3,7 +3,10 @@ export declare function buildPatternBytesAndMask(pattern: string, patternType: s
3
3
  patternBytes: number[];
4
4
  mask: number[];
5
5
  };
6
- export declare function patternToBytesMac(pattern: string, patternType: string): number[];
6
+ export declare function patternToBytesMac(pattern: string, patternType: string): {
7
+ bytes: number[];
8
+ mask: number[];
9
+ };
7
10
  export declare function scanMemory(platform: Platform, pid: number, pattern: string, patternType?: PatternType): Promise<MemoryScanResult>;
8
11
  export declare function scanMemoryFiltered(pid: number, pattern: string, addresses: string[], patternType: PatternType | undefined, _readMemoryFn: (pid: number, address: string, size: number) => Promise<{
9
12
  success: boolean;
@@ -1,6 +1,11 @@
1
+ import { readFileSync, openSync, readSync, closeSync } from 'node:fs';
1
2
  import { promises as fs } from 'node:fs';
2
3
  import { logger } from '../../../utils/logger.js';
3
4
  import { execAsync, executePowerShellScript, } from '../../process/memory/types.js';
5
+ import { nativeMemoryManager } from '../../../native/NativeMemoryManager.js';
6
+ import { isKoffiAvailable } from '../../../native/NativeMemoryManager.utils.js';
7
+ import { parseProcMaps } from './linux/mapsParser.js';
8
+ import { findPatternInBuffer } from '../../../native/NativeMemoryManager.utils.js';
4
9
  export function buildPatternBytesAndMask(pattern, patternType) {
5
10
  let patternBytes = [];
6
11
  let mask = [];
@@ -73,20 +78,27 @@ export function buildPatternBytesAndMask(pattern, patternType) {
73
78
  return { patternBytes, mask };
74
79
  }
75
80
  export function patternToBytesMac(pattern, patternType) {
81
+ const bytes = [];
82
+ const mask = [];
76
83
  switch (patternType) {
77
84
  case 'hex': {
78
- const bytes = [];
79
- for (const part of pattern.trim().split(/\s+/)) {
80
- if (part === '??' || part === '?' || part === '**')
81
- continue;
82
- const b = parseInt(part, 16);
83
- if (isNaN(b))
84
- throw new Error(`Invalid hex byte: ${part}`);
85
- bytes.push(b);
85
+ const parts = pattern.trim().split(/\s+/);
86
+ for (const part of parts) {
87
+ if (part === '??' || part === '?' || part === '**') {
88
+ bytes.push(0);
89
+ mask.push(0);
90
+ }
91
+ else {
92
+ const b = parseInt(part, 16);
93
+ if (isNaN(b))
94
+ throw new Error(`Invalid hex byte: ${part}`);
95
+ bytes.push(b);
96
+ mask.push(1);
97
+ }
86
98
  }
87
99
  if (!bytes.length)
88
- throw new Error('Pattern is empty or all wildcards');
89
- return bytes;
100
+ throw new Error('Pattern is empty');
101
+ break;
90
102
  }
91
103
  case 'int32': {
92
104
  const v = parseInt(pattern);
@@ -94,12 +106,18 @@ export function patternToBytesMac(pattern, patternType) {
94
106
  throw new Error('Invalid int32 value');
95
107
  const buf = Buffer.allocUnsafe(4);
96
108
  buf.writeInt32LE(v, 0);
97
- return Array.from(buf);
109
+ const arr = Array.from(buf);
110
+ bytes.push(...arr);
111
+ mask.push(...arr.map(() => 1));
112
+ break;
98
113
  }
99
114
  case 'int64': {
100
115
  const buf = Buffer.allocUnsafe(8);
101
116
  buf.writeBigInt64LE(BigInt.asIntN(64, BigInt(pattern)), 0);
102
- return Array.from(buf);
117
+ const arr = Array.from(buf);
118
+ bytes.push(...arr);
119
+ mask.push(...arr.map(() => 1));
120
+ break;
103
121
  }
104
122
  case 'float': {
105
123
  const v = parseFloat(pattern);
@@ -107,7 +125,10 @@ export function patternToBytesMac(pattern, patternType) {
107
125
  throw new Error('Invalid float value');
108
126
  const buf = Buffer.allocUnsafe(4);
109
127
  buf.writeFloatLE(v, 0);
110
- return Array.from(buf);
128
+ const arr = Array.from(buf);
129
+ bytes.push(...arr);
130
+ mask.push(...arr.map(() => 1));
131
+ break;
111
132
  }
112
133
  case 'double': {
113
134
  const v = parseFloat(pattern);
@@ -115,13 +136,21 @@ export function patternToBytesMac(pattern, patternType) {
115
136
  throw new Error('Invalid double value');
116
137
  const buf = Buffer.allocUnsafe(8);
117
138
  buf.writeDoubleLE(v, 0);
118
- return Array.from(buf);
139
+ const arr = Array.from(buf);
140
+ bytes.push(...arr);
141
+ mask.push(...arr.map(() => 1));
142
+ break;
143
+ }
144
+ case 'string': {
145
+ const arr = Array.from(Buffer.from(pattern, 'utf8'));
146
+ bytes.push(...arr);
147
+ mask.push(...arr.map(() => 1));
148
+ break;
119
149
  }
120
- case 'string':
121
- return Array.from(Buffer.from(pattern, 'utf8'));
122
150
  default:
123
151
  throw new Error(`Unsupported pattern type: ${patternType}`);
124
152
  }
153
+ return { bytes, mask };
125
154
  }
126
155
  function buildMemoryScanScript(pid, pattern, patternType) {
127
156
  const { patternBytes, mask } = buildPatternBytesAndMask(pattern, patternType);
@@ -255,6 +284,28 @@ try {
255
284
  }
256
285
  async function scanMemoryWindows(pid, pattern, patternType) {
257
286
  try {
287
+ if (isKoffiAvailable()) {
288
+ try {
289
+ const nativeResult = await nativeMemoryManager.scanMemory(pid, pattern, patternType);
290
+ if (nativeResult.success) {
291
+ return nativeResult;
292
+ }
293
+ logger.warn('Native Windows memory scan failed, falling back to PowerShell', {
294
+ pid,
295
+ patternType,
296
+ error: nativeResult.error,
297
+ nativeAvailable: isKoffiAvailable(),
298
+ });
299
+ }
300
+ catch (error) {
301
+ logger.warn('Native Windows memory scan threw, falling back to PowerShell', {
302
+ pid,
303
+ patternType,
304
+ error: error instanceof Error ? error.message : String(error),
305
+ nativeAvailable: isKoffiAvailable(),
306
+ });
307
+ }
308
+ }
258
309
  const psScript = buildMemoryScanScript(pid, pattern, patternType);
259
310
  const { stdout, stderr } = await executePowerShellScript(psScript, {
260
311
  maxBuffer: 1024 * 1024 * 50,
@@ -283,22 +334,166 @@ async function scanMemoryWindows(pid, pattern, patternType) {
283
334
  };
284
335
  }
285
336
  }
286
- async function scanMemoryLinux(_pid, _pattern, _patternType) {
287
- return {
288
- success: false,
289
- addresses: [],
290
- error: 'Memory scanning on Linux requires scanning /proc/pid/maps and iterating regions. Use scanmem or GameConqueror for now.',
291
- };
337
+ function formatLinuxProcAccessError(pid, procFile, error) {
338
+ const err = error;
339
+ switch (err?.code) {
340
+ case 'ENOENT':
341
+ case 'ESRCH':
342
+ return `Process ${pid} no longer exists or /proc/${pid}/${procFile} is unavailable.`;
343
+ case 'EACCES':
344
+ case 'EPERM':
345
+ return `Cannot access /proc/${pid}/${procFile}. Requires root privileges or ptrace access.`;
346
+ default:
347
+ return err instanceof Error ? err.message : String(error);
348
+ }
349
+ }
350
+ async function scanMemoryLinux(pid, pattern, patternType) {
351
+ let patternBytes;
352
+ let mask;
353
+ try {
354
+ const result = buildPatternBytesAndMask(pattern, patternType);
355
+ patternBytes = result.patternBytes;
356
+ mask = result.mask;
357
+ }
358
+ catch (error) {
359
+ return {
360
+ success: false,
361
+ addresses: [],
362
+ error: error instanceof Error ? error.message : 'Invalid pattern',
363
+ };
364
+ }
365
+ try {
366
+ let mapsContent;
367
+ try {
368
+ mapsContent = readFileSync(`/proc/${pid}/maps`, 'utf-8');
369
+ }
370
+ catch (error) {
371
+ return {
372
+ success: false,
373
+ addresses: [],
374
+ error: formatLinuxProcAccessError(pid, 'maps', error),
375
+ };
376
+ }
377
+ const linuxRegions = parseProcMaps(mapsContent).filter(r => r.permissions.read);
378
+ let fd;
379
+ try {
380
+ fd = openSync(`/proc/${pid}/mem`, 'r');
381
+ }
382
+ catch (error) {
383
+ return {
384
+ success: false,
385
+ addresses: [],
386
+ error: formatLinuxProcAccessError(pid, 'mem', error),
387
+ };
388
+ }
389
+ const foundAddresses = new Set();
390
+ const chunkSize = 16 * 1024 * 1024;
391
+ const maxResults = 10000;
392
+ const overlap = Math.max(patternBytes.length - 1, 0);
393
+ try {
394
+ for (const region of linuxRegions) {
395
+ if (foundAddresses.size >= maxResults)
396
+ break;
397
+ if (region.end <= region.start)
398
+ continue;
399
+ let chunkOffset = 0n;
400
+ let carryOver = Buffer.alloc(0);
401
+ const regionSize = region.end - region.start;
402
+ while (chunkOffset < regionSize && foundAddresses.size < maxResults) {
403
+ const remaining = regionSize - chunkOffset;
404
+ const readSize = Number(remaining > BigInt(chunkSize) ? BigInt(chunkSize) : remaining);
405
+ const chunkBuffer = Buffer.allocUnsafe(readSize);
406
+ let bytesRead;
407
+ try {
408
+ bytesRead = readSync(fd, chunkBuffer, 0, readSize, region.start + chunkOffset);
409
+ }
410
+ catch (error) {
411
+ const err = error;
412
+ if (err?.code === 'EIO' || err?.code === 'EFAULT' || err?.code === 'EACCES' || err?.code === 'EPERM') {
413
+ logger.debug('Skipping unreadable Linux memory region chunk', {
414
+ pid,
415
+ start: `0x${region.start.toString(16)}`,
416
+ offset: chunkOffset.toString(),
417
+ code: err.code,
418
+ });
419
+ break;
420
+ }
421
+ throw error;
422
+ }
423
+ if (bytesRead <= 0) {
424
+ break;
425
+ }
426
+ const chunk = bytesRead === readSize ? chunkBuffer : chunkBuffer.subarray(0, bytesRead);
427
+ const scanBuffer = carryOver.length > 0 ? Buffer.concat([carryOver, chunk]) : chunk;
428
+ const scanBase = region.start + chunkOffset - BigInt(carryOver.length);
429
+ const chunkAdvance = BigInt(bytesRead);
430
+ const isLastChunk = chunkOffset + chunkAdvance >= regionSize || bytesRead < readSize;
431
+ const deferredTail = isLastChunk ? 0 : Math.min(overlap, scanBuffer.length);
432
+ const reportableLimit = scanBuffer.length - deferredTail;
433
+ const matches = findPatternInBuffer(scanBuffer, patternBytes, mask);
434
+ for (const matchOffset of matches) {
435
+ if (!isLastChunk && matchOffset >= reportableLimit) {
436
+ continue;
437
+ }
438
+ const absoluteAddress = scanBase + BigInt(matchOffset);
439
+ if (absoluteAddress < region.start || absoluteAddress >= region.end) {
440
+ continue;
441
+ }
442
+ foundAddresses.add(`0x${absoluteAddress.toString(16)}`);
443
+ if (foundAddresses.size >= maxResults) {
444
+ break;
445
+ }
446
+ }
447
+ if (deferredTail > 0) {
448
+ carryOver = scanBuffer.subarray(scanBuffer.length - deferredTail);
449
+ }
450
+ else {
451
+ carryOver = Buffer.alloc(0);
452
+ }
453
+ chunkOffset += chunkAdvance;
454
+ if (bytesRead < readSize) {
455
+ logger.debug('Linux memory scan stopped after short read', {
456
+ pid,
457
+ start: `0x${region.start.toString(16)}`,
458
+ requested: readSize,
459
+ bytesRead,
460
+ });
461
+ break;
462
+ }
463
+ }
464
+ }
465
+ }
466
+ finally {
467
+ closeSync(fd);
468
+ }
469
+ const addresses = Array.from(foundAddresses);
470
+ return {
471
+ success: true,
472
+ addresses,
473
+ stats: { patternLength: patternBytes.length, resultsFound: addresses.length },
474
+ };
475
+ }
476
+ catch (error) {
477
+ return {
478
+ success: false,
479
+ addresses: [],
480
+ error: error instanceof Error ? error.message : String(error),
481
+ };
482
+ }
292
483
  }
293
484
  async function scanMemoryMac(pid, pattern, patternType) {
294
485
  let patternBytes;
486
+ let patternMask;
295
487
  try {
296
- patternBytes = patternToBytesMac(pattern, patternType);
488
+ const result = patternToBytesMac(pattern, patternType);
489
+ patternBytes = result.bytes;
490
+ patternMask = result.mask;
297
491
  }
298
492
  catch (e) {
299
493
  return { success: false, addresses: [], error: e instanceof Error ? e.message : 'Invalid pattern' };
300
494
  }
301
495
  const byteList = patternBytes.map(b => `0x${b.toString(16)}`).join(',');
496
+ const maskList = patternMask.join(',');
302
497
  const tag = `${pid}_${Date.now()}`;
303
498
  const pyFile = `/tmp/lldb_scan_${tag}.py`;
304
499
  const cmdFile = `/tmp/lldb_scan_${tag}.txt`;
@@ -308,6 +503,7 @@ import lldb, json, sys
308
503
  def __lldb_init_module(debugger, internal_dict):
309
504
  proc = debugger.GetSelectedTarget().GetProcess()
310
505
  pat = bytes([${byteList}])
506
+ mask = [${maskList}]
311
507
  results = []
312
508
  rl = proc.GetMemoryRegions()
313
509
  for i in range(rl.GetSize()):
@@ -325,7 +521,12 @@ def __lldb_init_module(debugger, internal_dict):
325
521
  continue
326
522
  n = len(pat)
327
523
  for j in range(len(data) - n + 1):
328
- if data[j:j+n] == pat:
524
+ match = True
525
+ for k in range(n):
526
+ if mask[k] == 1 and data[j+k] != pat[k]:
527
+ match = False
528
+ break
529
+ if match:
329
530
  results.append(hex(s + j))
330
531
  if len(results) >= 1000:
331
532
  break
@@ -1,5 +1,9 @@
1
1
  import type { MemoryRegion, ModuleInfo, NativeMemoryReadResult, NativeMemoryScanResult, NativeMemoryWriteResult } from './NativeMemoryManager.types.js';
2
2
  export type { MemoryRegion, ModuleInfo, NativeMemoryReadResult, NativeMemoryScanResult, NativeMemoryWriteResult, } from './NativeMemoryManager.types.js';
3
+ export declare function scanRegionInChunks(region: {
4
+ baseAddress: bigint;
5
+ regionSize: number;
6
+ }, patternBytes: number[], mask: number[], readChunk: (address: bigint, size: number) => Buffer<ArrayBufferLike>, chunkSize?: number): bigint[];
3
7
  export declare class NativeMemoryManager {
4
8
  checkAvailability(): Promise<{
5
9
  available: boolean;
@@ -1,10 +1,38 @@
1
1
  import { logger } from '../utils/logger.js';
2
2
  import { exec } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
+ import { cpuLimit } from '../utils/concurrency.js';
4
5
  import { PAGE, MEM, openProcessForMemory, CloseHandle, ReadProcessMemory, WriteProcessMemory, VirtualQueryEx, VirtualProtectEx, VirtualAllocEx, CreateRemoteThread, GetModuleHandle, GetProcAddress, NtQueryInformationProcess, EnumProcessModules, GetModuleBaseName, GetModuleInformation, } from './Win32API.js';
5
6
  import { findPatternInBuffer, getProtectionString, getStateString, getTypeString, isExecutable, isReadable, isWritable, parsePattern, } from './NativeMemoryManager.utils.js';
6
7
  import { checkNativeMemoryAvailability } from './NativeMemoryManager.availability.js';
7
8
  const execAsync = promisify(exec);
9
+ const SCAN_CHUNK_SIZE = 16 * 1024 * 1024;
10
+ export function scanRegionInChunks(region, patternBytes, mask, readChunk, chunkSize = SCAN_CHUNK_SIZE) {
11
+ if (patternBytes.length === 0 || region.regionSize < patternBytes.length || chunkSize <= 0) {
12
+ return [];
13
+ }
14
+ const overlap = Math.max(patternBytes.length - 1, 0);
15
+ let carryOver = Buffer.alloc(0);
16
+ const matches = [];
17
+ for (let chunkOffset = 0; chunkOffset < region.regionSize; chunkOffset += chunkSize) {
18
+ const readSize = Math.min(chunkSize, region.regionSize - chunkOffset);
19
+ const chunkAddress = region.baseAddress + BigInt(chunkOffset);
20
+ const chunk = readChunk(chunkAddress, readSize);
21
+ const scanBuffer = carryOver.length > 0 ? Buffer.concat([carryOver, chunk]) : chunk;
22
+ const chunkMatches = findPatternInBuffer(scanBuffer, patternBytes, mask);
23
+ for (const matchOffset of chunkMatches) {
24
+ const regionOffset = chunkOffset + matchOffset - carryOver.length;
25
+ matches.push(region.baseAddress + BigInt(regionOffset));
26
+ }
27
+ if (overlap === 0 || chunkOffset + readSize >= region.regionSize) {
28
+ carryOver = Buffer.alloc(0);
29
+ continue;
30
+ }
31
+ const carrySize = Math.min(overlap, scanBuffer.length);
32
+ carryOver = scanBuffer.subarray(scanBuffer.length - carrySize);
33
+ }
34
+ return matches;
35
+ }
8
36
  export class NativeMemoryManager {
9
37
  async checkAvailability() {
10
38
  return checkNativeMemoryAvailability(execAsync);
@@ -156,45 +184,65 @@ export class NativeMemoryManager {
156
184
  if (patternBytes.length === 0) {
157
185
  return { success: false, addresses: [], error: 'Invalid pattern' };
158
186
  }
159
- const handle = openProcessForMemory(pid, false);
160
- const addresses = [];
161
187
  const maxResults = 10000;
188
+ const readableRegions = [];
189
+ const handle = openProcessForMemory(pid, false);
162
190
  try {
163
191
  let address = 0n;
164
192
  const maxAddress = BigInt('0x7FFFFFFF0000');
165
- while (address < maxAddress && addresses.length < maxResults) {
193
+ while (address < maxAddress) {
166
194
  const { success, info } = VirtualQueryEx(handle, address);
167
195
  if (!success || info.RegionSize === 0n) {
168
196
  break;
169
197
  }
170
- if (isReadable(info) && Number(info.RegionSize) > 0 && Number(info.RegionSize) < 1024 * 1024 * 1024) {
171
- try {
172
- const regionBuffer = ReadProcessMemory(handle, info.BaseAddress, Number(info.RegionSize));
173
- const matches = findPatternInBuffer(regionBuffer, patternBytes, mask);
174
- for (const offset of matches) {
175
- const foundAddr = info.BaseAddress + BigInt(offset);
176
- addresses.push(`0x${foundAddr.toString(16).toUpperCase()}`);
177
- if (addresses.length >= maxResults)
178
- break;
179
- }
180
- }
181
- catch {
182
- }
198
+ if (isReadable(info) &&
199
+ info.RegionSize > 0n &&
200
+ info.RegionSize <= BigInt(Number.MAX_SAFE_INTEGER)) {
201
+ readableRegions.push({
202
+ baseAddress: info.BaseAddress,
203
+ regionSize: Number(info.RegionSize),
204
+ });
183
205
  }
184
206
  address = info.BaseAddress + info.RegionSize;
185
207
  }
186
- return {
187
- success: true,
188
- addresses,
189
- stats: {
190
- patternLength: patternBytes.length,
191
- resultsFound: addresses.length,
192
- },
193
- };
194
208
  }
195
209
  finally {
196
210
  CloseHandle(handle);
197
211
  }
212
+ const regionMatches = await Promise.all(readableRegions.map(region => cpuLimit(async () => {
213
+ const scanHandle = openProcessForMemory(pid, false);
214
+ try {
215
+ try {
216
+ return scanRegionInChunks(region, patternBytes, mask, (address, size) => ReadProcessMemory(scanHandle, address, size));
217
+ }
218
+ catch {
219
+ return [];
220
+ }
221
+ }
222
+ finally {
223
+ CloseHandle(scanHandle);
224
+ }
225
+ })));
226
+ const addresses = [];
227
+ for (const matches of regionMatches) {
228
+ for (const foundAddr of matches) {
229
+ addresses.push(`0x${foundAddr.toString(16).toUpperCase()}`);
230
+ if (addresses.length >= maxResults) {
231
+ break;
232
+ }
233
+ }
234
+ if (addresses.length >= maxResults) {
235
+ break;
236
+ }
237
+ }
238
+ return {
239
+ success: true,
240
+ addresses,
241
+ stats: {
242
+ patternLength: patternBytes.length,
243
+ resultsFound: addresses.length,
244
+ },
245
+ };
198
246
  }
199
247
  catch (error) {
200
248
  logger.error('Native memory scan failed', {
@@ -1,5 +1,6 @@
1
1
  import { type MemoryBasicInfo } from './Win32API.js';
2
2
  import type { NativePatternType } from './NativeMemoryManager.types.js';
3
+ export declare function isKoffiAvailable(): boolean;
3
4
  export declare function parsePattern(pattern: string, patternType: NativePatternType): {
4
5
  patternBytes: number[];
5
6
  mask: number[];
@@ -1,4 +1,7 @@
1
- import { PAGE, MEM, MEM_TYPE } from './Win32API.js';
1
+ import { PAGE, MEM, MEM_TYPE, isKoffiAvailable as isWin32KoffiAvailable, } from './Win32API.js';
2
+ export function isKoffiAvailable() {
3
+ return isWin32KoffiAvailable();
4
+ }
2
5
  export function parsePattern(pattern, patternType) {
3
6
  const patternBytes = [];
4
7
  const mask = [];
@@ -67,7 +70,47 @@ export function parsePattern(pattern, patternType) {
67
70
  }
68
71
  return { patternBytes, mask };
69
72
  }
73
+ function findExactPatternBMH(buffer, pattern) {
74
+ const matches = [];
75
+ const patternLength = pattern.length;
76
+ if (patternLength === 0 || buffer.length < patternLength) {
77
+ return matches;
78
+ }
79
+ const lastIndex = patternLength - 1;
80
+ const skipTable = new Uint32Array(256);
81
+ skipTable.fill(patternLength);
82
+ for (let i = 0; i < lastIndex; i++) {
83
+ const patternByte = pattern[i];
84
+ if (patternByte !== undefined) {
85
+ skipTable[patternByte] = lastIndex - i;
86
+ }
87
+ }
88
+ let offset = 0;
89
+ while (offset <= buffer.length - patternLength) {
90
+ let patternIndex = lastIndex;
91
+ while (patternIndex >= 0 && buffer[offset + patternIndex] === pattern[patternIndex]) {
92
+ patternIndex--;
93
+ }
94
+ if (patternIndex < 0) {
95
+ matches.push(offset);
96
+ offset += 1;
97
+ continue;
98
+ }
99
+ const skipByte = buffer[offset + lastIndex];
100
+ if (skipByte === undefined) {
101
+ break;
102
+ }
103
+ offset += skipTable[skipByte] ?? patternLength;
104
+ }
105
+ return matches;
106
+ }
70
107
  export function findPatternInBuffer(buffer, pattern, mask) {
108
+ if (pattern.length === 0) {
109
+ return [];
110
+ }
111
+ if (mask.every(value => value === 1)) {
112
+ return findExactPatternBMH(buffer, pattern);
113
+ }
71
114
  const matches = [];
72
115
  for (let i = 0; i <= buffer.length - pattern.length; i++) {
73
116
  let found = true;
@@ -1,2 +1,5 @@
1
1
  import type { MCPServerContext } from './MCPServer.context.js';
2
+ import { ToolSearchEngine } from './ToolSearch.js';
3
+ export declare function buildSearchSignature(ctx: MCPServerContext): string;
4
+ export declare function getSearchEngine(ctx: MCPServerContext): ToolSearchEngine;
2
5
  export declare function registerSearchMetaTools(ctx: MCPServerContext): void;
@@ -28,7 +28,24 @@ function getCombinedTools(ctx) {
28
28
  }
29
29
  return [...tools.values()];
30
30
  }
31
- function getSearchEngine(ctx) {
31
+ const searchEngineCache = new WeakMap();
32
+ export function buildSearchSignature(ctx) {
33
+ const extParts = [];
34
+ for (const [name, record] of ctx.extensionToolsByName) {
35
+ extParts.push(`${name}:${record.domain}`);
36
+ }
37
+ extParts.sort();
38
+ return [
39
+ ctx.currentTier,
40
+ ctx.extensionWorkflowRuntimeById.size,
41
+ extParts.join('|'),
42
+ ].join('::');
43
+ }
44
+ export function getSearchEngine(ctx) {
45
+ const signature = buildSearchSignature(ctx);
46
+ const cached = searchEngineCache.get(ctx);
47
+ if (cached && cached.signature === signature)
48
+ return cached.engine;
32
49
  const tools = getCombinedTools(ctx);
33
50
  const extensionDomains = getExtensionDomainMap(ctx);
34
51
  const domainScoreMultipliers = new Map();
@@ -43,7 +60,9 @@ function getSearchEngine(ctx) {
43
60
  toolScoreMultipliers.set('run_extension_workflow', 1.35);
44
61
  toolScoreMultipliers.set('list_extension_workflows', 1.25);
45
62
  }
46
- return new ToolSearchEngine(tools, extensionDomains, domainScoreMultipliers, toolScoreMultipliers);
63
+ const engine = new ToolSearchEngine(tools, extensionDomains, domainScoreMultipliers, toolScoreMultipliers);
64
+ searchEngineCache.set(ctx, { signature, engine });
65
+ return engine;
47
66
  }
48
67
  function getToolByName(ctx) {
49
68
  return new Map(getCombinedTools(ctx).map((tool) => [tool.name, tool]));
@@ -8,11 +8,13 @@ interface TabContextProvider {
8
8
  }
9
9
  export declare class ToolCallContextGuard {
10
10
  private getProvider;
11
+ private readonly contextSensitiveCache;
11
12
  constructor(getProvider: () => TabContextProvider | null);
12
13
  isContextSensitive(toolName: string): boolean;
13
14
  enrichResponse<T extends {
14
15
  content?: unknown[];
15
16
  isError?: boolean;
16
17
  }>(toolName: string, response: T): T;
18
+ private spliceTabContext;
17
19
  }
18
20
  export {};