monomind 1.10.33 → 1.10.35
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/intelligence.cjs +3 -2
- package/.claude/helpers/session.cjs +2 -1
- package/.claude/skills/monomind/.monomind/registry.json +6 -0
- package/package.json +1 -1
- package/packages/@monomind/cli/dist/src/browser/browser.js +16 -12
- package/packages/@monomind/cli/dist/src/browser/cdp.d.ts +1 -1
- package/packages/@monomind/cli/dist/src/browser/cdp.js +4 -2
- package/packages/@monomind/cli/dist/src/browser/find.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/find.js +12 -0
- package/packages/@monomind/cli/dist/src/browser/har.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/browser/har.js +130 -0
- package/packages/@monomind/cli/dist/src/browser/index.d.ts +5 -0
- package/packages/@monomind/cli/dist/src/browser/index.js +5 -0
- package/packages/@monomind/cli/dist/src/browser/network.d.ts +15 -0
- package/packages/@monomind/cli/dist/src/browser/network.js +54 -0
- package/packages/@monomind/cli/dist/src/browser/profiler.d.ts +10 -0
- package/packages/@monomind/cli/dist/src/browser/profiler.js +55 -0
- package/packages/@monomind/cli/dist/src/browser/record.d.ts +21 -0
- package/packages/@monomind/cli/dist/src/browser/record.js +48 -0
- package/packages/@monomind/cli/dist/src/browser/snapshot.js +23 -2
- package/packages/@monomind/cli/dist/src/browser/trace.d.ts +10 -0
- package/packages/@monomind/cli/dist/src/browser/trace.js +72 -0
- package/packages/@monomind/cli/dist/src/browser/types.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/vitals.d.ts +15 -0
- package/packages/@monomind/cli/dist/src/browser/vitals.js +116 -0
- package/packages/@monomind/cli/dist/src/browser/wait.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/browse.js +426 -23
- package/packages/@monomind/cli/package.json +1 -1
|
@@ -10,11 +10,12 @@ const fs = require('fs');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const os = require('os');
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const _CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
14
|
+
const DATA_DIR = path.join(_CWD, '.monomind', 'data');
|
|
14
15
|
const STORE_PATH = path.join(DATA_DIR, 'auto-memory-store.json');
|
|
15
16
|
const RANKED_PATH = path.join(DATA_DIR, 'ranked-context.json');
|
|
16
17
|
const PENDING_PATH = path.join(DATA_DIR, 'pending-insights.jsonl');
|
|
17
|
-
const SESSION_DIR = path.join(
|
|
18
|
+
const SESSION_DIR = path.join(_CWD, '.monomind', 'sessions');
|
|
18
19
|
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
|
19
20
|
|
|
20
21
|
// ── Safety limits (fixes #1530, #1531) ─────────────────────────────────────
|
|
@@ -12,7 +12,8 @@ const platform = os.platform();
|
|
|
12
12
|
const homeDir = os.homedir();
|
|
13
13
|
|
|
14
14
|
function getDataDir() {
|
|
15
|
-
const
|
|
15
|
+
const baseDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
16
|
+
const localDir = path.join(baseDir, '.monomind', 'sessions');
|
|
16
17
|
if (fs.existsSync(path.dirname(localDir))) {
|
|
17
18
|
return localDir;
|
|
18
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.35",
|
|
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,8 @@ 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';
|
|
8
|
+
import { setupDialogAutoHandling } from './dialog.js';
|
|
7
9
|
const DEFAULT_PORT = 9222;
|
|
8
10
|
const LAUNCH_TIMEOUT = 10_000;
|
|
9
11
|
const POLL_INTERVAL = 200;
|
|
@@ -112,6 +114,9 @@ export async function connectToTarget(port, targetId) {
|
|
|
112
114
|
client.send('DOM.enable', {}, sessionId),
|
|
113
115
|
client.send('Accessibility.enable', {}, sessionId),
|
|
114
116
|
]);
|
|
117
|
+
// Wire up auto-capture listeners so console/errors/dialogs work immediately
|
|
118
|
+
enableConsoleCapture(client, sessionId);
|
|
119
|
+
setupDialogAutoHandling(client, sessionId);
|
|
115
120
|
return { client, target, sessionId };
|
|
116
121
|
}
|
|
117
122
|
export async function openUrl(client, sessionId, url) {
|
|
@@ -122,7 +127,7 @@ export async function waitForLoad(client, sessionId, condition = 'load', timeout
|
|
|
122
127
|
if (condition === 'load' || condition === 'domcontentloaded') {
|
|
123
128
|
const event = condition === 'load' ? 'Page.loadEventFired' : 'Page.domContentEventFired';
|
|
124
129
|
await Promise.race([
|
|
125
|
-
client.once(event),
|
|
130
|
+
client.once(event, sessionId),
|
|
126
131
|
sleep(timeout).then(() => { throw new Error(`Timeout waiting for ${condition}`); }),
|
|
127
132
|
]);
|
|
128
133
|
return;
|
|
@@ -137,16 +142,23 @@ async function waitForNetworkIdle(client, sessionId, idleMs) {
|
|
|
137
142
|
return new Promise((resolve) => {
|
|
138
143
|
let pending = 0;
|
|
139
144
|
let timer = null;
|
|
145
|
+
const settle = () => {
|
|
146
|
+
offReq();
|
|
147
|
+
offResp();
|
|
148
|
+
offFail();
|
|
149
|
+
resolve();
|
|
150
|
+
};
|
|
140
151
|
const check = () => {
|
|
141
152
|
if (pending === 0) {
|
|
142
153
|
if (timer)
|
|
143
154
|
clearTimeout(timer);
|
|
144
|
-
timer = setTimeout(
|
|
155
|
+
timer = setTimeout(settle, idleMs);
|
|
145
156
|
}
|
|
146
157
|
else {
|
|
147
|
-
if (timer)
|
|
158
|
+
if (timer) {
|
|
148
159
|
clearTimeout(timer);
|
|
149
|
-
|
|
160
|
+
timer = null;
|
|
161
|
+
}
|
|
150
162
|
}
|
|
151
163
|
};
|
|
152
164
|
const offReq = client.on('Network.requestWillBeSent', (_, sid) => {
|
|
@@ -168,14 +180,6 @@ async function waitForNetworkIdle(client, sessionId, idleMs) {
|
|
|
168
180
|
}
|
|
169
181
|
});
|
|
170
182
|
check();
|
|
171
|
-
// Cleanup after resolution
|
|
172
|
-
const originalResolve = resolve;
|
|
173
|
-
resolve = () => {
|
|
174
|
-
offReq();
|
|
175
|
-
offResp();
|
|
176
|
-
offFail();
|
|
177
|
-
originalResolve();
|
|
178
|
-
};
|
|
179
183
|
});
|
|
180
184
|
}
|
|
181
185
|
export async function getCurrentUrl(client, sessionId) {
|
|
@@ -8,7 +8,7 @@ export declare class CdpClient {
|
|
|
8
8
|
connect(wsUrl: string): Promise<void>;
|
|
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
|
-
once(event: string): Promise<Record<string, unknown>>;
|
|
11
|
+
once(event: string, sessionId?: string): Promise<Record<string, unknown>>;
|
|
12
12
|
close(): void;
|
|
13
13
|
isConnected(): boolean;
|
|
14
14
|
}
|
|
@@ -74,9 +74,11 @@ export class CdpClient {
|
|
|
74
74
|
this.eventListeners.get(event).add(fn);
|
|
75
75
|
return () => this.eventListeners.get(event)?.delete(fn);
|
|
76
76
|
}
|
|
77
|
-
once(event) {
|
|
77
|
+
once(event, sessionId) {
|
|
78
78
|
return new Promise((resolve) => {
|
|
79
|
-
const off = this.on(event, (params) => {
|
|
79
|
+
const off = this.on(event, (params, sid) => {
|
|
80
|
+
if (sessionId !== undefined && sid !== sessionId)
|
|
81
|
+
return;
|
|
80
82
|
off();
|
|
81
83
|
resolve(params);
|
|
82
84
|
});
|
|
@@ -12,6 +12,7 @@ export declare function findBySelector(client: CdpClient, sessionId: string, sel
|
|
|
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>;
|
|
15
|
+
export declare function findByPlaceholder(client: CdpClient, sessionId: string, refs: Map<string, ElementRef>, placeholder: string, options?: FindOptions): Promise<ElementRef | null>;
|
|
15
16
|
export declare function findByTestId(client: CdpClient, sessionId: string, testId: string): Promise<string | null>;
|
|
16
17
|
export declare function isVisible(client: CdpClient, sessionId: string, ref: ElementRef): Promise<boolean>;
|
|
17
18
|
export declare function isEnabled(client: CdpClient, sessionId: string, ref: ElementRef): Promise<boolean>;
|
|
@@ -43,6 +43,18 @@ export async function findByText(client, sessionId, refs, text, options = {}) {
|
|
|
43
43
|
export async function findByLabel(client, sessionId, refs, label, options = {}) {
|
|
44
44
|
return findByText(client, sessionId, refs, label, options);
|
|
45
45
|
}
|
|
46
|
+
export async function findByPlaceholder(client, sessionId, refs, placeholder, options = {}) {
|
|
47
|
+
const lower = placeholder.toLowerCase();
|
|
48
|
+
const candidates = [...refs.values()].filter((r) => {
|
|
49
|
+
const desc = (r.description ?? '').toLowerCase();
|
|
50
|
+
return options.exact ? desc === lower : desc.includes(lower);
|
|
51
|
+
});
|
|
52
|
+
if (options.nth !== undefined)
|
|
53
|
+
return candidates[options.nth - 1] ?? null;
|
|
54
|
+
if (options.last)
|
|
55
|
+
return candidates[candidates.length - 1];
|
|
56
|
+
return candidates[0] ?? null;
|
|
57
|
+
}
|
|
46
58
|
export async function findByTestId(client, sessionId, testId) {
|
|
47
59
|
const selectors = [
|
|
48
60
|
`[data-testid="${testId}"]`,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CdpClient } from './cdp.js';
|
|
2
|
+
interface HarRequest {
|
|
3
|
+
id: string;
|
|
4
|
+
url: string;
|
|
5
|
+
method: string;
|
|
6
|
+
status: number;
|
|
7
|
+
statusText: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
requestHeaders: Record<string, string>;
|
|
10
|
+
responseHeaders: Record<string, string>;
|
|
11
|
+
startTime: number;
|
|
12
|
+
endTime: number;
|
|
13
|
+
size: number;
|
|
14
|
+
encodedSize: number;
|
|
15
|
+
fromCache: boolean;
|
|
16
|
+
responseBody?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function startHarRecording(client: CdpClient, sessionId: string): Promise<void>;
|
|
19
|
+
export declare function stopHarRecording(client: CdpClient, sessionId: string, outputPath?: string, captureResponseBodies?: boolean): Promise<string>;
|
|
20
|
+
export declare function getHarStatus(sessionId: string): {
|
|
21
|
+
recording: boolean;
|
|
22
|
+
requestCount: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function getRequests(sessionId: string): Partial<HarRequest>[];
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=har.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
const _sessions = new Map();
|
|
5
|
+
export async function startHarRecording(client, sessionId) {
|
|
6
|
+
if (_sessions.has(sessionId))
|
|
7
|
+
throw new Error('HAR recording already in progress');
|
|
8
|
+
const requests = new Map();
|
|
9
|
+
const startTime = Date.now();
|
|
10
|
+
const offReq = client.on('Network.requestWillBeSent', (params, sid) => {
|
|
11
|
+
if (sid !== sessionId)
|
|
12
|
+
return;
|
|
13
|
+
const p = params;
|
|
14
|
+
requests.set(p.requestId, {
|
|
15
|
+
id: p.requestId,
|
|
16
|
+
url: p.request.url,
|
|
17
|
+
method: p.request.method,
|
|
18
|
+
requestHeaders: p.request.headers,
|
|
19
|
+
startTime: p.timestamp * 1000,
|
|
20
|
+
fromCache: false,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
const offResp = client.on('Network.responseReceived', (params, sid) => {
|
|
24
|
+
if (sid !== sessionId)
|
|
25
|
+
return;
|
|
26
|
+
const p = params;
|
|
27
|
+
const entry = requests.get(p.requestId);
|
|
28
|
+
if (entry) {
|
|
29
|
+
entry.status = p.response.status;
|
|
30
|
+
entry.statusText = p.response.statusText;
|
|
31
|
+
entry.mimeType = p.response.mimeType;
|
|
32
|
+
entry.responseHeaders = p.response.headers;
|
|
33
|
+
entry.fromCache = p.response.fromDiskCache || p.response.fromServiceWorker;
|
|
34
|
+
entry.endTime = p.timestamp * 1000;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const offFinished = client.on('Network.loadingFinished', (params, sid) => {
|
|
38
|
+
if (sid !== sessionId)
|
|
39
|
+
return;
|
|
40
|
+
const p = params;
|
|
41
|
+
const entry = requests.get(p.requestId);
|
|
42
|
+
if (entry) {
|
|
43
|
+
entry.encodedSize = p.encodedDataLength;
|
|
44
|
+
entry.endTime = p.timestamp * 1000;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
_sessions.set(sessionId, { requests, offReq, offResp, offFinished, startTime });
|
|
48
|
+
}
|
|
49
|
+
export async function stopHarRecording(client, sessionId, outputPath, captureResponseBodies = false) {
|
|
50
|
+
const state = _sessions.get(sessionId);
|
|
51
|
+
if (!state)
|
|
52
|
+
throw new Error('No active HAR recording for this session');
|
|
53
|
+
state.offReq();
|
|
54
|
+
state.offResp();
|
|
55
|
+
state.offFinished();
|
|
56
|
+
// Optionally fetch response bodies
|
|
57
|
+
if (captureResponseBodies) {
|
|
58
|
+
for (const [reqId, entry] of state.requests.entries()) {
|
|
59
|
+
try {
|
|
60
|
+
const body = await client.send('Network.getResponseBody', { requestId: reqId }, sessionId);
|
|
61
|
+
entry.responseBody = body.base64Encoded
|
|
62
|
+
? Buffer.from(body.body, 'base64').toString('utf8')
|
|
63
|
+
: body.body;
|
|
64
|
+
entry.size = entry.responseBody.length;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// body may not be available for cached/redirected responses
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
_sessions.delete(sessionId);
|
|
72
|
+
const har = buildHar(Array.from(state.requests.values()), state.startTime);
|
|
73
|
+
const path = outputPath ?? join(tmpdir(), `monomind-har-${Date.now()}.har`);
|
|
74
|
+
await writeFile(path, JSON.stringify(har, null, 2));
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
export function getHarStatus(sessionId) {
|
|
78
|
+
const state = _sessions.get(sessionId);
|
|
79
|
+
return { recording: !!state, requestCount: state?.requests.size ?? 0 };
|
|
80
|
+
}
|
|
81
|
+
export function getRequests(sessionId) {
|
|
82
|
+
const state = _sessions.get(sessionId);
|
|
83
|
+
return state ? Array.from(state.requests.values()) : [];
|
|
84
|
+
}
|
|
85
|
+
function buildHar(entries, startTime) {
|
|
86
|
+
return {
|
|
87
|
+
log: {
|
|
88
|
+
version: '1.2',
|
|
89
|
+
creator: { name: 'monomind browse', version: '1.0.0' },
|
|
90
|
+
pages: [{
|
|
91
|
+
startedDateTime: new Date(startTime).toISOString(),
|
|
92
|
+
id: 'page_1',
|
|
93
|
+
title: '',
|
|
94
|
+
pageTimings: {},
|
|
95
|
+
}],
|
|
96
|
+
entries: entries.map((e) => ({
|
|
97
|
+
startedDateTime: new Date(e.startTime ?? startTime).toISOString(),
|
|
98
|
+
time: (e.endTime ?? e.startTime ?? startTime) - (e.startTime ?? startTime),
|
|
99
|
+
request: {
|
|
100
|
+
method: e.method ?? 'GET',
|
|
101
|
+
url: e.url ?? '',
|
|
102
|
+
httpVersion: 'HTTP/1.1',
|
|
103
|
+
cookies: [],
|
|
104
|
+
headers: Object.entries(e.requestHeaders ?? {}).map(([name, value]) => ({ name, value })),
|
|
105
|
+
queryString: [],
|
|
106
|
+
headersSize: -1,
|
|
107
|
+
bodySize: -1,
|
|
108
|
+
},
|
|
109
|
+
response: {
|
|
110
|
+
status: e.status ?? 0,
|
|
111
|
+
statusText: e.statusText ?? '',
|
|
112
|
+
httpVersion: 'HTTP/1.1',
|
|
113
|
+
cookies: [],
|
|
114
|
+
headers: Object.entries(e.responseHeaders ?? {}).map(([name, value]) => ({ name, value })),
|
|
115
|
+
content: {
|
|
116
|
+
size: e.size ?? -1,
|
|
117
|
+
mimeType: e.mimeType ?? 'application/octet-stream',
|
|
118
|
+
text: e.responseBody,
|
|
119
|
+
},
|
|
120
|
+
redirectURL: '',
|
|
121
|
+
headersSize: -1,
|
|
122
|
+
bodySize: e.encodedSize ?? -1,
|
|
123
|
+
},
|
|
124
|
+
cache: {},
|
|
125
|
+
timings: { send: 0, wait: 0, receive: 0 },
|
|
126
|
+
})),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=har.js.map
|
|
@@ -15,4 +15,9 @@ export * from './find.js';
|
|
|
15
15
|
export * from './pdf.js';
|
|
16
16
|
export * from './emulation.js';
|
|
17
17
|
export * from './batch.js';
|
|
18
|
+
export * from './record.js';
|
|
19
|
+
export * from './trace.js';
|
|
20
|
+
export * from './profiler.js';
|
|
21
|
+
export * from './vitals.js';
|
|
22
|
+
export * from './har.js';
|
|
18
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -15,4 +15,9 @@ export * from './find.js';
|
|
|
15
15
|
export * from './pdf.js';
|
|
16
16
|
export * from './emulation.js';
|
|
17
17
|
export * from './batch.js';
|
|
18
|
+
export * from './record.js';
|
|
19
|
+
export * from './trace.js';
|
|
20
|
+
export * from './profiler.js';
|
|
21
|
+
export * from './vitals.js';
|
|
22
|
+
export * from './har.js';
|
|
18
23
|
//# sourceMappingURL=index.js.map
|
|
@@ -6,6 +6,21 @@ export declare function clearCookies(client: CdpClient, sessionId: string): Prom
|
|
|
6
6
|
export declare function setExtraHeaders(client: CdpClient, sessionId: string, headers: Record<string, string>): Promise<void>;
|
|
7
7
|
export declare function enableInterception(client: CdpClient, sessionId: string): Promise<void>;
|
|
8
8
|
export declare function setupRoutes(client: CdpClient, sessionId: string, routes: NetworkRoute[]): Promise<void>;
|
|
9
|
+
export declare function startRequestCapture(client: CdpClient, sessionId: string): void;
|
|
10
|
+
export declare function stopRequestCapture(sessionId: string): void;
|
|
11
|
+
export declare function getCapturedRequests(sessionId: string): {
|
|
12
|
+
url: string;
|
|
13
|
+
method: string;
|
|
14
|
+
status?: number;
|
|
15
|
+
mimeType?: string;
|
|
16
|
+
requestHeaders?: Record<string, string>;
|
|
17
|
+
responseHeaders?: Record<string, string>;
|
|
18
|
+
startTime: number;
|
|
19
|
+
endTime?: number;
|
|
20
|
+
encodedSize?: number;
|
|
21
|
+
}[];
|
|
22
|
+
export declare function clearCapturedRequests(sessionId: string): void;
|
|
23
|
+
export declare function disableInterception(client: CdpClient, sessionId: string): Promise<void>;
|
|
9
24
|
export declare function getLocalStorage(client: CdpClient, sessionId: string): Promise<Record<string, string>>;
|
|
10
25
|
export declare function setLocalStorage(client: CdpClient, sessionId: string, data: Record<string, string>): Promise<void>;
|
|
11
26
|
//# sourceMappingURL=network.d.ts.map
|
|
@@ -50,6 +50,60 @@ export async function setupRoutes(client, sessionId, routes) {
|
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
+
const _capturedRequests = new Map();
|
|
54
|
+
const _captureListeners = new Map();
|
|
55
|
+
export function startRequestCapture(client, sessionId) {
|
|
56
|
+
if (_capturedRequests.has(sessionId))
|
|
57
|
+
return;
|
|
58
|
+
const list = [];
|
|
59
|
+
_capturedRequests.set(sessionId, list);
|
|
60
|
+
const offReq = client.on('Network.requestWillBeSent', (params, sid) => {
|
|
61
|
+
if (sid !== sessionId)
|
|
62
|
+
return;
|
|
63
|
+
const p = params;
|
|
64
|
+
list.push({ url: p.request.url, method: p.request.method, requestHeaders: p.request.headers, startTime: p.timestamp * 1000 });
|
|
65
|
+
});
|
|
66
|
+
const offResp = client.on('Network.responseReceived', (params, sid) => {
|
|
67
|
+
if (sid !== sessionId)
|
|
68
|
+
return;
|
|
69
|
+
const p = params;
|
|
70
|
+
const entry = list.find((r) => r.url === p.response.url && !r.status);
|
|
71
|
+
if (entry) {
|
|
72
|
+
entry.status = p.response.status;
|
|
73
|
+
entry.mimeType = p.response.mimeType;
|
|
74
|
+
entry.responseHeaders = p.response.headers;
|
|
75
|
+
entry.endTime = p.timestamp * 1000;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const offFinished = client.on('Network.loadingFinished', (params, sid) => {
|
|
79
|
+
if (sid !== sessionId)
|
|
80
|
+
return;
|
|
81
|
+
const p = params;
|
|
82
|
+
const entry = list[list.length - 1];
|
|
83
|
+
if (entry) {
|
|
84
|
+
entry.encodedSize = p.encodedDataLength;
|
|
85
|
+
entry.endTime = p.timestamp * 1000;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
_captureListeners.set(sessionId, [offReq, offResp, offFinished]);
|
|
89
|
+
}
|
|
90
|
+
export function stopRequestCapture(sessionId) {
|
|
91
|
+
const offs = _captureListeners.get(sessionId);
|
|
92
|
+
if (offs) {
|
|
93
|
+
for (const off of offs)
|
|
94
|
+
off();
|
|
95
|
+
_captureListeners.delete(sessionId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function getCapturedRequests(sessionId) {
|
|
99
|
+
return _capturedRequests.get(sessionId) ?? [];
|
|
100
|
+
}
|
|
101
|
+
export function clearCapturedRequests(sessionId) {
|
|
102
|
+
_capturedRequests.set(sessionId, []);
|
|
103
|
+
}
|
|
104
|
+
export async function disableInterception(client, sessionId) {
|
|
105
|
+
await client.send('Fetch.disable', {}, sessionId);
|
|
106
|
+
}
|
|
53
107
|
export async function getLocalStorage(client, sessionId) {
|
|
54
108
|
const result = await client.send('Runtime.evaluate', {
|
|
55
109
|
expression: 'JSON.stringify(Object.fromEntries(Object.entries(localStorage)))',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CdpClient } from './cdp.js';
|
|
2
|
+
export interface ProfilerOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
samplingInterval?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function startCpuProfile(client: CdpClient, sessionId: string, options?: ProfilerOptions): Promise<void>;
|
|
7
|
+
export declare function stopCpuProfile(client: CdpClient, sessionId: string, outputPath?: string): Promise<string>;
|
|
8
|
+
export declare function isProfilingActive(sessionId: string): boolean;
|
|
9
|
+
export declare function startHeapSnapshot(client: CdpClient, sessionId: string, outputPath?: string): Promise<string>;
|
|
10
|
+
//# sourceMappingURL=profiler.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
const _sessions = new Set();
|
|
5
|
+
export async function startCpuProfile(client, sessionId, options = {}) {
|
|
6
|
+
if (_sessions.has(sessionId)) {
|
|
7
|
+
throw new Error('CPU profiler already running for this session');
|
|
8
|
+
}
|
|
9
|
+
await client.send('Profiler.enable', {}, sessionId);
|
|
10
|
+
if (options.samplingInterval !== undefined) {
|
|
11
|
+
await client.send('Profiler.setSamplingInterval', { interval: options.samplingInterval }, sessionId);
|
|
12
|
+
}
|
|
13
|
+
await client.send('Profiler.start', {}, sessionId);
|
|
14
|
+
_sessions.add(sessionId);
|
|
15
|
+
}
|
|
16
|
+
export async function stopCpuProfile(client, sessionId, outputPath) {
|
|
17
|
+
if (!_sessions.has(sessionId)) {
|
|
18
|
+
throw new Error('No active CPU profiler for this session');
|
|
19
|
+
}
|
|
20
|
+
const result = await client.send('Profiler.stop', {}, sessionId);
|
|
21
|
+
await client.send('Profiler.disable', {}, sessionId);
|
|
22
|
+
_sessions.delete(sessionId);
|
|
23
|
+
const path = outputPath ?? join(tmpdir(), `monomind-profile-${Date.now()}.cpuprofile`);
|
|
24
|
+
await writeFile(path, JSON.stringify(result.profile));
|
|
25
|
+
return path;
|
|
26
|
+
}
|
|
27
|
+
export function isProfilingActive(sessionId) {
|
|
28
|
+
return _sessions.has(sessionId);
|
|
29
|
+
}
|
|
30
|
+
export async function startHeapSnapshot(client, sessionId, outputPath) {
|
|
31
|
+
await client.send('HeapProfiler.enable', {}, sessionId);
|
|
32
|
+
const chunks = [];
|
|
33
|
+
const off = client.on('HeapProfiler.addHeapSnapshotChunk', (params, sid) => {
|
|
34
|
+
if (sid !== sessionId)
|
|
35
|
+
return;
|
|
36
|
+
chunks.push(params.chunk);
|
|
37
|
+
});
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
const off2 = client.on('HeapProfiler.reportHeapSnapshotProgress', (params, sid) => {
|
|
40
|
+
if (sid !== sessionId)
|
|
41
|
+
return;
|
|
42
|
+
if (params.finished) {
|
|
43
|
+
off2();
|
|
44
|
+
resolve();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
client.send('HeapProfiler.takeHeapSnapshot', { reportProgress: true }, sessionId).catch(() => { });
|
|
48
|
+
});
|
|
49
|
+
off();
|
|
50
|
+
await client.send('HeapProfiler.disable', {}, sessionId);
|
|
51
|
+
const path = outputPath ?? join(tmpdir(), `monomind-heap-${Date.now()}.heapsnapshot`);
|
|
52
|
+
await writeFile(path, chunks.join(''));
|
|
53
|
+
return path;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=profiler.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CdpClient } from './cdp.js';
|
|
2
|
+
export interface RecordOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
format?: 'jpeg' | 'png' | 'webp';
|
|
5
|
+
quality?: number;
|
|
6
|
+
everyNthFrame?: number;
|
|
7
|
+
maxWidth?: number;
|
|
8
|
+
maxHeight?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RecordingState {
|
|
11
|
+
frames: string[];
|
|
12
|
+
offScreencast: (() => void) | null;
|
|
13
|
+
}
|
|
14
|
+
export declare function startRecording(client: CdpClient, sessionId: string, options?: RecordOptions): Promise<void>;
|
|
15
|
+
export declare function stopRecording(client: CdpClient, sessionId: string, outputPath?: string): Promise<string>;
|
|
16
|
+
export declare function getRecordingStatus(sessionId: string): {
|
|
17
|
+
recording: boolean;
|
|
18
|
+
frames: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function saveFrameAsPng(client: CdpClient, sessionId: string, frameIndex: number, outputPath: string): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
const _sessions = new Map();
|
|
5
|
+
export async function startRecording(client, sessionId, options = {}) {
|
|
6
|
+
if (_sessions.has(sessionId)) {
|
|
7
|
+
throw new Error('Recording already in progress for this session');
|
|
8
|
+
}
|
|
9
|
+
const state = { frames: [], offScreencast: null };
|
|
10
|
+
_sessions.set(sessionId, state);
|
|
11
|
+
state.offScreencast = client.on('Page.screencastFrame', async (params, sid) => {
|
|
12
|
+
if (sid !== sessionId)
|
|
13
|
+
return;
|
|
14
|
+
const { data, sessionId: frameSessionId } = params;
|
|
15
|
+
state.frames.push(data);
|
|
16
|
+
await client.send('Page.screencastFrameAck', { sessionId: frameSessionId }, sessionId).catch(() => { });
|
|
17
|
+
});
|
|
18
|
+
await client.send('Page.startScreencast', {
|
|
19
|
+
format: options.format ?? 'jpeg',
|
|
20
|
+
quality: options.quality ?? 80,
|
|
21
|
+
everyNthFrame: options.everyNthFrame ?? 1,
|
|
22
|
+
...(options.maxWidth ? { maxWidth: options.maxWidth } : {}),
|
|
23
|
+
...(options.maxHeight ? { maxHeight: options.maxHeight } : {}),
|
|
24
|
+
}, sessionId);
|
|
25
|
+
}
|
|
26
|
+
export async function stopRecording(client, sessionId, outputPath) {
|
|
27
|
+
const state = _sessions.get(sessionId);
|
|
28
|
+
if (!state)
|
|
29
|
+
throw new Error('No active recording for this session');
|
|
30
|
+
await client.send('Page.stopScreencast', {}, sessionId);
|
|
31
|
+
state.offScreencast?.();
|
|
32
|
+
_sessions.delete(sessionId);
|
|
33
|
+
const path = outputPath ?? join(tmpdir(), `monomind-screencast-${Date.now()}.frames.json`);
|
|
34
|
+
await writeFile(path, JSON.stringify({ frameCount: state.frames.length, frames: state.frames }));
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
export function getRecordingStatus(sessionId) {
|
|
38
|
+
const state = _sessions.get(sessionId);
|
|
39
|
+
return { recording: !!state, frames: state?.frames.length ?? 0 };
|
|
40
|
+
}
|
|
41
|
+
export async function saveFrameAsPng(client, sessionId, frameIndex, outputPath) {
|
|
42
|
+
const state = _sessions.get(sessionId);
|
|
43
|
+
if (!state || frameIndex >= state.frames.length) {
|
|
44
|
+
throw new Error(`Frame ${frameIndex} not available`);
|
|
45
|
+
}
|
|
46
|
+
await writeFile(outputPath, Buffer.from(state.frames[frameIndex], 'base64'));
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { INTERACTIVE_ROLES } from './types.js';
|
|
2
2
|
import { getCurrentUrl, getCurrentTitle } from './browser.js';
|
|
3
3
|
export async function captureSnapshot(client, sessionId, options = {}) {
|
|
4
|
-
const { interactiveOnly = false, compact = false } = options;
|
|
5
|
-
|
|
4
|
+
const { interactiveOnly = false, compact = false, maxDepth, selector } = options;
|
|
5
|
+
// If a selector scope is requested, resolve it to a backendDOMNodeId and use getPartialAXTree
|
|
6
|
+
let nodes;
|
|
7
|
+
if (selector) {
|
|
8
|
+
const nodeResult = await client.send('Runtime.evaluate', { expression: `document.querySelector(${JSON.stringify(selector)})?.getAttribute('data-ax-node-id') ?? null`, returnByValue: true }, sessionId);
|
|
9
|
+
// Use DOM.querySelector to find the backend node
|
|
10
|
+
const doc = await client.send('DOM.getDocument', {}, sessionId);
|
|
11
|
+
const found = await client.send('DOM.querySelector', { nodeId: doc.root.nodeId, selector }, sessionId).catch(() => ({ nodeId: 0 }));
|
|
12
|
+
if (found.nodeId) {
|
|
13
|
+
const partial = await client.send('Accessibility.getPartialAXTree', { nodeId: found.nodeId, fetchRelatives: true }, sessionId).catch(async () => client.send('Accessibility.getFullAXTree', {}, sessionId));
|
|
14
|
+
nodes = partial.nodes;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
const full = await client.send('Accessibility.getFullAXTree', {}, sessionId);
|
|
18
|
+
nodes = full.nodes;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const full = await client.send('Accessibility.getFullAXTree', {}, sessionId);
|
|
23
|
+
nodes = full.nodes;
|
|
24
|
+
}
|
|
6
25
|
const url = await getCurrentUrl(client, sessionId);
|
|
7
26
|
const title = await getCurrentTitle(client, sessionId);
|
|
8
27
|
const refs = new Map();
|
|
@@ -14,6 +33,8 @@ export async function captureSnapshot(client, sessionId, options = {}) {
|
|
|
14
33
|
const processNode = (node, depth) => {
|
|
15
34
|
if (node.ignored)
|
|
16
35
|
return;
|
|
36
|
+
if (maxDepth !== undefined && depth > maxDepth)
|
|
37
|
+
return;
|
|
17
38
|
const role = node.role?.value ?? 'generic';
|
|
18
39
|
const name = node.name?.value ?? '';
|
|
19
40
|
const description = node.description?.value;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CdpClient } from './cdp.js';
|
|
2
|
+
export interface TraceOptions {
|
|
3
|
+
path?: string;
|
|
4
|
+
categories?: string[];
|
|
5
|
+
screenshots?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function startTrace(client: CdpClient, sessionId: string, options?: TraceOptions): Promise<void>;
|
|
8
|
+
export declare function stopTrace(client: CdpClient, sessionId: string, outputPath?: string): Promise<string>;
|
|
9
|
+
export declare function getTraceStatus(sessionId: string): boolean;
|
|
10
|
+
//# sourceMappingURL=trace.d.ts.map
|