monomind 1.10.39 → 1.10.41

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 (27) hide show
  1. package/package.json +1 -1
  2. package/packages/@monomind/cli/dist/src/browser/actions.js +114 -22
  3. package/packages/@monomind/cli/dist/src/browser/batch.d.ts +0 -2
  4. package/packages/@monomind/cli/dist/src/browser/batch.js +1 -10
  5. package/packages/@monomind/cli/dist/src/browser/browser.d.ts +1 -0
  6. package/packages/@monomind/cli/dist/src/browser/browser.js +51 -24
  7. package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -4
  8. package/packages/@monomind/cli/dist/src/browser/console-log.d.ts +1 -0
  9. package/packages/@monomind/cli/dist/src/browser/console-log.js +19 -3
  10. package/packages/@monomind/cli/dist/src/browser/dialog.js +5 -2
  11. package/packages/@monomind/cli/dist/src/browser/find.js +28 -8
  12. package/packages/@monomind/cli/dist/src/browser/har.d.ts +1 -0
  13. package/packages/@monomind/cli/dist/src/browser/har.js +7 -5
  14. package/packages/@monomind/cli/dist/src/browser/network.d.ts +7 -4
  15. package/packages/@monomind/cli/dist/src/browser/network.js +60 -23
  16. package/packages/@monomind/cli/dist/src/browser/profiler.js +13 -2
  17. package/packages/@monomind/cli/dist/src/browser/screenshot.js +4 -2
  18. package/packages/@monomind/cli/dist/src/browser/session.js +49 -12
  19. package/packages/@monomind/cli/dist/src/browser/snapshot.js +26 -14
  20. package/packages/@monomind/cli/dist/src/browser/storage.d.ts +1 -0
  21. package/packages/@monomind/cli/dist/src/browser/storage.js +3 -0
  22. package/packages/@monomind/cli/dist/src/browser/tabs.d.ts +3 -3
  23. package/packages/@monomind/cli/dist/src/browser/tabs.js +13 -13
  24. package/packages/@monomind/cli/dist/src/browser/trace.js +10 -4
  25. package/packages/@monomind/cli/dist/src/browser/wait.js +24 -14
  26. package/packages/@monomind/cli/dist/src/commands/browse.js +300 -34
  27. package/packages/@monomind/cli/package.json +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monomind",
3
- "version": "1.10.39",
3
+ "version": "1.10.41",
4
4
  "description": "Monomind - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,3 +1,4 @@
1
+ import { resolve } from 'path';
1
2
  import { getObjectIdForRef, getElementBox } from './snapshot.js';
