@jshookmcp/jshook 0.1.5 → 0.1.6

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 (81) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +72 -40
  3. package/README.zh.md +77 -40
  4. package/dist/constants.d.ts +1 -0
  5. package/dist/constants.js +13 -1
  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/captcha/AICaptchaDetector.js +185 -185
  15. package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
  16. package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
  17. package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
  18. package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
  19. package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
  20. package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
  21. package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
  22. package/dist/modules/monitor/PerformanceMonitor.js +64 -32
  23. package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
  24. package/dist/modules/process/LinuxProcessManager.js +7 -3
  25. package/dist/modules/process/MacProcessManager.d.ts +3 -1
  26. package/dist/modules/process/MacProcessManager.js +32 -28
  27. package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
  28. package/dist/modules/process/ProcessManager.impl.js +54 -13
  29. package/dist/modules/process/index.d.ts +3 -1
  30. package/dist/modules/process/index.js +2 -2
  31. package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
  32. package/dist/modules/process/memory/AuditTrail.js +44 -0
  33. package/dist/modules/process/memory/availability.js +49 -49
  34. package/dist/modules/process/memory/injector.js +185 -185
  35. package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
  36. package/dist/modules/process/memory/linux/mapsParser.js +28 -0
  37. package/dist/modules/process/memory/reader.js +50 -50
  38. package/dist/modules/process/memory/regions.enumerate.js +45 -1
  39. package/dist/modules/process/memory/regions.protection.js +48 -2
  40. package/dist/modules/process/memory/scanner.d.ts +4 -1
  41. package/dist/modules/process/memory/scanner.js +383 -182
  42. package/dist/modules/process/memory/writer.js +54 -54
  43. package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
  44. package/dist/native/NativeMemoryManager.impl.js +72 -24
  45. package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
  46. package/dist/native/NativeMemoryManager.utils.js +44 -1
  47. package/dist/native/scripts/linux/enum-windows.sh +12 -12
  48. package/dist/native/scripts/macos/enum-windows.applescript +22 -22
  49. package/dist/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
  50. package/dist/native/scripts/windows/enum-windows.ps1 +44 -44
  51. package/dist/native/scripts/windows/inject-dll.ps1 +21 -21
  52. package/dist/server/MCPServer.search.d.ts +3 -0
  53. package/dist/server/MCPServer.search.js +21 -2
  54. package/dist/server/ToolCallContextGuard.d.ts +2 -0
  55. package/dist/server/ToolCallContextGuard.js +29 -14
  56. package/dist/server/ToolSearch.js +11 -5
  57. package/dist/server/domains/browser/definitions.tools.page-core.js +53 -53
  58. package/dist/server/domains/browser/definitions.tools.runtime.js +40 -40
  59. package/dist/server/domains/browser/definitions.tools.security.js +76 -76
  60. package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
  61. package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
  62. package/dist/server/domains/process/definitions.js +20 -7
  63. package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
  64. package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
  65. package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
  66. package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
  67. package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
  68. package/dist/server/domains/process/manifest.js +1 -0
  69. package/dist/server/domains/transform/handlers.impl.transform-base.js +102 -102
  70. package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
  71. package/dist/server/domains/workflow/handlers.impl.workflow-base.js +51 -51
  72. package/dist/server/registry/discovery.js +17 -12
  73. package/dist/server/registry/index.js +10 -2
  74. package/dist/utils/TokenBudgetManager.d.ts +1 -0
  75. package/dist/utils/TokenBudgetManager.js +22 -0
  76. package/package.json +5 -1
  77. package/src/native/scripts/linux/enum-windows.sh +12 -12
  78. package/src/native/scripts/macos/enum-windows.applescript +22 -22
  79. package/src/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
  80. package/src/native/scripts/windows/enum-windows.ps1 +44 -44
  81. package/src/native/scripts/windows/inject-dll.ps1 +21 -21
@@ -135,194 +135,194 @@ export class AICaptchaDetector {
135
135
  bodyTextPreview: `${pageInfo.bodyText.substring(0, 200)}...`,
136
136
  };
