monomind 1.10.37 → 1.10.38
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/.claude/helpers/utils/telemetry.cjs +1 -0
- package/package.json +1 -1
- package/packages/@monomind/cli/dist/src/browser/actions.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/actions.js +12 -5
- package/packages/@monomind/cli/dist/src/browser/batch.d.ts +0 -6
- package/packages/@monomind/cli/dist/src/browser/browser.js +37 -20
- package/packages/@monomind/cli/dist/src/browser/cdp.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/cdp.js +14 -4
- package/packages/@monomind/cli/dist/src/browser/console-log.d.ts +4 -4
- package/packages/@monomind/cli/dist/src/browser/console-log.js +49 -16
- package/packages/@monomind/cli/dist/src/browser/dialog.d.ts +2 -1
- package/packages/@monomind/cli/dist/src/browser/dialog.js +24 -10
- package/packages/@monomind/cli/dist/src/browser/find.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/browser/find.js +29 -10
- package/packages/@monomind/cli/dist/src/browser/har.js +3 -3
- package/packages/@monomind/cli/dist/src/browser/network.d.ts +2 -0
- package/packages/@monomind/cli/dist/src/browser/network.js +64 -23
- package/packages/@monomind/cli/dist/src/browser/profiler.js +25 -15
- package/packages/@monomind/cli/dist/src/browser/record.js +7 -3
- package/packages/@monomind/cli/dist/src/browser/session.js +11 -7
- package/packages/@monomind/cli/dist/src/browser/trace.js +32 -17
- package/packages/@monomind/cli/dist/src/browser/wait.js +28 -14
- package/packages/@monomind/cli/dist/src/commands/browse.js +8 -14
- package/packages/@monomind/cli/package.json +1 -1
|
@@ -129,6 +129,7 @@ function _recordDecisionMarkers(promptText) {
|
|
|
129
129
|
if (!matches || matches.length === 0) return;
|
|
130
130
|
try {
|
|
131
131
|
var f = path.join(CWD, '.monomind', 'decisions.jsonl');
|
|
132
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
132
133
|
var entry = JSON.stringify({ ts: Date.now(), excerpts: matches.slice(0, 3), prompt: promptText.slice(0, 400) });
|
|
133
134
|
fs.appendFileSync(f, entry + '\n');
|
|
134
135
|
} catch (e) {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.38",
|
|
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",
|
|
@@ -4,6 +4,7 @@ export declare function clickElement(client: CdpClient, sessionId: string, ref:
|
|
|
4
4
|
export declare function clickPoint(client: CdpClient, sessionId: string, x: number, y: number, options?: ClickOptions): Promise<void>;
|
|
5
5
|
export declare function fillElement(client: CdpClient, sessionId: string, ref: ElementRef, value: string): Promise<void>;
|
|
6
6
|
export declare function typeText(client: CdpClient, sessionId: string, text: string): Promise<void>;
|
|
7
|
+
export declare function pressKeyCombo(client: CdpClient, sessionId: string, key: string, modifiers: number): Promise<void>;
|
|
7
8
|
export declare function pressKey(client: CdpClient, sessionId: string, key: string): Promise<void>;
|
|
8
9
|
export declare function scrollElement(client: CdpClient, sessionId: string, direction: 'up' | 'down' | 'left' | 'right', amount?: number, ref?: ElementRef): Promise<void>;
|
|
9
10
|
export declare function hoverElement(client: CdpClient, sessionId: string, ref: ElementRef): Promise<void>;
|
|
@@ -20,10 +20,11 @@ export async function clickPoint(client, sessionId, x, y, options = {}) {
|
|
|
20
20
|
const button = options.button ?? 'left';
|
|
21
21
|
const clickCount = options.clickCount ?? 1;
|
|
22
22
|
const modifiers = options.modifiers ?? 0;
|
|
23
|
-
const shared = { x, y, button, modifiers
|
|
23
|
+
const shared = { x, y, button, modifiers };
|
|
24
24
|
for (let i = 0; i < clickCount; i++) {
|
|
25
|
-
|
|
26
|
-
await client.send('Input.dispatchMouseEvent', { ...shared, type: '
|
|
25
|
+
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);
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
export async function fillElement(client, sessionId, ref, value) {
|
|
@@ -56,6 +57,12 @@ export async function fillElement(client, sessionId, ref, value) {
|
|
|
56
57
|
export async function typeText(client, sessionId, text) {
|
|
57
58
|
await client.send('Input.insertText', { text }, sessionId);
|
|
58
59
|
}
|
|
60
|
+
export async function pressKeyCombo(client, sessionId, key, modifiers) {
|
|
61
|
+
const { text: _text, ...keyInfo } = resolveKey(key);
|
|
62
|
+
// rawKeyDown prevents Chrome from inserting the character text; only the shortcut fires
|
|
63
|
+
await client.send('Input.dispatchKeyEvent', { type: 'rawKeyDown', ...keyInfo, modifiers }, sessionId);
|
|
64
|
+
await client.send('Input.dispatchKeyEvent', { type: 'keyUp', ...keyInfo, modifiers }, sessionId);
|
|
65
|
+
}
|
|
59
66
|
export async function pressKey(client, sessionId, key) {
|
|
60
67
|
const keyInfo = resolveKey(key);
|
|
61
68
|
await client.send('Input.dispatchKeyEvent', {
|
|
@@ -93,9 +100,9 @@ function resolveKey(key) {
|
|
|
93
100
|
};
|
|
94
101
|
if (keyMap[key])
|
|
95
102
|
return keyMap[key];
|
|
96
|
-
// Single printable character
|
|
103
|
+
// Single printable character — windowsVirtualKeyCode required for rawKeyDown shortcut dispatch
|
|
97
104
|
if (key.length === 1) {
|
|
98
|
-
return { key, code: `Key${key.toUpperCase()}`, text: key };
|
|
105
|
+
return { key, code: `Key${key.toUpperCase()}`, text: key, windowsVirtualKeyCode: key.toUpperCase().charCodeAt(0) };
|
|
99
106
|
}
|
|
100
107
|
return { key, code: key };
|
|
101
108
|
}
|
|
@@ -2,12 +2,6 @@ export interface BatchCommand {
|
|
|
2
2
|
command: string;
|
|
3
3
|
args: string[];
|
|
4
4
|
}
|
|
5
|
-
export type BatchResult = {
|
|
6
|
-
command: string;
|
|
7
|
-
success: boolean;
|
|
8
|
-
output?: string;
|
|
9
|
-
error?: string;
|
|
10
|
-
};
|
|
11
5
|
export declare function parseBatchCommands(input: string[]): Promise<BatchCommand[]>;
|
|
12
6
|
export declare function parseBatchJson(json: string): Promise<BatchCommand[]>;
|
|
13
7
|
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from 'os';
|
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { CdpClient, fetchTargets, fetchNewTarget } from './cdp.js';
|
|
6
6
|
import { CHROME_EXECUTABLES } from './types.js';
|
|
7
|
-
import { enableConsoleCapture } from './console-log.js';
|
|
7
|
+
import { enableConsoleCapture, setupConsoleCapture } from './console-log.js';
|
|
8
8
|
import { setupDialogAutoHandling } from './dialog.js';
|
|
9
9
|
const DEFAULT_PORT = 9222;
|
|
10
10
|
const LAUNCH_TIMEOUT = 10_000;
|
|
@@ -115,7 +115,8 @@ export async function connectToTarget(port, targetId) {
|
|
|
115
115
|
client.send('Accessibility.enable', {}, sessionId),
|
|
116
116
|
]);
|
|
117
117
|
// Wire up auto-capture listeners so console/errors/dialogs work immediately
|
|
118
|
-
|
|
118
|
+
setupConsoleCapture(client, sessionId);
|
|
119
|
+
await enableConsoleCapture(client, sessionId);
|
|
119
120
|
setupDialogAutoHandling(client, sessionId);
|
|
120
121
|
return { client, target, sessionId };
|
|
121
122
|
}
|
|
@@ -126,38 +127,54 @@ export async function openUrl(client, sessionId, url) {
|
|
|
126
127
|
export async function waitForLoad(client, sessionId, condition = 'load', timeout = 30_000) {
|
|
127
128
|
if (condition === 'load' || condition === 'domcontentloaded') {
|
|
128
129
|
const event = condition === 'load' ? 'Page.loadEventFired' : 'Page.domContentEventFired';
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
const [eventPromise, cancelOnce] = client.onceWithOff(event, sessionId);
|
|
131
|
+
let timedOut = false;
|
|
132
|
+
const timeoutPromise = sleep(timeout).then(() => { timedOut = true; });
|
|
133
|
+
try {
|
|
134
|
+
await Promise.race([eventPromise, timeoutPromise]);
|
|
135
|
+
if (timedOut)
|
|
136
|
+
throw new Error(`Timeout waiting for ${condition}`);
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
cancelOnce();
|
|
140
|
+
}
|
|
133
141
|
return;
|
|
134
142
|
}
|
|
135
143
|
// networkidle: no network requests for 500ms
|
|
136
|
-
await
|
|
137
|
-
waitForNetworkIdle(client, sessionId, 500),
|
|
138
|
-
sleep(timeout).then(() => { throw new Error('Timeout waiting for networkidle'); }),
|
|
139
|
-
]);
|
|
144
|
+
await waitForNetworkIdle(client, sessionId, 500, timeout);
|
|
140
145
|
}
|
|
141
|
-
async function waitForNetworkIdle(client, sessionId, idleMs) {
|
|
142
|
-
return new Promise((resolve) => {
|
|
146
|
+
async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
143
148
|
let pending = 0;
|
|
144
|
-
let
|
|
145
|
-
const
|
|
149
|
+
let idleTimer = null;
|
|
150
|
+
const killTimer = setTimeout(() => {
|
|
151
|
+
cleanup();
|
|
152
|
+
reject(new Error('Timeout waiting for networkidle'));
|
|
153
|
+
}, timeout);
|
|
154
|
+
const cleanup = () => {
|
|
155
|
+
if (idleTimer) {
|
|
156
|
+
clearTimeout(idleTimer);
|
|
157
|
+
idleTimer = null;
|
|
158
|
+
}
|
|
159
|
+
clearTimeout(killTimer);
|
|
146
160
|
offReq();
|
|
147
161
|
offResp();
|
|
148
162
|
offFail();
|
|
163
|
+
};
|
|
164
|
+
const settle = () => {
|
|
165
|
+
cleanup();
|
|
149
166
|
resolve();
|
|
150
167
|
};
|
|
151
168
|
const check = () => {
|
|
152
169
|
if (pending === 0) {
|
|
153
|
-
if (
|
|
154
|
-
clearTimeout(
|
|
155
|
-
|
|
170
|
+
if (idleTimer)
|
|
171
|
+
clearTimeout(idleTimer);
|
|
172
|
+
idleTimer = setTimeout(settle, idleMs);
|
|
156
173
|
}
|
|
157
174
|
else {
|
|
158
|
-
if (
|
|
159
|
-
clearTimeout(
|
|
160
|
-
|
|
175
|
+
if (idleTimer) {
|
|
176
|
+
clearTimeout(idleTimer);
|
|
177
|
+
idleTimer = null;
|
|
161
178
|
}
|
|
162
179
|
}
|
|
163
180
|
};
|
|
@@ -9,6 +9,7 @@ export declare class CdpClient {
|
|
|
9
9
|
send<T = Record<string, unknown>>(method: string, params?: Record<string, unknown>, sessionId?: string): Promise<T>;
|
|
10
10
|
on(event: string, fn: (params: Record<string, unknown>, sessionId?: string) => void): () => void;
|
|
11
11
|
once(event: string, sessionId?: string): Promise<Record<string, unknown>>;
|
|
12
|
+
onceWithOff(event: string, sessionId?: string): [Promise<Record<string, unknown>>, () => void];
|
|
12
13
|
close(): void;
|
|
13
14
|
isConnected(): boolean;
|
|
14
15
|
}
|
|
@@ -39,8 +39,12 @@ export class CdpClient {
|
|
|
39
39
|
else if (msg.method) {
|
|
40
40
|
const listeners = this.eventListeners.get(msg.method);
|
|
41
41
|
if (listeners) {
|
|
42
|
-
for (const fn of listeners)
|
|
43
|
-
|
|
42
|
+
for (const fn of listeners) {
|
|
43
|
+
try {
|
|
44
|
+
fn(msg.params ?? {}, msg.sessionId);
|
|
45
|
+
}
|
|
46
|
+
catch { /* isolate per-listener errors */ }
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
}
|
|
@@ -75,14 +79,20 @@ export class CdpClient {
|
|
|
75
79
|
return () => this.eventListeners.get(event)?.delete(fn);
|
|
76
80
|
}
|
|
77
81
|
once(event, sessionId) {
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
const [promise] = this.onceWithOff(event, sessionId);
|
|
83
|
+
return promise;
|
|
84
|
+
}
|
|
85
|
+
onceWithOff(event, sessionId) {
|
|
86
|
+
let off = () => { };
|
|
87
|
+
const promise = new Promise((resolve) => {
|
|
88
|
+
off = this.on(event, (params, sid) => {
|
|
80
89
|
if (sessionId !== undefined && sid !== sessionId)
|
|
81
90
|
return;
|
|
82
91
|
off();
|
|
83
92
|
resolve(params);
|
|
84
93
|
});
|
|
85
94
|
});
|
|
95
|
+
return [promise, () => off()];
|
|
86
96
|
}
|
|
87
97
|
close() {
|
|
88
98
|
this.ws?.close();
|
|
@@ -15,8 +15,8 @@ export interface PageError {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function setupConsoleCapture(client: CdpClient, sessionId: string): void;
|
|
17
17
|
export declare function enableConsoleCapture(client: CdpClient, sessionId: string): Promise<void>;
|
|
18
|
-
export declare function getConsoleMessages(): ConsoleMessage[];
|
|
19
|
-
export declare function clearConsoleMessages(): void;
|
|
20
|
-
export declare function getPageErrors(): PageError[];
|
|
21
|
-
export declare function clearPageErrors(): void;
|
|
18
|
+
export declare function getConsoleMessages(sessionId?: string): ConsoleMessage[];
|
|
19
|
+
export declare function clearConsoleMessages(sessionId?: string): void;
|
|
20
|
+
export declare function getPageErrors(sessionId?: string): PageError[];
|
|
21
|
+
export declare function clearPageErrors(sessionId?: string): void;
|
|
22
22
|
//# sourceMappingURL=console-log.d.ts.map
|
|
@@ -1,22 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const _consoleMessages = new Map();
|
|
2
|
+
const _pageErrors = new Map();
|
|
3
|
+
const _consoleListeners = new Map();
|
|
4
|
+
function messagesFor(sessionId) {
|
|
5
|
+
if (!_consoleMessages.has(sessionId))
|
|
6
|
+
_consoleMessages.set(sessionId, []);
|
|
7
|
+
return _consoleMessages.get(sessionId);
|
|
8
|
+
}
|
|
9
|
+
function errorsFor(sessionId) {
|
|
10
|
+
if (!_pageErrors.has(sessionId))
|
|
11
|
+
_pageErrors.set(sessionId, []);
|
|
12
|
+
return _pageErrors.get(sessionId);
|
|
13
|
+
}
|
|
3
14
|
export function setupConsoleCapture(client, sessionId) {
|
|
4
|
-
|
|
15
|
+
// Remove stale listeners from any prior connection on this sessionId
|
|
16
|
+
const prevOffs = _consoleListeners.get(sessionId);
|
|
17
|
+
if (prevOffs) {
|
|
18
|
+
for (const off of prevOffs)
|
|
19
|
+
off();
|
|
20
|
+
}
|
|
21
|
+
_consoleMessages.set(sessionId, []);
|
|
22
|
+
_pageErrors.set(sessionId, []);
|
|
23
|
+
const off1 = client.on('Runtime.consoleAPICalled', (params, sid) => {
|
|
5
24
|
if (sid !== sessionId)
|
|
6
25
|
return;
|
|
7
26
|
const args = params.args ?? [];
|
|
8
27
|
const text = args.map((a) => a.description ?? String(a.value ?? '')).join(' ');
|
|
9
|
-
|
|
28
|
+
messagesFor(sessionId).push({
|
|
10
29
|
type: params.type ?? 'log',
|
|
11
30
|
text,
|
|
12
31
|
timestamp: Date.now(),
|
|
13
32
|
});
|
|
14
33
|
});
|
|
15
|
-
client.on('Log.entryAdded', (params, sid) => {
|
|
34
|
+
const off2 = client.on('Log.entryAdded', (params, sid) => {
|
|
16
35
|
if (sid !== sessionId)
|
|
17
36
|
return;
|
|
18
37
|
const entry = params.entry;
|
|
19
|
-
|
|
38
|
+
messagesFor(sessionId).push({
|
|
20
39
|
type: entry.level ?? 'log',
|
|
21
40
|
text: entry.text ?? '',
|
|
22
41
|
timestamp: Date.now(),
|
|
@@ -24,11 +43,11 @@ export function setupConsoleCapture(client, sessionId) {
|
|
|
24
43
|
lineNumber: entry.lineNumber,
|
|
25
44
|
});
|
|
26
45
|
});
|
|
27
|
-
client.on('Runtime.exceptionThrown', (params, sid) => {
|
|
46
|
+
const off3 = client.on('Runtime.exceptionThrown', (params, sid) => {
|
|
28
47
|
if (sid !== sessionId)
|
|
29
48
|
return;
|
|
30
49
|
const detail = params.exceptionDetails;
|
|
31
|
-
|
|
50
|
+
errorsFor(sessionId).push({
|
|
32
51
|
text: detail.text ?? 'Unknown error',
|
|
33
52
|
url: detail.url,
|
|
34
53
|
lineNumber: detail.lineNumber,
|
|
@@ -36,20 +55,34 @@ export function setupConsoleCapture(client, sessionId) {
|
|
|
36
55
|
timestamp: Date.now(),
|
|
37
56
|
});
|
|
38
57
|
});
|
|
58
|
+
_consoleListeners.set(sessionId, [off1, off2, off3]);
|
|
39
59
|
}
|
|
40
60
|
export async function enableConsoleCapture(client, sessionId) {
|
|
41
61
|
await client.send('Log.enable', {}, sessionId);
|
|
42
62
|
}
|
|
43
|
-
export function getConsoleMessages() {
|
|
44
|
-
|
|
63
|
+
export function getConsoleMessages(sessionId) {
|
|
64
|
+
if (sessionId)
|
|
65
|
+
return [...(messagesFor(sessionId))];
|
|
66
|
+
// Fallback: return all messages across all sessions (legacy callers)
|
|
67
|
+
return [..._consoleMessages.values()].flat();
|
|
45
68
|
}
|
|
46
|
-
export function clearConsoleMessages() {
|
|
47
|
-
|
|
69
|
+
export function clearConsoleMessages(sessionId) {
|
|
70
|
+
if (sessionId) {
|
|
71
|
+
_consoleMessages.set(sessionId, []);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
_consoleMessages.clear();
|
|
48
75
|
}
|
|
49
|
-
export function getPageErrors() {
|
|
50
|
-
|
|
76
|
+
export function getPageErrors(sessionId) {
|
|
77
|
+
if (sessionId)
|
|
78
|
+
return [...(errorsFor(sessionId))];
|
|
79
|
+
return [..._pageErrors.values()].flat();
|
|
51
80
|
}
|
|
52
|
-
export function clearPageErrors() {
|
|
53
|
-
|
|
81
|
+
export function clearPageErrors(sessionId) {
|
|
82
|
+
if (sessionId) {
|
|
83
|
+
_pageErrors.set(sessionId, []);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
_pageErrors.clear();
|
|
54
87
|
}
|
|
55
88
|
//# sourceMappingURL=console-log.js.map
|
|
@@ -5,7 +5,8 @@ export interface DialogInfo {
|
|
|
5
5
|
defaultPrompt?: string;
|
|
6
6
|
}
|
|
7
7
|
export declare function setupDialogAutoHandling(client: CdpClient, sessionId: string, autoAccept?: boolean): void;
|
|
8
|
+
export declare function teardownDialogHandling(sessionId: string): void;
|
|
8
9
|
export declare function acceptDialog(client: CdpClient, sessionId: string, text?: string): Promise<void>;
|
|
9
10
|
export declare function dismissDialog(client: CdpClient, sessionId: string): Promise<void>;
|
|
10
|
-
export declare function getDialogStatus(): DialogInfo | null;
|
|
11
|
+
export declare function getDialogStatus(sessionId: string): DialogInfo | null;
|
|
11
12
|
//# sourceMappingURL=dialog.d.ts.map
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
const _pendingDialogs = new Map();
|
|
2
|
+
const _dialogListeners = new Map();
|
|
2
3
|
export function setupDialogAutoHandling(client, sessionId, autoAccept = true) {
|
|
3
|
-
|
|
4
|
+
if (_pendingDialogs.has(sessionId))
|
|
5
|
+
return;
|
|
6
|
+
_pendingDialogs.set(sessionId, null);
|
|
7
|
+
const off1 = client.on('Page.javascriptDialogOpening', async (params, sid) => {
|
|
4
8
|
if (sid !== sessionId)
|
|
5
9
|
return;
|
|
6
10
|
const info = {
|
|
@@ -8,29 +12,39 @@ export function setupDialogAutoHandling(client, sessionId, autoAccept = true) {
|
|
|
8
12
|
message: params.message,
|
|
9
13
|
defaultPrompt: params.defaultPrompt,
|
|
10
14
|
};
|
|
11
|
-
|
|
15
|
+
_pendingDialogs.set(sessionId, info);
|
|
12
16
|
if (autoAccept && (info.type === 'alert' || info.type === 'beforeunload')) {
|
|
13
17
|
await client.send('Page.handleJavaScriptDialog', { accept: true }, sessionId);
|
|
14
|
-
|
|
18
|
+
_pendingDialogs.set(sessionId, null);
|
|
15
19
|
}
|
|
16
20
|
});
|
|
17
|
-
client.on('Page.javascriptDialogClosed', (_, sid) => {
|
|
21
|
+
const off2 = client.on('Page.javascriptDialogClosed', (_, sid) => {
|
|
18
22
|
if (sid === sessionId)
|
|
19
|
-
|
|
23
|
+
_pendingDialogs.set(sessionId, null);
|
|
20
24
|
});
|
|
25
|
+
_dialogListeners.set(sessionId, [off1, off2]);
|
|
26
|
+
}
|
|
27
|
+
export function teardownDialogHandling(sessionId) {
|
|
28
|
+
const offs = _dialogListeners.get(sessionId);
|
|
29
|
+
if (offs) {
|
|
30
|
+
for (const off of offs)
|
|
31
|
+
off();
|
|
32
|
+
_dialogListeners.delete(sessionId);
|
|
33
|
+
}
|
|
34
|
+
_pendingDialogs.delete(sessionId);
|
|
21
35
|
}
|
|
22
36
|
export async function acceptDialog(client, sessionId, text) {
|
|
23
37
|
await client.send('Page.handleJavaScriptDialog', {
|
|
24
38
|
accept: true,
|
|
25
39
|
promptText: text,
|
|
26
40
|
}, sessionId);
|
|
27
|
-
|
|
41
|
+
_pendingDialogs.set(sessionId, null);
|
|
28
42
|
}
|
|
29
43
|
export async function dismissDialog(client, sessionId) {
|
|
30
44
|
await client.send('Page.handleJavaScriptDialog', { accept: false }, sessionId);
|
|
31
|
-
|
|
45
|
+
_pendingDialogs.set(sessionId, null);
|
|
32
46
|
}
|
|
33
|
-
export function getDialogStatus() {
|
|
34
|
-
return
|
|
47
|
+
export function getDialogStatus(sessionId) {
|
|
48
|
+
return _pendingDialogs.get(sessionId) ?? null;
|
|
35
49
|
}
|
|
36
50
|
//# sourceMappingURL=dialog.js.map
|
|
@@ -8,7 +8,7 @@ export interface FindOptions {
|
|
|
8
8
|
first?: boolean;
|
|
9
9
|
last?: boolean;
|
|
10
10
|
}
|
|
11
|
-
export declare function findBySelector(client: CdpClient, sessionId: string, selector: string, options?: FindOptions): Promise<
|
|
11
|
+
export declare function findBySelector(client: CdpClient, sessionId: string, refs: Map<string, ElementRef>, selector: string, options?: FindOptions): Promise<ElementRef | null>;
|
|
12
12
|
export declare function findByRole(client: CdpClient, sessionId: string, refs: Map<string, ElementRef>, role: string, options?: FindOptions): Promise<ElementRef | null>;
|
|
13
13
|
export declare function findByText(client: CdpClient, sessionId: string, refs: Map<string, ElementRef>, text: string, options?: FindOptions): Promise<ElementRef | null>;
|
|
14
14
|
export declare function findByLabel(client: CdpClient, sessionId: string, refs: Map<string, ElementRef>, label: string, options?: FindOptions): Promise<ElementRef | null>;
|
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { evaluateJs } from './actions.js';
|
|
2
2
|
import { getObjectIdForRef } from './snapshot.js';
|
|
3
|
-
export async function findBySelector(client, sessionId, selector, options = {}) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export async function findBySelector(client, sessionId, refs, selector, options = {}) {
|
|
4
|
+
try {
|
|
5
|
+
const doc = await client.send('DOM.getDocument', {}, sessionId);
|
|
6
|
+
let targetNodeId;
|
|
7
|
+
if (options.nth !== undefined || options.last) {
|
|
8
|
+
const result = await client.send('DOM.querySelectorAll', { nodeId: doc.root.nodeId, selector }, sessionId);
|
|
9
|
+
const nodeIds = result.nodeIds ?? [];
|
|
10
|
+
if (nodeIds.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
targetNodeId = options.last ? nodeIds[nodeIds.length - 1] : (nodeIds[(options.nth ?? 1) - 1] ?? 0);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const result = await client.send('DOM.querySelector', { nodeId: doc.root.nodeId, selector }, sessionId);
|
|
16
|
+
targetNodeId = result.nodeId ?? 0;
|
|
17
|
+
}
|
|
18
|
+
if (!targetNodeId)
|
|
19
|
+
return null;
|
|
20
|
+
const desc = await client.send('DOM.describeNode', { nodeId: targetNodeId }, sessionId);
|
|
21
|
+
const backendDOMNodeId = desc.node?.backendNodeId;
|
|
22
|
+
if (!backendDOMNodeId)
|
|
23
|
+
return null;
|
|
24
|
+
// Return existing ref from snapshot if this node is already indexed
|
|
25
|
+
const existing = [...refs.values()].find((r) => r.backendDOMNodeId === backendDOMNodeId);
|
|
26
|
+
if (existing)
|
|
27
|
+
return existing;
|
|
28
|
+
// Synthetic ref for elements not represented in the AX tree
|
|
29
|
+
return { ref: `sel-${backendDOMNodeId}`, role: 'generic', name: selector, nodeId: targetNodeId, backendDOMNodeId };
|
|
7
30
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
expr = `document.querySelector(${JSON.stringify(selector)})?.outerHTML ?? null`;
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
13
33
|
}
|
|
14
|
-
return evaluateJs(client, sessionId, expr);
|
|
15
34
|
}
|
|
16
35
|
export async function findByRole(client, sessionId, refs, role, options = {}) {
|
|
17
36
|
const lowerRole = role.toLowerCase();
|
|
@@ -51,7 +51,7 @@ export async function startHarRecording(client, sessionId) {
|
|
|
51
51
|
entry.endTime = cdpToWall(p.timestamp);
|
|
52
52
|
}
|
|
53
53
|
});
|
|
54
|
-
_sessions.set(sessionId, { requests, offReq, offResp, offFinished, startTime, startWallMs
|
|
54
|
+
_sessions.set(sessionId, { requests, offReq, offResp, offFinished, startTime, startWallMs });
|
|
55
55
|
}
|
|
56
56
|
export async function stopHarRecording(client, sessionId, outputPath, captureResponseBodies = false) {
|
|
57
57
|
const state = _sessions.get(sessionId);
|
|
@@ -68,14 +68,14 @@ export async function stopHarRecording(client, sessionId, outputPath, captureRes
|
|
|
68
68
|
entry.responseBody = body.base64Encoded
|
|
69
69
|
? Buffer.from(body.body, 'base64').toString('utf8')
|
|
70
70
|
: body.body;
|
|
71
|
-
entry.size = entry.responseBody
|
|
71
|
+
entry.size = entry.responseBody?.length ?? 0;
|
|
72
72
|
}
|
|
73
73
|
catch {
|
|
74
74
|
// body may not be available for cached/redirected responses
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
_sessions.delete(sessionId);
|
|
78
|
+
_sessions.delete(sessionId); // always runs — response body loop errors are caught per-entry
|
|
79
79
|
const har = buildHar(Array.from(state.requests.values()), state.startTime);
|
|
80
80
|
const path = outputPath ?? join(tmpdir(), `monomind-har-${Date.now()}.har`);
|
|
81
81
|
await writeFile(path, JSON.stringify(har, null, 2));
|
|
@@ -24,4 +24,6 @@ export declare function clearCapturedRequests(sessionId: string): void;
|
|
|
24
24
|
export declare function disableInterception(client: CdpClient, sessionId: string): Promise<void>;
|
|
25
25
|
export declare function getLocalStorage(client: CdpClient, sessionId: string): Promise<Record<string, string>>;
|
|
26
26
|
export declare function setLocalStorage(client: CdpClient, sessionId: string, data: Record<string, string>): Promise<void>;
|
|
27
|
+
export declare function getSessionStorage(client: CdpClient, sessionId: string): Promise<Record<string, string>>;
|
|
28
|
+
export declare function setSessionStorage(client: CdpClient, sessionId: string, data: Record<string, string>): Promise<void>;
|
|
27
29
|
//# sourceMappingURL=network.d.ts.map
|
|
@@ -16,39 +16,55 @@ export async function enableInterception(client, sessionId) {
|
|
|
16
16
|
patterns: [{ requestStage: 'Request' }],
|
|
17
17
|
}, sessionId);
|
|
18
18
|
}
|
|
19
|
+
const _routeListeners = new Map();
|
|
19
20
|
export async function setupRoutes(client, sessionId, routes) {
|
|
20
|
-
|
|
21
|
+
// Remove previous route listener for this session before registering a new one
|
|
22
|
+
const prevOff = _routeListeners.get(sessionId);
|
|
23
|
+
if (prevOff) {
|
|
24
|
+
prevOff();
|
|
25
|
+
_routeListeners.delete(sessionId);
|
|
26
|
+
}
|
|
27
|
+
if (routes.length === 0) {
|
|
28
|
+
await client.send('Fetch.disable', {}, sessionId);
|
|
21
29
|
return;
|
|
30
|
+
}
|
|
22
31
|
await client.send('Fetch.enable', {
|
|
23
32
|
patterns: routes.map((r) => ({ urlPattern: globToFetchPattern(r.pattern), requestStage: 'Request' })),
|
|
24
33
|
}, sessionId);
|
|
25
|
-
client.on('Fetch.requestPaused', async (params, sid) => {
|
|
34
|
+
const off = client.on('Fetch.requestPaused', async (params, sid) => {
|
|
26
35
|
if (sid !== sessionId)
|
|
27
36
|
return;
|
|
28
37
|
const { requestId, request } = params;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
switch (matchedRoute.action) {
|
|
35
|
-
case 'abort':
|
|
36
|
-
await client.send('Fetch.failRequest', { requestId, errorReason: 'Failed' }, sessionId);
|
|
37
|
-
break;
|
|
38
|
-
case 'fulfill':
|
|
39
|
-
await client.send('Fetch.fulfillRequest', {
|
|
40
|
-
requestId,
|
|
41
|
-
responseCode: matchedRoute.response?.status ?? 200,
|
|
42
|
-
responseHeaders: Object.entries(matchedRoute.response?.headers ?? {}).map(([name, value]) => ({ name, value })),
|
|
43
|
-
body: matchedRoute.response?.body ? Buffer.from(matchedRoute.response.body).toString('base64') : '',
|
|
44
|
-
}, sessionId);
|
|
45
|
-
break;
|
|
46
|
-
case 'continue':
|
|
47
|
-
default:
|
|
38
|
+
try {
|
|
39
|
+
const matchedRoute = routes.find((r) => globToRegex(r.pattern).test(request.url));
|
|
40
|
+
if (!matchedRoute) {
|
|
48
41
|
await client.send('Fetch.continueRequest', { requestId }, sessionId);
|
|
49
|
-
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
switch (matchedRoute.action) {
|
|
45
|
+
case 'abort':
|
|
46
|
+
await client.send('Fetch.failRequest', { requestId, errorReason: 'Failed' }, sessionId);
|
|
47
|
+
break;
|
|
48
|
+
case 'fulfill':
|
|
49
|
+
await client.send('Fetch.fulfillRequest', {
|
|
50
|
+
requestId,
|
|
51
|
+
responseCode: matchedRoute.response?.status ?? 200,
|
|
52
|
+
responseHeaders: Object.entries(matchedRoute.response?.headers ?? {}).map(([name, value]) => ({ name, value })),
|
|
53
|
+
body: matchedRoute.response?.body ? Buffer.from(matchedRoute.response.body).toString('base64') : '',
|
|
54
|
+
}, sessionId);
|
|
55
|
+
break;
|
|
56
|
+
case 'continue':
|
|
57
|
+
default:
|
|
58
|
+
await client.send('Fetch.continueRequest', { requestId }, sessionId);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Best-effort: resume the request so it doesn't hang in Chrome
|
|
64
|
+
await client.send('Fetch.continueRequest', { requestId }, sessionId).catch(() => { });
|
|
50
65
|
}
|
|
51
66
|
});
|
|
67
|
+
_routeListeners.set(sessionId, off);
|
|
52
68
|
}
|
|
53
69
|
const _capturedRequests = new Map();
|
|
54
70
|
const _captureListeners = new Map();
|
|
@@ -103,6 +119,11 @@ export function clearCapturedRequests(sessionId) {
|
|
|
103
119
|
_capturedRequests.set(sessionId, []);
|
|
104
120
|
}
|
|
105
121
|
export async function disableInterception(client, sessionId) {
|
|
122
|
+
const prevOff = _routeListeners.get(sessionId);
|
|
123
|
+
if (prevOff) {
|
|
124
|
+
prevOff();
|
|
125
|
+
_routeListeners.delete(sessionId);
|
|
126
|
+
}
|
|
106
127
|
await client.send('Fetch.disable', {}, sessionId);
|
|
107
128
|
}
|
|
108
129
|
export async function getLocalStorage(client, sessionId) {
|
|
@@ -121,7 +142,27 @@ export async function setLocalStorage(client, sessionId, data) {
|
|
|
121
142
|
const script = Object.entries(data)
|
|
122
143
|
.map(([k, v]) => `localStorage.setItem(${JSON.stringify(k)}, ${JSON.stringify(v)})`)
|
|
123
144
|
.join('; ');
|
|
124
|
-
|
|
145
|
+
if (script)
|
|
146
|
+
await client.send('Runtime.evaluate', { expression: script }, sessionId);
|
|
147
|
+
}
|
|
148
|
+
export async function getSessionStorage(client, sessionId) {
|
|
149
|
+
const result = await client.send('Runtime.evaluate', {
|
|
150
|
+
expression: 'JSON.stringify(Object.fromEntries(Object.entries(sessionStorage)))',
|
|
151
|
+
returnByValue: true,
|
|
152
|
+
}, sessionId);
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(result.result?.value ?? '{}');
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export async function setSessionStorage(client, sessionId, data) {
|
|
161
|
+
const script = Object.entries(data)
|
|
162
|
+
.map(([k, v]) => `sessionStorage.setItem(${JSON.stringify(k)}, ${JSON.stringify(v)})`)
|
|
163
|
+
.join('; ');
|
|
164
|
+
if (script)
|
|
165
|
+
await client.send('Runtime.evaluate', { expression: script }, sessionId);
|
|
125
166
|
}
|
|
126
167
|
function globToRegex(pattern) {
|
|
127
168
|
const escaped = pattern
|
|
@@ -17,9 +17,15 @@ export async function stopCpuProfile(client, sessionId, outputPath) {
|
|
|
17
17
|
if (!_sessions.has(sessionId)) {
|
|
18
18
|
throw new Error('No active CPU profiler for this session');
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
let result;
|
|
21
|
+
try {
|
|
22
|
+
result = await client.send('Profiler.stop', {}, sessionId);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
_sessions.delete(sessionId);
|
|
26
|
+
}
|
|
27
|
+
// Disable after capturing the profile so a disable failure doesn't discard valid data
|
|
28
|
+
await client.send('Profiler.disable', {}, sessionId).catch(() => { });
|
|
23
29
|
const path = outputPath ?? join(tmpdir(), `monomind-profile-${Date.now()}.cpuprofile`);
|
|
24
30
|
await writeFile(path, JSON.stringify(result.profile));
|
|
25
31
|
return path;
|
|
@@ -35,19 +41,23 @@ export async function startHeapSnapshot(client, sessionId, outputPath) {
|
|
|
35
41
|
return;
|
|
36
42
|
chunks.push(params.chunk);
|
|
37
43
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
try {
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
const off2 = client.on('HeapProfiler.reportHeapSnapshotProgress', (params, sid) => {
|
|
47
|
+
if (sid !== sessionId)
|
|
48
|
+
return;
|
|
49
|
+
if (params.finished) {
|
|
50
|
+
off2();
|
|
51
|
+
resolve();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
client.send('HeapProfiler.takeHeapSnapshot', { reportProgress: true }, sessionId)
|
|
55
|
+
.catch((err) => { off2(); reject(err); });
|
|
46
56
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
off();
|
|
60
|
+
}
|
|
51
61
|
await client.send('HeapProfiler.disable', {}, sessionId);
|
|
52
62
|
const path = outputPath ?? join(tmpdir(), `monomind-heap-${Date.now()}.heapsnapshot`);
|
|
53
63
|
await writeFile(path, chunks.join(''));
|
|
@@ -34,9 +34,13 @@ export async function stopRecording(client, sessionId, outputPath) {
|
|
|
34
34
|
const state = _sessions.get(sessionId);
|
|
35
35
|
if (!state)
|
|
36
36
|
throw new Error('No active recording for this session');
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
try {
|
|
38
|
+
await client.send('Page.stopScreencast', {}, sessionId);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
state.offScreencast?.();
|
|
42
|
+
_sessions.delete(sessionId);
|
|
43
|
+
}
|
|
40
44
|
const path = outputPath ?? join(tmpdir(), `monomind-screencast-${Date.now()}.frames.json`);
|
|
41
45
|
await writeFile(path, JSON.stringify({ frameCount: state.frames.length, frames: state.frames }));
|
|
42
46
|
return path;
|
|
@@ -2,13 +2,14 @@ import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
-
import { getCookies, setCookies, getLocalStorage, setLocalStorage } from './network.js';
|
|
5
|
+
import { getCookies, setCookies, getLocalStorage, setLocalStorage, getSessionStorage, setSessionStorage } from './network.js';
|
|
6
6
|
const SESSION_DIR = join(homedir(), '.monomind', 'browser-sessions');
|
|
7
7
|
export async function saveSession(client, sessionId, targetId, name, url, title) {
|
|
8
8
|
await mkdir(SESSION_DIR, { recursive: true });
|
|
9
9
|
const cookies = await getCookies(client, sessionId);
|
|
10
10
|
const localStorage = await getLocalStorage(client, sessionId);
|
|
11
|
-
const
|
|
11
|
+
const sessionStorage = await getSessionStorage(client, sessionId);
|
|
12
|
+
const state = { targetId, sessionId, url, title, cookies, localStorage, sessionStorage };
|
|
12
13
|
const filePath = join(SESSION_DIR, `${name}.json`);
|
|
13
14
|
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
14
15
|
return filePath;
|
|
@@ -20,24 +21,27 @@ export async function loadSession(client, sessionId, name) {
|
|
|
20
21
|
const raw = await readFile(filePath, 'utf8');
|
|
21
22
|
const state = JSON.parse(raw);
|
|
22
23
|
await setCookies(client, sessionId, state.cookies);
|
|
23
|
-
if (state.localStorage)
|
|
24
|
+
if (state.localStorage)
|
|
24
25
|
await setLocalStorage(client, sessionId, state.localStorage);
|
|
25
|
-
|
|
26
|
+
if (state.sessionStorage)
|
|
27
|
+
await setSessionStorage(client, sessionId, state.sessionStorage);
|
|
26
28
|
return state;
|
|
27
29
|
}
|
|
28
30
|
export async function saveStateFile(client, sessionId, targetId, filePath, url, title) {
|
|
29
31
|
const cookies = await getCookies(client, sessionId);
|
|
30
32
|
const localStorage = await getLocalStorage(client, sessionId);
|
|
31
|
-
const
|
|
33
|
+
const sessionStorage = await getSessionStorage(client, sessionId);
|
|
34
|
+
const state = { targetId, sessionId, url, title, cookies, localStorage, sessionStorage };
|
|
32
35
|
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
33
36
|
}
|
|
34
37
|
export async function loadStateFile(client, sessionId, filePath) {
|
|
35
38
|
const raw = await readFile(filePath, 'utf8');
|
|
36
39
|
const state = JSON.parse(raw);
|
|
37
40
|
await setCookies(client, sessionId, state.cookies);
|
|
38
|
-
if (state.localStorage)
|
|
41
|
+
if (state.localStorage)
|
|
39
42
|
await setLocalStorage(client, sessionId, state.localStorage);
|
|
40
|
-
|
|
43
|
+
if (state.sessionStorage)
|
|
44
|
+
await setSessionStorage(client, sessionId, state.sessionStorage);
|
|
41
45
|
return state;
|
|
42
46
|
}
|
|
43
47
|
export async function listSessions() {
|
|
@@ -30,28 +30,43 @@ export async function startTrace(client, sessionId, options = {}) {
|
|
|
30
30
|
const cats = [...(options.categories ?? DEFAULT_CATEGORIES)];
|
|
31
31
|
if (options.screenshots)
|
|
32
32
|
cats.push('disabled-by-default-devtools.screenshot');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
try {
|
|
34
|
+
await client.send('Tracing.start', {
|
|
35
|
+
traceConfig: {
|
|
36
|
+
includedCategories: cats.filter((c) => !c.startsWith('-')),
|
|
37
|
+
excludedCategories: cats.filter((c) => c.startsWith('-')).map((c) => c.slice(1)),
|
|
38
|
+
},
|
|
39
|
+
}, sessionId);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
offData();
|
|
43
|
+
_sessions.delete(sessionId);
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
39
46
|
}
|
|
40
47
|
export async function stopTrace(client, sessionId, outputPath) {
|
|
41
48
|
const state = _sessions.get(sessionId);
|
|
42
49
|
if (!state)
|
|
43
50
|
throw new Error('No active trace for this session');
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
try {
|
|
52
|
+
const [completePromise, cancelOnce] = client.onceWithOff('Tracing.tracingComplete', sessionId);
|
|
53
|
+
let timedOut = false;
|
|
54
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => { timedOut = true; cancelOnce(); reject(new Error('Timeout waiting for Tracing.tracingComplete')); }, 30_000));
|
|
55
|
+
try {
|
|
56
|
+
await client.send('Tracing.end', {}, sessionId);
|
|
57
|
+
await Promise.race([completePromise, timeoutPromise]);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
cancelOnce();
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
if (timedOut)
|
|
64
|
+
throw new Error('Timeout waiting for Tracing.tracingComplete');
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
state.offData();
|
|
68
|
+
_sessions.delete(sessionId);
|
|
69
|
+
}
|
|
55
70
|
const trace = {
|
|
56
71
|
traceEvents: state.events,
|
|
57
72
|
metadata: { 'clock-offset-since-epoch': Date.now() },
|
|
@@ -30,33 +30,47 @@ async function waitForLoad(client, sessionId, condition, timeout) {
|
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
const event = condition === 'load' ? 'Page.loadEventFired' : 'Page.domContentEventFired';
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const [eventPromise, cancelOnce] = client.onceWithOff(event, sessionId);
|
|
34
|
+
let timedOut = false;
|
|
35
|
+
const timeoutPromise = sleep(timeout).then(() => { timedOut = true; });
|
|
36
|
+
try {
|
|
37
|
+
await Promise.race([eventPromise, timeoutPromise]);
|
|
38
|
+
if (timedOut)
|
|
39
|
+
throw new Error(`Timeout waiting for ${condition}`);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
cancelOnce();
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
39
46
|
return new Promise((resolve, reject) => {
|
|
40
47
|
let pending = 0;
|
|
41
|
-
let
|
|
42
|
-
const
|
|
43
|
-
|
|
48
|
+
let idleTimer = null;
|
|
49
|
+
const cleanup = () => {
|
|
50
|
+
if (idleTimer) {
|
|
51
|
+
clearTimeout(idleTimer);
|
|
52
|
+
idleTimer = null;
|
|
53
|
+
}
|
|
44
54
|
clearTimeout(killTimer);
|
|
45
55
|
offReq();
|
|
46
56
|
offResp();
|
|
47
57
|
offFail();
|
|
48
|
-
resolve();
|
|
49
58
|
};
|
|
59
|
+
const killTimer = setTimeout(() => {
|
|
60
|
+
cleanup();
|
|
61
|
+
reject(new Error('Timeout waiting for networkidle'));
|
|
62
|
+
}, timeout);
|
|
63
|
+
const settle = () => { cleanup(); resolve(); };
|
|
50
64
|
const check = () => {
|
|
51
65
|
if (pending === 0) {
|
|
52
|
-
if (
|
|
53
|
-
clearTimeout(
|
|
54
|
-
|
|
66
|
+
if (idleTimer)
|
|
67
|
+
clearTimeout(idleTimer);
|
|
68
|
+
idleTimer = setTimeout(settle, idleMs);
|
|
55
69
|
}
|
|
56
70
|
else {
|
|
57
|
-
if (
|
|
58
|
-
clearTimeout(
|
|
59
|
-
|
|
71
|
+
if (idleTimer) {
|
|
72
|
+
clearTimeout(idleTimer);
|
|
73
|
+
idleTimer = null;
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
};
|
|
@@ -994,12 +994,12 @@ const clipboardCommand = {
|
|
|
994
994
|
break;
|
|
995
995
|
}
|
|
996
996
|
case 'copy':
|
|
997
|
-
await browser.
|
|
998
|
-
output.printSuccess('Copy sent
|
|
997
|
+
await browser.pressKeyCombo(client, sessionId, 'c', 2); // Ctrl modifier
|
|
998
|
+
output.printSuccess('Copy sent');
|
|
999
999
|
break;
|
|
1000
1000
|
case 'paste':
|
|
1001
|
-
await browser.
|
|
1002
|
-
output.printSuccess('Paste sent
|
|
1001
|
+
await browser.pressKeyCombo(client, sessionId, 'v', 2); // Ctrl modifier
|
|
1002
|
+
output.printSuccess('Paste sent');
|
|
1003
1003
|
break;
|
|
1004
1004
|
default:
|
|
1005
1005
|
throw new Error('Usage: monomind browse clipboard read|write|copy|paste');
|
|
@@ -1026,7 +1026,7 @@ const dialogCommand = {
|
|
|
1026
1026
|
output.printSuccess('Dialog dismissed');
|
|
1027
1027
|
break;
|
|
1028
1028
|
case 'status': {
|
|
1029
|
-
const info = browser.getDialogStatus();
|
|
1029
|
+
const info = browser.getDialogStatus(sessionId);
|
|
1030
1030
|
if (info) {
|
|
1031
1031
|
print(`Dialog open: type=${info.type} message="${info.message}"`);
|
|
1032
1032
|
}
|
|
@@ -1354,15 +1354,9 @@ const findCommand = {
|
|
|
1354
1354
|
case 'placeholder':
|
|
1355
1355
|
ref = await browser.findByPlaceholder(client, sessionId, _refs, value, opts);
|
|
1356
1356
|
break;
|
|
1357
|
-
case 'selector':
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
output.printWarning(`selector not found: ${value}`);
|
|
1361
|
-
return { success: false };
|
|
1362
|
-
}
|
|
1363
|
-
output.printSuccess(`Found: ${value}`);
|
|
1364
|
-
return { success: true, data: { html } };
|
|
1365
|
-
}
|
|
1357
|
+
case 'selector':
|
|
1358
|
+
ref = await browser.findBySelector(client, sessionId, _refs, value, opts);
|
|
1359
|
+
break;
|
|
1366
1360
|
case 'testid': {
|
|
1367
1361
|
const sel = await browser.findByTestId(client, sessionId, value);
|
|
1368
1362
|
if (!sel) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Monomind CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|