2
3
  export async function clickElement(client, sessionId, ref, options = {}) {
3
4
  const box = await getElementBox(client, sessionId, ref);
@@ -11,6 +12,7 @@ export async function clickElement(client, sessionId, ref, options = {}) {
11
12
  await client.send('Runtime.callFunctionOn', {
12
13
  functionDeclaration: 'function() { this.click(); }',
13
14
  objectId,
15
+ returnByValue: true,
14
16
  }, sessionId);
15
17
  return;
16
18
  }
@@ -21,10 +23,11 @@ export async function clickPoint(client, sessionId, x, y, options = {}) {
21
23
  const clickCount = options.clickCount ?? 1;
22
24
  const modifiers = options.modifiers ?? 0;
23
25
  const shared = { x, y, button, modifiers };
26
+ const buttonsMask = button === 'right' ? 2 : button === 'middle' ? 4 : 1;
24
27
  for (let i = 0; i < clickCount; i++) {
25
28
  const count = i + 1;
26
- await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mousePressed', buttons: 1, clickCount: count }, sessionId);
27
- await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mouseReleased', buttons: 0, clickCount: count }, sessionId);
29
+ await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mousePressed', buttons: buttonsMask, clickCount: count }, sessionId);
30
+ await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mouseReleased', buttons: i < clickCount - 1 ? buttonsMask : 0, clickCount: count }, sessionId);
28
31
  }
29
32
  }
30
33
  export async function fillElement(client, sessionId, ref, value) {
@@ -36,7 +39,7 @@ export async function fillElement(client, sessionId, ref, value) {
36
39
  // Select all and replace
37
40
  const objectId = await getObjectIdForRef(client, sessionId, ref);
38
41
  if (objectId) {
39
- await client.send('Runtime.callFunctionOn', {
42
+ const fillSelectResult = await client.send('Runtime.callFunctionOn', {
40
43
  functionDeclaration: `function() {
41
44
  this.focus();
42
45
  if (this.tagName === 'INPUT' || this.tagName === 'TEXTAREA') {
@@ -49,8 +52,19 @@ export async function fillElement(client, sessionId, ref, value) {
49
52
  }
50
53
  }`,
51
54
  objectId,
55
+ returnByValue: true,
52
56
  }, sessionId);
57
+ if (fillSelectResult.exceptionDetails) {
58
+ throw new Error(`fillElement select-all failed: ${fillSelectResult.exceptionDetails.exception?.description ?? fillSelectResult.exceptionDetails.text}`);
59
+ }
60
+ }
61
+ else if (box) {
62
+ // Fallback for elements without a resolvable objectId: keyboard select-all clears existing content
63
+ const mod = process.platform === 'darwin' ? 4 : 2;
64
+ await pressKeyCombo(client, sessionId, 'a', mod);
53
65
  }
66
+ if (!objectId && !box)
67
+ throw new Error(`Cannot fill ref @${ref.ref}: element not found in DOM`);
54
68
  // Type the value character by character for natural input
55
69
  await typeText(client, sessionId, value);
56
70
  }
@@ -64,15 +78,16 @@ export async function pressKeyCombo(client, sessionId, key, modifiers) {
64
78
  await client.send('Input.dispatchKeyEvent', { type: 'keyUp', ...keyInfo, modifiers }, sessionId);
65
79
  }
66
80
  export async function pressKey(client, sessionId, key) {
67
- const keyInfo = resolveKey(key);
81
+ const { text, ...keyInfo } = resolveKey(key);
82
+ // rawKeyDown does not insert text; the explicit char event handles insertion
68
83
  await client.send('Input.dispatchKeyEvent', {
69
- type: 'keyDown',
84
+ type: 'rawKeyDown',
70
85
  ...keyInfo,
71
86
  }, sessionId);
72
- if (keyInfo.text) {
87
+ if (text) {
73
88
  await client.send('Input.dispatchKeyEvent', {
74
89
  type: 'char',
75
- text: keyInfo.text,
90
+ text,
76
91
  }, sessionId);
77
92
  }
78
93
  await client.send('Input.dispatchKeyEvent', {
@@ -97,12 +112,74 @@ function resolveKey(key) {
97
112
  PageDown: { key: 'PageDown', code: 'PageDown', windowsVirtualKeyCode: 34 },
98
113
  Space: { key: ' ', code: 'Space', text: ' ', windowsVirtualKeyCode: 32 },
99
114
  ' ': { key: ' ', code: 'Space', text: ' ', windowsVirtualKeyCode: 32 },
115
+ Shift: { key: 'Shift', code: 'ShiftLeft', windowsVirtualKeyCode: 16 },
116
+ Control: { key: 'Control', code: 'ControlLeft', windowsVirtualKeyCode: 17 },
117
+ Alt: { key: 'Alt', code: 'AltLeft', windowsVirtualKeyCode: 18 },
118
+ Meta: { key: 'Meta', code: 'MetaLeft', windowsVirtualKeyCode: 91 },
119
+ F1: { key: 'F1', code: 'F1', windowsVirtualKeyCode: 112 },
120
+ F2: { key: 'F2', code: 'F2', windowsVirtualKeyCode: 113 },
121
+ F3: { key: 'F3', code: 'F3', windowsVirtualKeyCode: 114 },
122
+ F4: { key: 'F4', code: 'F4', windowsVirtualKeyCode: 115 },
123
+ F5: { key: 'F5', code: 'F5', windowsVirtualKeyCode: 116 },
124
+ F6: { key: 'F6', code: 'F6', windowsVirtualKeyCode: 117 },
125
+ F7: { key: 'F7', code: 'F7', windowsVirtualKeyCode: 118 },
126
+ F8: { key: 'F8', code: 'F8', windowsVirtualKeyCode: 119 },
127
+ F9: { key: 'F9', code: 'F9', windowsVirtualKeyCode: 120 },
128
+ F10: { key: 'F10', code: 'F10', windowsVirtualKeyCode: 121 },
129
+ F11: { key: 'F11', code: 'F11', windowsVirtualKeyCode: 122 },
130
+ F12: { key: 'F12', code: 'F12', windowsVirtualKeyCode: 123 },
100
131
  };
101
132
  if (keyMap[key])
102
133
  return keyMap[key];
103
134
  // Single printable character — windowsVirtualKeyCode required for rawKeyDown shortcut dispatch
104
135
  if (key.length === 1) {
105
- return { key, code: `Key${key.toUpperCase()}`, text: key, windowsVirtualKeyCode: key.toUpperCase().charCodeAt(0) };
136
+ const charCode = key.charCodeAt(0);
137
+ if (charCode >= 48 && charCode <= 57) {
138
+ return { key, code: `Digit${key}`, text: key, windowsVirtualKeyCode: charCode };
139
+ }
140
+ if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122)) {
141
+ return { key, code: `Key${key.toUpperCase()}`, text: key, windowsVirtualKeyCode: key.toUpperCase().charCodeAt(0) };
142
+ }
143
+ // Symbol keys: map to the physical key's code and Windows virtual key code
144
+ const symbolMap = {
145
+ '!': { code: 'Digit1', windowsVirtualKeyCode: 49 },
146
+ '@': { code: 'Digit2', windowsVirtualKeyCode: 50 },
147
+ '#': { code: 'Digit3', windowsVirtualKeyCode: 51 },
148
+ '$': { code: 'Digit4', windowsVirtualKeyCode: 52 },
149
+ '%': { code: 'Digit5', windowsVirtualKeyCode: 53 },
150
+ '^': { code: 'Digit6', windowsVirtualKeyCode: 54 },
151
+ '&': { code: 'Digit7', windowsVirtualKeyCode: 55 },
152
+ '*': { code: 'Digit8', windowsVirtualKeyCode: 56 },
153
+ '(': { code: 'Digit9', windowsVirtualKeyCode: 57 },
154
+ ')': { code: 'Digit0', windowsVirtualKeyCode: 48 },
155
+ '-': { code: 'Minus', windowsVirtualKeyCode: 189 },
156
+ '_': { code: 'Minus', windowsVirtualKeyCode: 189 },
157
+ '=': { code: 'Equal', windowsVirtualKeyCode: 187 },
158
+ '+': { code: 'Equal', windowsVirtualKeyCode: 187 },
159
+ '[': { code: 'BracketLeft', windowsVirtualKeyCode: 219 },
160
+ '{': { code: 'BracketLeft', windowsVirtualKeyCode: 219 },
161
+ ']': { code: 'BracketRight', windowsVirtualKeyCode: 221 },
162
+ '}': { code: 'BracketRight', windowsVirtualKeyCode: 221 },
163
+ '\\': { code: 'Backslash', windowsVirtualKeyCode: 220 },
164
+ '|': { code: 'Backslash', windowsVirtualKeyCode: 220 },
165
+ ';': { code: 'Semicolon', windowsVirtualKeyCode: 186 },
166
+ ':': { code: 'Semicolon', windowsVirtualKeyCode: 186 },
167
+ "'": { code: 'Quote', windowsVirtualKeyCode: 222 },
168
+ '"': { code: 'Quote', windowsVirtualKeyCode: 222 },
169
+ '`': { code: 'Backquote', windowsVirtualKeyCode: 192 },
170
+ '~': { code: 'Backquote', windowsVirtualKeyCode: 192 },
171
+ ',': { code: 'Comma', windowsVirtualKeyCode: 188 },
172
+ '<': { code: 'Comma', windowsVirtualKeyCode: 188 },
173
+ '.': { code: 'Period', windowsVirtualKeyCode: 190 },
174
+ '>': { code: 'Period', windowsVirtualKeyCode: 190 },
175
+ '/': { code: 'Slash', windowsVirtualKeyCode: 191 },
176
+ '?': { code: 'Slash', windowsVirtualKeyCode: 191 },
177
+ };
178
+ const sym = symbolMap[key];
179
+ if (sym)
180
+ return { key, code: sym.code, text: key, windowsVirtualKeyCode: sym.windowsVirtualKeyCode };
181
+ // Unknown/non-ASCII character — omit code since no valid DOM KeyboardEvent.code exists
182
+ return { key, code: '', text: key };
106
183
  }
107
184
  return { key, code: key };
108
185
  }
@@ -113,10 +190,10 @@ export async function scrollElement(client, sessionId, direction, amount = 300,
113
190
  let deltaY = 0;
114
191
  if (ref) {
115
192
  const box = await getElementBox(client, sessionId, ref);
116
- if (box) {
117
- x = box.x;
118
- y = box.y;
119
- }
193
+ if (!box)
194
+ throw new Error(`Cannot scroll: ref @${ref.ref} not found in DOM`);
195
+ x = box.x;
196
+ y = box.y;
120
197
  }
121
198
  else {
122
199
  // Center of viewport
@@ -164,7 +241,7 @@ export async function selectOption(client, sessionId, ref, value) {
164
241
  const objectId = await getObjectIdForRef(client, sessionId, ref);
165
242
  if (!objectId)
166
243
  throw new Error(`Cannot select: ref @${ref.ref} not found in DOM`);
167
- await client.send('Runtime.callFunctionOn', {
244
+ const selectResult = await client.send('Runtime.callFunctionOn', {
168
245
  functionDeclaration: `function(value) {
169
246
  if (this.tagName !== 'SELECT') throw new Error('Not a select element');
170
247
  for (const opt of this.options) {
@@ -178,13 +255,17 @@ export async function selectOption(client, sessionId, ref, value) {
178
255
  }`,
179
256
  objectId,
180
257
  arguments: [{ value }],
258
+ returnByValue: true,
181
259
  }, sessionId);
260
+ if (selectResult.exceptionDetails) {
261
+ throw new Error(`selectOption failed: ${selectResult.exceptionDetails.exception?.description ?? selectResult.exceptionDetails.text}`);
262
+ }
182
263
  }
183
264
  export async function checkElement(client, sessionId, ref, checked = true) {
184
265
  const objectId = await getObjectIdForRef(client, sessionId, ref);
185
266
  if (!objectId)
186
267
  throw new Error(`Cannot check: ref @${ref.ref} not found in DOM`);
187
- await client.send('Runtime.callFunctionOn', {
268
+ const checkResult = await client.send('Runtime.callFunctionOn', {
188
269
  functionDeclaration: `function(checked) {
189
270
  if (this.checked !== checked) {
190
271
  this.click();
@@ -192,7 +273,11 @@ export async function checkElement(client, sessionId, ref, checked = true) {
192
273
  }`,
193
274
  objectId,
194
275
  arguments: [{ value: checked }],
276
+ returnByValue: true,
195
277
  }, sessionId);
278
+ if (checkResult.exceptionDetails) {
279
+ throw new Error(`checkElement failed: ${checkResult.exceptionDetails.exception?.description ?? checkResult.exceptionDetails.text}`);
280
+ }
196
281
  }
197
282
  export async function focusElement(client, sessionId, ref) {
198
283
  const objectId = await getObjectIdForRef(client, sessionId, ref);
@@ -200,30 +285,36 @@ export async function focusElement(client, sessionId, ref) {
200
285
  await client.send('Runtime.callFunctionOn', {
201
286
  functionDeclaration: 'function() { this.focus(); }',
202
287
  objectId,
288
+ returnByValue: true,
203
289
  }, sessionId);
204
290
  return;
205
291
  }
206
292
  const box = await getElementBox(client, sessionId, ref);
207
- if (box)
293
+ if (box) {
208
294
  await clickPoint(client, sessionId, box.x, box.y);
295
+ }
296
+ else {
297
+ throw new Error(`Cannot focus ref @${ref.ref}: element not found in DOM`);
298
+ }
209
299
  }
210
300
  export async function typeIntoElement(client, sessionId, ref, text) {
211
301
  await focusElement(client, sessionId, ref);
212
302
  await typeText(client, sessionId, text);
213
303
  }
214
304
  export async function keyDown(client, sessionId, key) {
215
- const keyInfo = resolveKey(key);
216
- await client.send('Input.dispatchKeyEvent', { type: 'keyDown', ...keyInfo }, sessionId);
305
+ const { text: _text, ...keyInfo } = resolveKey(key);
306
+ await client.send('Input.dispatchKeyEvent', { type: 'rawKeyDown', ...keyInfo }, sessionId);
217
307
  }
218
308
  export async function keyUp(client, sessionId, key) {
219
- const keyInfo = resolveKey(key);
309
+ const { text: _text, ...keyInfo } = resolveKey(key);
220
310
  await client.send('Input.dispatchKeyEvent', { type: 'keyUp', ...keyInfo }, sessionId);
221
311
  }
222
312
  export async function mouseMove(client, sessionId, x, y) {
223
313
  await client.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }, sessionId);
224
314
  }
225
315
  export async function mouseDown(client, sessionId, x, y, button = 'left') {
226
- await client.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button, buttons: 1, clickCount: 1 }, sessionId);
316
+ const buttonsMask = button === 'right' ? 2 : button === 'middle' ? 4 : 1;
317
+ await client.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button, buttons: buttonsMask, clickCount: 1 }, sessionId);
227
318
  }
228
319
  export async function mouseUp(client, sessionId, x, y, button = 'left') {
229
320
  await client.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button, buttons: 0, clickCount: 1 }, sessionId);
@@ -251,7 +342,7 @@ export async function uploadFile(client, sessionId, ref, filePaths) {
251
342
  if (!ref.backendDOMNodeId)
252
343
  throw new Error(`Cannot upload: ref @${ref.ref} has no DOM node`);
253
344
  await client.send('DOM.setFileInputFiles', {
254
- files: filePaths,
345
+ files: filePaths.map((f) => resolve(f)),
255
346
  backendNodeId: ref.backendDOMNodeId,
256
347
  }, sessionId);
257
348
  }
@@ -268,7 +359,8 @@ export async function pushState(client, sessionId, url) {
268
359
  (function() {
269
360
  const url = ${JSON.stringify(url)};
270
361
  if (window.next && window.next.router) {
271
- window.next.router.push(url);
362
+ window.next.router.push(url).catch(function() {});
363
+ return;
272
364
  } else {
273
365
  history.pushState({}, '', url);
274
366
  window.dispatchEvent(new PopStateEvent('popstate'));
@@ -292,7 +384,7 @@ export async function evaluateJs(client, sessionId, expression) {
292
384
  awaitPromise: true,
293
385
  }, sessionId);
294
386
  if (result.exceptionDetails) {
295
- throw new Error(`JS evaluation error: ${result.exceptionDetails.text}`);
387
+ throw new Error(`JS evaluation error: ${result.exceptionDetails.exception?.description ?? result.exceptionDetails.text}`);
296
388
  }
297
389
  return result.result?.value;
298
390
  }
@@ -2,6 +2,4 @@ export interface BatchCommand {
2
2
  command: string;
3
3
  args: string[];
4
4
  }
5
- export declare function parseBatchCommands(input: string[]): Promise<BatchCommand[]>;
6
- export declare function parseBatchJson(json: string): Promise<BatchCommand[]>;
7
5
  //# sourceMappingURL=batch.d.ts.map
@@ -1,11 +1,2 @@
1
- export async function parseBatchCommands(input) {
2
- return input.map((raw) => {
3
- const parts = raw.trim().split(/\s+/);
4
- return { command: parts[0], args: parts.slice(1) };
5
- });
6
- }
7
- export async function parseBatchJson(json) {
8
- const parsed = JSON.parse(json);
9
- return parsed.map(([command, ...args]) => ({ command, args }));
10
- }
1
+ export {};
11
2
  //# sourceMappingURL=batch.js.map
@@ -2,6 +2,7 @@ import { CdpClient } from './cdp.js';
2
2
  import type { BrowserConfig, CdpTarget } from './types.js';
3
3
  export declare function isPortOpen(port: number): Promise<boolean>;
4
4
  export declare function launchBrowser(config?: BrowserConfig): Promise<number>;
5
+ export declare function enableSessionDomains(client: CdpClient, sessionId: string): Promise<void>;
5
6
  export declare function connectToTarget(port: number, targetId?: string): Promise<{
6
7
  client: CdpClient;
7
8
  target: CdpTarget;
@@ -83,6 +83,18 @@ export async function launchBrowser(config = {}) {
83
83
  }
84
84
  throw new Error(`Chrome failed to start on port ${port} within ${LAUNCH_TIMEOUT}ms`);
85
85
  }
86
+ export async function enableSessionDomains(client, sessionId) {
87
+ await Promise.all([
88
+ client.send('Page.enable', {}, sessionId),
89
+ client.send('Runtime.enable', {}, sessionId),
90
+ client.send('Network.enable', {}, sessionId),
91
+ client.send('DOM.enable', {}, sessionId),
92
+ client.send('Accessibility.enable', {}, sessionId),
93
+ ]);
94
+ setupConsoleCapture(client, sessionId);
95
+ await enableConsoleCapture(client, sessionId);
96
+ setupDialogAutoHandling(client, sessionId);
97
+ }
86
98
  export async function connectToTarget(port, targetId) {
87
99
  const targets = await fetchTargets(port);
88
100
  const pageTargets = targets.filter((t) => t.type === 'page');
@@ -102,34 +114,33 @@ export async function connectToTarget(port, targetId) {
102
114
  const wsUrl = target.webSocketDebuggerUrl ?? `ws://127.0.0.1:${port}/devtools/page/${target.id}`;
103
115
  const client = new CdpClient();
104
116
  await client.connect(wsUrl);
105
- // Enable required domains
106
117
  const { sessionId } = await client.send('Target.attachToTarget', {
107
118
  targetId: target.id,
108
119
  flatten: true,
109
120
  });
110
- await Promise.all([
111
- client.send('Page.enable', {}, sessionId),
112
- client.send('Runtime.enable', {}, sessionId),
113
- client.send('Network.enable', {}, sessionId),
114
- client.send('DOM.enable', {}, sessionId),
115
- client.send('Accessibility.enable', {}, sessionId),
116
- ]);
117
- // Wire up auto-capture listeners so console/errors/dialogs work immediately
118
- setupConsoleCapture(client, sessionId);
119
- await enableConsoleCapture(client, sessionId);
120
- setupDialogAutoHandling(client, sessionId);
121
+ await enableSessionDomains(client, sessionId);
121
122
  return { client, target, sessionId };
122
123
  }
123
124
  export async function openUrl(client, sessionId, url) {
124
125
  await client.send('Page.navigate', { url }, sessionId);
125
- await waitForLoad(client, sessionId, 'networkidle');
126
+ await waitForNetworkIdle(client, sessionId, 500, 30_000);
126
127
  }
127
128
  export async function waitForLoad(client, sessionId, condition = 'load', timeout = 30_000) {
128
129
  if (condition === 'load' || condition === 'domcontentloaded') {
130
+ // Guard against race where the page loads before the listener is registered
131
+ const readyExpr = condition === 'load' ? 'document.readyState === "complete"' : 'document.readyState !== "loading"';
132
+ const readyCheck = await client.send('Runtime.evaluate', {
133
+ expression: readyExpr, returnByValue: true,
134
+ }, sessionId).catch(() => ({ result: { value: false } }));
135
+ if (readyCheck.result?.value)
136
+ return;
129
137
  const event = condition === 'load' ? 'Page.loadEventFired' : 'Page.domContentEventFired';
130
138
  const [eventPromise, cancelOnce] = client.onceWithOff(event, sessionId);
131
139
  let timedOut = false;
132
- const timeoutPromise = sleep(timeout).then(() => { timedOut = true; });
140
+ let timeoutHandle;
141
+ const timeoutPromise = new Promise((resolve) => {
142
+ timeoutHandle = setTimeout(() => { timedOut = true; resolve(); }, timeout);
143
+ });
133
144
  try {
134
145
  await Promise.race([eventPromise, timeoutPromise]);
135
146
  if (timedOut)
@@ -137,6 +148,7 @@ export async function waitForLoad(client, sessionId, condition = 'load', timeout
137
148
  }
138
149
  finally {
139
150
  cancelOnce();
151
+ clearTimeout(timeoutHandle);
140
152
  }
141
153
  return;
142
154
  }
@@ -146,6 +158,7 @@ export async function waitForLoad(client, sessionId, condition = 'load', timeout
146
158
  async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
147
159
  return new Promise((resolve, reject) => {
148
160
  let pending = 0;
161
+ const inflight = new Set();
149
162
  let idleTimer = null;
150
163
  const killTimer = setTimeout(() => {
151
164
  cleanup();
@@ -160,6 +173,8 @@ async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
160
173
  offReq();
161
174
  offResp();
162
175
  offFail();
176
+ offCache();
177
+ offResp2();
163
178
  };
164
179
  const settle = () => {
165
180
  cleanup();
@@ -178,23 +193,35 @@ async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
178
193
  }
179
194
  }
180
195
  };
181
- const offReq = client.on('Network.requestWillBeSent', (_, sid) => {
182
- if (sid === sessionId) {
196
+ const offReq = client.on('Network.requestWillBeSent', (params, sid) => {
197
+ if (sid !== sessionId)
198
+ return;
199
+ const id = params.requestId;
200
+ if (!inflight.has(id)) {
201
+ inflight.add(id);
183
202
  pending++;
184
203
  check();
185
204
  }
186
205
  });
187
- const offResp = client.on('Network.loadingFinished', (_, sid) => {
188
- if (sid === sessionId) {
189
- pending = Math.max(0, pending - 1);
190
- check();
191
- }
192
- });
193
- const offFail = client.on('Network.loadingFailed', (_, sid) => {
194
- if (sid === sessionId) {
206
+ const decrement = (params, sid) => {
207
+ if (sid !== sessionId)
208
+ return;
209
+ const id = params.requestId;
210
+ if (inflight.delete(id)) {
195
211
  pending = Math.max(0, pending - 1);
196
212
  check();
197
213
  }
214
+ };
215
+ const offResp = client.on('Network.loadingFinished', decrement);
216
+ const offFail = client.on('Network.loadingFailed', decrement);
217
+ const offCache = client.on('Network.requestServedFromCache', decrement);
218
+ // Guard against requests that never fire loadingFinished/loadingFailed (e.g. data: URLs)
219
+ // Skip 3xx redirect responses — the request continues under the same requestId
220
+ const offResp2 = client.on('Network.responseReceived', (params, sid) => {
221
+ const p = params;
222
+ if (p.response.status >= 300 && p.response.status < 400)
223
+ return;
224
+ decrement(params, sid);
198
225
  });
199
226
  check();
200
227
  });
@@ -13,8 +13,16 @@ export class CdpClient {
13
13
  resolve();
14
14
  });
15
15
  this.ws.on('error', (err) => {
16
- if (!this.connected)
16
+ if (!this.connected) {
17
17
  reject(err);
18
+ }
19
+ else {
20
+ // Post-connect: flush all pending commands — 'close' may not fire on all platforms
21
+ this.connected = false;
22
+ for (const { reject: r } of this.pendingCommands.values())
23
+ r(err);
24
+ this.pendingCommands.clear();
25
+ }
18
26
  });
19
27
  this.ws.on('close', () => {
20
28
  this.connected = false;
@@ -68,7 +76,12 @@ export class CdpClient {
68
76
  resolve: (r) => resolve((r.result ?? {})),
69
77
  reject,
70
78
  });
71
- this.ws.send(JSON.stringify(cmd));
79
+ this.ws.send(JSON.stringify(cmd), (err) => {
80
+ if (err) {
81
+ this.pendingCommands.delete(id);
82
+ reject(err);
83
+ }
84
+ });
72
85
  });
73
86
  }
74
87
  on(event, fn) {
@@ -95,9 +108,13 @@ export class CdpClient {
95
108
  return [promise, () => off()];
96
109
  }
97
110
  close() {
111
+ this.connected = false;
112
+ for (const { reject: r } of this.pendingCommands.values())
113
+ r(new Error('CDP connection closed'));
114
+ this.pendingCommands.clear();
98
115
  this.ws?.close();
99
116
  this.ws = null;
100
- this.connected = false;
117
+ this.eventListeners.clear();
101
118
  }
102
119
  isConnected() {
103
120
  return this.connected;
@@ -110,7 +127,7 @@ export async function fetchTargets(port) {
110
127
  return res.json();
111
128
  }
112
129
  export async function fetchNewTarget(port, url) {
113
- const res = await fetch(`http://127.0.0.1:${port}/json/new?${encodeURIComponent(url)}`);
130
+ const res = await fetch(`http://127.0.0.1:${port}/json/new?${url}`);
114
131
  if (!res.ok)
115
132
  throw new Error(`Failed to create target: ${res.statusText}`);
116
133
  return res.json();
@@ -19,4 +19,5 @@ export declare function getConsoleMessages(sessionId?: string): ConsoleMessage[]
19
19
  export declare function clearConsoleMessages(sessionId?: string): void;
20
20
  export declare function getPageErrors(sessionId?: string): PageError[];
21
21
  export declare function clearPageErrors(sessionId?: string): void;
22
+ export declare function teardownConsoleCapture(sessionId: string): void;
22
23
  //# sourceMappingURL=console-log.d.ts.map
@@ -17,6 +17,7 @@ export function setupConsoleCapture(client, sessionId) {
17
17
  if (prevOffs) {
18
18
  for (const off of prevOffs)
19
19
  off();
20
+ _consoleListeners.delete(sessionId);
20
21
  }
21
22
  _consoleMessages.set(sessionId, []);
22
23
  _pageErrors.set(sessionId, []);
@@ -25,8 +26,9 @@ export function setupConsoleCapture(client, sessionId) {
25
26
  return;
26
27
  const args = params.args ?? [];
27
28
  const text = args.map((a) => a.description ?? String(a.value ?? '')).join(' ');
29
+ const rawType = params.type === 'warning' ? 'warn' : params.type;
28
30
  messagesFor(sessionId).push({
29
- type: params.type ?? 'log',
31
+ type: rawType ?? 'log',
30
32
  text,
31
33
  timestamp: Date.now(),
32
34
  });
@@ -35,8 +37,10 @@ export function setupConsoleCapture(client, sessionId) {
35
37
  if (sid !== sessionId)
36
38
  return;
37
39
  const entry = params.entry;
40
+ // CDP uses 'warning' but ConsoleMessage type uses 'warn'
41
+ const rawLevel = entry.level === 'warning' ? 'warn' : entry.level;
38
42
  messagesFor(sessionId).push({
39
- type: entry.level ?? 'log',
43
+ type: rawLevel ?? 'log',
40
44
  text: entry.text ?? '',
41
45
  timestamp: Date.now(),
42
46
  url: entry.url,
@@ -47,8 +51,9 @@ export function setupConsoleCapture(client, sessionId) {
47
51
  if (sid !== sessionId)
48
52
  return;
49
53
  const detail = params.exceptionDetails;
54
+ const message = detail.exception?.description ?? detail.text ?? 'Unknown error';
50
55
  errorsFor(sessionId).push({
51
- text: detail.text ?? 'Unknown error',
56
+ text: message,
52
57
  url: detail.url,
53
58
  lineNumber: detail.lineNumber,
54
59
  columnNumber: detail.columnNumber,
@@ -58,6 +63,7 @@ export function setupConsoleCapture(client, sessionId) {
58
63
  _consoleListeners.set(sessionId, [off1, off2, off3]);
59
64
  }
60
65
  export async function enableConsoleCapture(client, sessionId) {
66
+ await client.send('Runtime.enable', {}, sessionId);
61
67
  await client.send('Log.enable', {}, sessionId);
62
68
  }
63
69
  export function getConsoleMessages(sessionId) {
@@ -85,4 +91,14 @@ export function clearPageErrors(sessionId) {
85
91
  }
86
92
  _pageErrors.clear();
87
93
  }
94
+ export function teardownConsoleCapture(sessionId) {
95
+ const offs = _consoleListeners.get(sessionId);
96
+ if (offs) {
97
+ for (const off of offs)
98
+ off();
99
+ _consoleListeners.delete(sessionId);
100
+ }
101
+ _consoleMessages.delete(sessionId);
102
+ _pageErrors.delete(sessionId);
103
+ }
88
104
  //# sourceMappingURL=console-log.js.map
@@ -13,8 +13,11 @@ export function setupDialogAutoHandling(client, sessionId, autoAccept = true) {
13
13
  defaultPrompt: params.defaultPrompt,
14
14
  };
15
15
  _pendingDialogs.set(sessionId, info);
16
- if (autoAccept && (info.type === 'alert' || info.type === 'beforeunload')) {
17
- await client.send('Page.handleJavaScriptDialog', { accept: true }, sessionId);
16
+ if (autoAccept) {
17
+ try {
18
+ await client.send('Page.handleJavaScriptDialog', { accept: true }, sessionId);
19
+ }
20
+ catch { /* dialog may have already been dismissed */ }
18
21
  _pendingDialogs.set(sessionId, null);
19
22
  }
20
23
  });