137
137
  return `#
138
-
139
- ##
140
- ,(CAPTCHA),。
141
-
142
- ##
138
+
139
+ ##
140
+ ,(CAPTCHA),。
141
+
142
+ ##
143
143
  \`\`\`json
144
144
  ${JSON.stringify(promptPayload, null, 2)}
145
145
  \`\`\`
146
-
147
- ##
148
-
149
- ### 1. (Interactive CAPTCHA)
150
- **1.1 (Slider CAPTCHA)**
151
- - ****: 、
152
- - ****: (Geetest)、、、
153
- - ****: 、、""
154
- - **DOM**: \`.geetest_slider\`, \`.nc_1_wrapper\`, \`.tcaptcha-transform\`
155
-
156
- **1.2 (Image CAPTCHA)**
157
- - ****: ("")
158
- - ****: reCAPTCHA v2、hCaptcha
159
- - ****: 3x34x4、
160
- - **DOM**: \`iframe[src*="recaptcha"]\`, \`.h-captcha\`
161
-
162
- **1.3 (Text CAPTCHA)**
163
- - ****: /
164
- - ****: 、
165
- - ****: ""
166
-
167
- ### 2. (Automatic CAPTCHA)
168
- **2.1 reCAPTCHA v3**
169
- - ****: ,reCAPTCHA
170
- - ****: "Protected by reCAPTCHA"
171
-
172
- **2.2 Cloudflare Turnstile**
173
- - ****: "" / "Checking your browser"
174
- - ****: Cloudflare logo、、Ray ID
175
-
176
- ### 3. (False Positives - )
177
- **3.1 **
178
- - 、、
179
- - ""()
180
- - ""()
181
-
182
- **3.2 **
183
- - 、
184
- - 、
185
- -
186
-
187
- **3.3 UI**
188
- - Range slider、Progress bar
189
- - Carousel、Swiper
190
- - 、
191
-
192
- ##
193
-
194
- ### Step 1:
195
- 1. :
196
- - +
197
- - +
198
- - ""
199
- - ""
200
- - Cloudflare/reCAPTCHA logo
201
-
202
- ### Step 2:
203
- 1. URL:
204
- - \`/captcha\`, \`/challenge\`, \`/verify\`
205
- - \`cdn-cgi/challenge\` (Cloudflare)
206
- - \`recaptcha.net\`, \`hcaptcha.com\`
207
-
208
- 2. :
209
- - ""、""、""
210
- - "Verify", "Challenge", "Security Check"
211
-
212
- 3. :
213
- - suspiciousElements →
214
- - →
215
-
216
- ### Step 3:
217
- 1. :
218
- - ""、""
219
- - ,
220
- - → \`detected: false\`
221
-
222
- 2. /UI:
223
- - 、、
224
- - → \`detected: false\`
225
-
226
- ### Step 4:
227
- - **90-100%**: + DOM
228
- - **70-89%**: ,DOM
229
- - **50-69%**: ,
230
- - **0-49%**:
231
-
232
- ##
233
-
234
- **JSON Schema**:
235
-
236
- \`\`\`json
237
- {
238
- "detected": boolean,
239
- "type": "slider" | "image" | "recaptcha" | "hcaptcha" | "cloudflare" | "text_input" | "none",
240
- "confidence": number,
241
- "reasoning": string,
242
- "location": {
243
- "x": number,
244
- "y": number,
245
- "width": number,
246
- "height": number
247
- } | null,
248
- "vendor": "geetest" | "tencent" | "aliyun" | "recaptcha" | "hcaptcha" | "cloudflare" | "unknown",
249
- "suggestions": string[]
250
- }
251
- \`\`\`
252
-
253
- ###
254
- - **detected**: ()
255
- - **type**: ()
256
- - **confidence**: (0-100)
257
- - **reasoning**: (200,)
258
- - **location**: (,null)
259
- - **vendor**: ("unknown")
260
- - **suggestions**: (,2-3)
261
-
262
- ###
263
-
264
- **1: **
265
- \`\`\`json
266
- {
267
- "detected": true,
268
- "type": "slider",
269
- "confidence": 95,
270
- "reasoning": ":1) ;2) '';3) DOM.geetest_slider。。",
271
- "location": {
272
- "x": 450,
273
- "y": 300,
274
- "width": 320,
275
- "height": 180
276
- },
277
- "vendor": "geetest",
278
- "suggestions": [
279
- "",
280
- "captcha_wait",
281
- ","
282
- ]
283
- }
284
- \`\`\`
285
-
286
- **2: - **
287
- \`\`\`json
288
- {
289
- "detected": false,
290
- "type": "none",
291
- "confidence": 95,
292
- "reasoning": "'''',,。,。",
293
- "location": null,
294
- "vendor": "unknown",
295
- "suggestions": [
296
- ",",
297
- ""
298
- ]
299
- }
300
- \`\`\`
301
-
302
- **3: **
303
- \`\`\`json
304
- {
305
- "detected": false,
306
- "type": "none",
307
- "confidence": 98,
308
- "reasoning": ",、。,suspiciousElements,URL。",
309
- "location": null,
310
- "vendor": "unknown",
311
- "suggestions": [
312
- ",",
313
- ""
314
- ]
315
- }
316
- \`\`\`
317
-
318
- ##
319
-
320
- 1. ****: \`detected: false\`,
321
- 2. ****: > DOM >
322
- 3. ****: URL、、DOM、
323
- 4. ****: reasoning
324
- 5. ****: suggestions
325
-
146
+
147
+ ##
148
+
149
+ ### 1. (Interactive CAPTCHA)
150
+ **1.1 (Slider CAPTCHA)**
151
+ - ****: 、
152
+ - ****: (Geetest)、、、
153
+ - ****: 、、""
154
+ - **DOM**: \`.geetest_slider\`, \`.nc_1_wrapper\`, \`.tcaptcha-transform\`
155
+
156
+ **1.2 (Image CAPTCHA)**
157
+ - ****: ("")
158
+ - ****: reCAPTCHA v2、hCaptcha
159
+ - ****: 3x34x4、
160
+ - **DOM**: \`iframe[src*="recaptcha"]\`, \`.h-captcha\`
161
+
162
+ **1.3 (Text CAPTCHA)**
163
+ - ****: /
164
+ - ****: 、
165
+ - ****: ""
166
+
167
+ ### 2. (Automatic CAPTCHA)
168
+ **2.1 reCAPTCHA v3**
169
+ - ****: ,reCAPTCHA
170
+ - ****: "Protected by reCAPTCHA"
171
+
172
+ **2.2 Cloudflare Turnstile**
173
+ - ****: "" / "Checking your browser"
174
+ - ****: Cloudflare logo、、Ray ID
175
+
176
+ ### 3. (False Positives - )
177
+ **3.1 **
178
+ - 、、
179
+ - ""()
180
+ - ""()
181
+
182
+ **3.2 **
183
+ - 、
184
+ - 、
185
+ -
186
+
187
+ **3.3 UI**
188
+ - Range slider、Progress bar
189
+ - Carousel、Swiper
190
+ - 、
191
+
192
+ ##
193
+
194
+ ### Step 1:
195
+ 1. :
196
+ - +
197
+ - +
198
+ - ""
199
+ - ""
200
+ - Cloudflare/reCAPTCHA logo
201
+
202
+ ### Step 2:
203
+ 1. URL:
204
+ - \`/captcha\`, \`/challenge\`, \`/verify\`
205
+ - \`cdn-cgi/challenge\` (Cloudflare)
206
+ - \`recaptcha.net\`, \`hcaptcha.com\`
207
+
208
+ 2. :
209
+ - ""、""、""
210
+ - "Verify", "Challenge", "Security Check"
211
+
212
+ 3. :
213
+ - suspiciousElements →
214
+ - →
215
+
216
+ ### Step 3:
217
+ 1. :
218
+ - ""、""
219
+ - ,
220
+ - → \`detected: false\`
221
+
222
+ 2. /UI:
223
+ - 、、
224
+ - → \`detected: false\`
225
+
226
+ ### Step 4:
227
+ - **90-100%**: + DOM
228
+ - **70-89%**: ,DOM
229
+ - **50-69%**: ,
230
+ - **0-49%**:
231
+
232
+ ##
233
+
234
+ **JSON Schema**:
235
+
236
+ \`\`\`json
237
+ {
238
+ "detected": boolean,
239
+ "type": "slider" | "image" | "recaptcha" | "hcaptcha" | "cloudflare" | "text_input" | "none",
240
+ "confidence": number,
241
+ "reasoning": string,
242
+ "location": {
243
+ "x": number,
244
+ "y": number,
245
+ "width": number,
246
+ "height": number
247
+ } | null,
248
+ "vendor": "geetest" | "tencent" | "aliyun" | "recaptcha" | "hcaptcha" | "cloudflare" | "unknown",
249
+ "suggestions": string[]
250
+ }
251
+ \`\`\`
252
+
253
+ ###
254
+ - **detected**: ()
255
+ - **type**: ()
256
+ - **confidence**: (0-100)
257
+ - **reasoning**: (200,)
258
+ - **location**: (,null)
259
+ - **vendor**: ("unknown")
260
+ - **suggestions**: (,2-3)
261
+
262
+ ###
263
+
264
+ **1: **
265
+ \`\`\`json
266
+ {
267
+ "detected": true,
268
+ "type": "slider",
269
+ "confidence": 95,
270
+ "reasoning": ":1) ;2) '';3) DOM.geetest_slider。。",
271
+ "location": {
272
+ "x": 450,
273
+ "y": 300,
274
+ "width": 320,
275
+ "height": 180
276
+ },
277
+ "vendor": "geetest",
278
+ "suggestions": [
279
+ "",
280
+ "captcha_wait",
281
+ ","
282
+ ]
283
+ }
284
+ \`\`\`
285
+
286
+ **2: - **
287
+ \`\`\`json
288
+ {
289
+ "detected": false,
290
+ "type": "none",
291
+ "confidence": 95,
292
+ "reasoning": "'''',,。,。",
293
+ "location": null,
294
+ "vendor": "unknown",
295
+ "suggestions": [
296
+ ",",
297
+ ""
298
+ ]
299
+ }
300
+ \`\`\`
301
+
302
+ **3: **
303
+ \`\`\`json
304
+ {
305
+ "detected": false,
306
+ "type": "none",
307
+ "confidence": 98,
308
+ "reasoning": ",、。,suspiciousElements,URL。",
309
+ "location": null,
310
+ "vendor": "unknown",
311
+ "suggestions": [
312
+ ",",
313
+ ""
314
+ ]
315
+ }
316
+ \`\`\`
317
+
318
+ ##
319
+
320
+ 1. ****: \`detected: false\`,
321
+ 2. ****: > DOM >
322
+ 3. ****: URL、、DOM、
323
+ 4. ****: reasoning
324
+ 5. ****: suggestions
325
+
326
326
  ,JSON。`;
327
327
  }
328
328
  parseAIResponse(response, screenshotPath) {
@@ -7,7 +7,11 @@ type SavedDebuggerSessionSummary = {
7
7
  };
8
8
  export declare class DebuggerSessionManager {
9
9
  private debuggerManager;
10
+ private readonly SESSION_IMPORT_BATCH_SIZE;
11
+ private readonly SESSION_FILE_READ_BATCH_SIZE;
10
12
  constructor(debuggerManager: DebuggerManager);
13
+ private processInBatches;
14
+ private readSessionFile;
11
15
  private validateFilePath;
12
16
  exportSession(metadata?: DebuggerSession['metadata']): DebuggerSession;
13
17
  saveSession(filePath?: string, metadata?: DebuggerSession['metadata']): Promise<string>;
@@ -4,9 +4,20 @@ import * as path from 'path';
4
4
  import { logger } from '../../utils/logger.js';
5
5
  export class DebuggerSessionManager {
6
6
  debuggerManager;
7
+ SESSION_IMPORT_BATCH_SIZE = 8;
8
+ SESSION_FILE_READ_BATCH_SIZE = 8;
7
9
  constructor(debuggerManager) {
8
10
  this.debuggerManager = debuggerManager;
9
11
  }
12
+ async processInBatches(items, batchSize, worker) {
13
+ for (let i = 0; i < items.length; i += batchSize) {
14
+ const batch = items.slice(i, i + batchSize);
15
+ await Promise.all(batch.map((item) => worker(item)));
16
+ }
17
+ }
18
+ async readSessionFile(filePath) {
19
+ return fs.readFile(filePath, 'utf-8');
20
+ }
10
21
  async validateFilePath(filePath) {
11
22
  const resolved = path.resolve(filePath);
12
23
  const cwd = await fs.realpath(process.cwd());
@@ -70,7 +81,7 @@ export class DebuggerSessionManager {
70
81
  }
71
82
  async loadSessionFromFile(filePath) {
72
83
  const resolvedPath = await this.validateFilePath(filePath);
73
- const content = await fs.readFile(resolvedPath, 'utf-8');
84
+ const content = await this.readSessionFile(resolvedPath);
74
85
  const session = JSON.parse(content);
75
86
  await this.importSession(session);
76
87
  logger.info(`Session loaded from ${resolvedPath}`, {
@@ -93,7 +104,7 @@ export class DebuggerSessionManager {
93
104
  await this.debuggerManager.clearAllBreakpoints();
94
105
  let successCount = 0;
95
106
  let failCount = 0;
96
- for (const bp of session.breakpoints) {
107
+ await this.processInBatches(session.breakpoints, this.SESSION_IMPORT_BATCH_SIZE, async (bp) => {
97
108
  try {
98
109
  if (bp.location.url) {
99
110
  await this.debuggerManager.setBreakpointByUrl({
@@ -122,7 +133,7 @@ export class DebuggerSessionManager {
122
133
  logger.error('Failed to restore breakpoint:', error, bp);
123
134
  failCount++;
124
135
  }
125
- }
136
+ });
126
137
  if (session.pauseOnExceptions) {
127
138
  await this.debuggerManager.setPauseOnExceptions(session.pauseOnExceptions);
128
139
  }
@@ -143,23 +154,22 @@ export class DebuggerSessionManager {
143
154
  }
144
155
  const files = await fs.readdir(sessionsDir);
145
156
  const sessions = [];
146
- for (const file of files) {
147
- if (file.endsWith('.json')) {
148
- const filePath = path.join(sessionsDir, file);
149
- try {
150
- const content = await fs.readFile(filePath, 'utf-8');
151
- const session = JSON.parse(content);
152
- sessions.push({
153
- path: filePath,
154
- timestamp: session.timestamp,
155
- metadata: session.metadata,
156
- });
157
- }
158
- catch (error) {
159
- logger.warn(`Failed to read session file ${file}:`, error);
160
- }
157
+ const sessionFiles = files.filter((file) => file.endsWith('.json'));
158
+ await this.processInBatches(sessionFiles, this.SESSION_FILE_READ_BATCH_SIZE, async (file) => {
159
+ const filePath = path.join(sessionsDir, file);
160
+ try {
161
+ const content = await this.readSessionFile(filePath);
162
+ const session = JSON.parse(content);
163
+ sessions.push({
164
+ path: filePath,
165
+ timestamp: session.timestamp,
166
+ metadata: session.metadata,
167
+ });
161
168
  }
162
- }
169
+ catch (error) {
170
+ logger.warn(`Failed to read session file ${file}:`, error);
171
+ }
172
+ });
163
173
  sessions.sort((a, b) => b.timestamp - a.timestamp);
164
174
  return sessions;
165
175
  }
@@ -12,6 +12,9 @@ export interface ScriptInfo {
12
12
  }
13
13
  export declare class ScriptManager {
14
14
  private collector;
15
+ private static readonly SOURCE_LOAD_BATCH_SIZE;
16
+ private static readonly SEARCH_LINE_YIELD_INTERVAL;
17
+ private static readonly SEARCH_SCRIPT_YIELD_INTERVAL;
15
18
  private cdpSession;
16
19
  private scripts;
17
20
  private scriptsByUrl;
@@ -23,6 +26,7 @@ export declare class ScriptManager {
23
26
  constructor(collector: CodeCollector);
24
27
  init(): Promise<void>;
25
28
  private doInit;
29
+ private loadScriptSourceInternal;
26
30
  enable(): Promise<void>;
27
31
  getAllScripts(includeSource?: boolean, maxScripts?: number): Promise<ScriptInfo[]>;
28
32
  getScriptSource(scriptId?: string, url?: string): Promise<ScriptInfo | null>;
@@ -1,7 +1,11 @@
1
+ import { setImmediate as waitForImmediate } from 'node:timers/promises';
1
2
  import { logger } from '../../utils/logger.js';
2
3
  import { extractFunctionTreeCore, } from '../debugger/ScriptManager.impl.extract-function-tree.js';
3
4
  export class ScriptManager {
4
5
  collector;
6
+ static SOURCE_LOAD_BATCH_SIZE = 8;
7
+ static SEARCH_LINE_YIELD_INTERVAL = 250;
8
+ static SEARCH_SCRIPT_YIELD_INTERVAL = 10;
5
9
  cdpSession = null;
6
10
  scripts = new Map();
7
11
  scriptsByUrl = new Map();
@@ -54,6 +58,25 @@ export class ScriptManager {
54
58
  this.initialized = true;
55
59
  logger.info('ScriptManager initialized');
56
60
  }
61
+ async loadScriptSourceInternal(script) {
62
+ if (script.source) {
63
+ return true;
64
+ }
65
+ try {
66
+ const { scriptSource } = await this.cdpSession.send('Debugger.getScriptSource', {
67
+ scriptId: script.scriptId,
68
+ });
69
+ script.source = scriptSource;
70
+ script.sourceLength = scriptSource.length;
71
+ this.buildKeywordIndex(script.scriptId, script.url, scriptSource);
72
+ this.chunkScript(script.scriptId, scriptSource);
73
+ return true;
74
+ }
75
+ catch (error) {
76
+ logger.warn(`Failed to get source for script ${script.scriptId}:`, error);
77
+ return false;
78
+ }
79
+ }
57
80
  async enable() {
58
81
  return this.init();
59
82
  }
@@ -70,23 +93,27 @@ export class ScriptManager {
70
93
  logger.warn(`Loading source code for ${limitedScripts.length} scripts. This may use significant memory.`);
71
94
  let loadedCount = 0;
72
95
  let failedCount = 0;
73
- for (const script of limitedScripts) {
74
- if (!script.source) {
75
- try {
76
- const { scriptSource } = await this.cdpSession.send('Debugger.getScriptSource', {
77
- scriptId: script.scriptId,
78
- });
79
- script.source = scriptSource;
96
+ const missingScripts = limitedScripts.filter((script) => !script.source);
97
+ for (let batchStart = 0; batchStart < missingScripts.length; batchStart += ScriptManager.SOURCE_LOAD_BATCH_SIZE) {
98
+ const batch = missingScripts.slice(batchStart, batchStart + ScriptManager.SOURCE_LOAD_BATCH_SIZE);
99
+ const settled = await Promise.allSettled(batch.map(async (script) => {
100
+ const loaded = await this.loadScriptSourceInternal(script);
101
+ if (loaded) {
80
102
  loadedCount++;
81
103
  if (loadedCount % 10 === 0) {
82
104
  logger.debug(`Loaded ${loadedCount}/${limitedScripts.length} scripts...`);
83
105
  }
84
106
  }
85
- catch (error) {
86
- logger.warn(`Failed to get source for script ${script.scriptId}:`, error);
107
+ else {
108
+ failedCount++;
109
+ }
110
+ }));
111
+ for (const result of settled) {
112
+ if (result.status === 'rejected') {
87
113
  failedCount++;
88
114
  }
89
115
  }
116
+ await waitForImmediate();
90
117
  }
91
118
  logger.info(`getAllScripts: ${limitedScripts.length} scripts (loaded: ${loadedCount}, failed: ${failedCount})`);
92
119
  }
@@ -121,17 +148,9 @@ export class ScriptManager {
121
148
  return null;
122
149
  }
123
150
  if (!targetScript.source) {
124
- try {
125
- const { scriptSource } = await this.cdpSession.send('Debugger.getScriptSource', {
126
- scriptId: targetScript.scriptId,
127
- });
128
- targetScript.source = scriptSource;
129
- targetScript.sourceLength = scriptSource.length;
130
- this.buildKeywordIndex(targetScript.scriptId, targetScript.url, scriptSource);
131
- this.chunkScript(targetScript.scriptId, scriptSource);
132
- }
133
- catch (error) {
134
- logger.error(`Failed to get script source for ${targetScript.scriptId}:`, error);
151
+ const loaded = await this.loadScriptSourceInternal(targetScript);
152
+ if (!loaded) {
153
+ logger.error(`Failed to get script source for ${targetScript.scriptId}`);
135
154
  return null;
136
155
  }
137
156
  }
@@ -166,7 +185,7 @@ export class ScriptManager {
166
185
  : new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), caseSensitive ? 'g' : 'gi');
167
186
  const matches = [];
168
187
  const scripts = await this.getAllScripts(true, 500);
169
- for (const script of scripts) {
188
+ for (const [scriptIndex, script] of scripts.entries()) {
170
189
  if (!script.source)
171
190
  continue;
172
191
  if (matches.length >= maxMatches)
@@ -193,6 +212,12 @@ export class ScriptManager {
193
212
  context,
194
213
  });
195
214
  }
215
+ if ((i + 1) % ScriptManager.SEARCH_LINE_YIELD_INTERVAL === 0) {
216
+ await waitForImmediate();
217
+ }
218
+ }
219
+ if ((scriptIndex + 1) % ScriptManager.SEARCH_SCRIPT_YIELD_INTERVAL === 0) {
220
+ await waitForImmediate();
196
221
  }
197
222
  }
198
223
  logger.info(`searchInScripts: "${keyword}" - found ${matches.length} matches`);
@@ -12,8 +12,8 @@ export class EnvironmentEmulator {
12
12
  llm;
13
13
  constructor(llm) {
14
14
  this.llm = llm;
15
- if (llm) {
16
- logger.info('LLM service unavailable, skipping AI environment analysis');
15
+ if (!llm) {
16
+ logger.debug('LLM service unavailable, skipping AI environment analysis');
17
17
  }
18
18
  }
19
19
  async analyze(options) {
@@ -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;