auto-feedback 0.1.0
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/README.md +180 -0
- package/build/capture/console-collector.d.ts +16 -0
- package/build/capture/console-collector.js +43 -0
- package/build/capture/error-collector.d.ts +15 -0
- package/build/capture/error-collector.js +47 -0
- package/build/capture/network-collector.d.ts +16 -0
- package/build/capture/network-collector.js +76 -0
- package/build/capture/process-collector.d.ts +16 -0
- package/build/capture/process-collector.js +48 -0
- package/build/capture/types.d.ts +61 -0
- package/build/capture/types.js +5 -0
- package/build/index.d.ts +6 -0
- package/build/index.js +41 -0
- package/build/interaction/selectors.d.ts +26 -0
- package/build/interaction/selectors.js +84 -0
- package/build/interaction/types.d.ts +56 -0
- package/build/interaction/types.js +5 -0
- package/build/process/cleanup.d.ts +23 -0
- package/build/process/cleanup.js +50 -0
- package/build/process/launcher.d.ts +22 -0
- package/build/process/launcher.js +54 -0
- package/build/process/monitor.d.ts +14 -0
- package/build/process/monitor.js +67 -0
- package/build/process/types.d.ts +84 -0
- package/build/process/types.js +5 -0
- package/build/screenshot/auto-capture.d.ts +14 -0
- package/build/screenshot/auto-capture.js +38 -0
- package/build/screenshot/capture.d.ts +21 -0
- package/build/screenshot/capture.js +48 -0
- package/build/screenshot/optimize.d.ts +19 -0
- package/build/screenshot/optimize.js +28 -0
- package/build/screenshot/types.d.ts +43 -0
- package/build/screenshot/types.js +4 -0
- package/build/server.d.ts +10 -0
- package/build/server.js +18 -0
- package/build/session-manager.d.ts +119 -0
- package/build/session-manager.js +284 -0
- package/build/tools/check-port.d.ts +10 -0
- package/build/tools/check-port.js +40 -0
- package/build/tools/click-element.d.ts +13 -0
- package/build/tools/click-element.js +118 -0
- package/build/tools/get-console-logs.d.ts +7 -0
- package/build/tools/get-console-logs.js +55 -0
- package/build/tools/get-element-state.d.ts +14 -0
- package/build/tools/get-element-state.js +116 -0
- package/build/tools/get-errors.d.ts +7 -0
- package/build/tools/get-errors.js +40 -0
- package/build/tools/get-network-logs.d.ts +7 -0
- package/build/tools/get-network-logs.js +58 -0
- package/build/tools/get-process-output.d.ts +7 -0
- package/build/tools/get-process-output.js +55 -0
- package/build/tools/get-screenshot.d.ts +7 -0
- package/build/tools/get-screenshot.js +32 -0
- package/build/tools/index.d.ts +9 -0
- package/build/tools/index.js +117 -0
- package/build/tools/launch-electron.d.ts +13 -0
- package/build/tools/launch-electron.js +97 -0
- package/build/tools/launch-web-server.d.ts +13 -0
- package/build/tools/launch-web-server.js +88 -0
- package/build/tools/launch-windows-exe.d.ts +13 -0
- package/build/tools/launch-windows-exe.js +81 -0
- package/build/tools/navigate.d.ts +13 -0
- package/build/tools/navigate.js +137 -0
- package/build/tools/run-workflow.d.ts +14 -0
- package/build/tools/run-workflow.js +207 -0
- package/build/tools/screenshot-desktop.d.ts +13 -0
- package/build/tools/screenshot-desktop.js +80 -0
- package/build/tools/screenshot-electron.d.ts +13 -0
- package/build/tools/screenshot-electron.js +72 -0
- package/build/tools/screenshot-web.d.ts +13 -0
- package/build/tools/screenshot-web.js +129 -0
- package/build/tools/stop-process.d.ts +14 -0
- package/build/tools/stop-process.js +41 -0
- package/build/tools/type-text.d.ts +13 -0
- package/build/tools/type-text.js +137 -0
- package/build/tools/wait-for-element.d.ts +14 -0
- package/build/tools/wait-for-element.js +93 -0
- package/build/types/index.d.ts +31 -0
- package/build/types/index.js +4 -0
- package/build/utils/errors.d.ts +26 -0
- package/build/utils/errors.js +62 -0
- package/build/utils/shutdown.d.ts +16 -0
- package/build/utils/shutdown.js +34 -0
- package/build/workflow/assertions.d.ts +25 -0
- package/build/workflow/assertions.js +326 -0
- package/build/workflow/executor.d.ts +34 -0
- package/build/workflow/executor.js +269 -0
- package/build/workflow/types.d.ts +95 -0
- package/build/workflow/types.js +6 -0
- package/package.json +36 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selector resolution and page discovery
|
|
3
|
+
* Converts string selectors to Playwright Locators and discovers active pages
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Convert a selector string to a Playwright Locator.
|
|
7
|
+
*
|
|
8
|
+
* Supported formats:
|
|
9
|
+
* - CSS selectors: #id, .class, div > span (passed directly to Playwright)
|
|
10
|
+
* - Text: text=Click me (Playwright native)
|
|
11
|
+
* - Role: role=button[name='Submit'] (Playwright native)
|
|
12
|
+
* - XPath: xpath=//div[@id='main'] (Playwright native)
|
|
13
|
+
* - Test ID: testid=my-btn (resolved via getByTestId)
|
|
14
|
+
*/
|
|
15
|
+
export function resolveSelector(page, selector) {
|
|
16
|
+
// testid= is the one prefix Playwright doesn't handle natively
|
|
17
|
+
if (selector.startsWith("testid=")) {
|
|
18
|
+
const testId = selector.slice("testid=".length);
|
|
19
|
+
return page.getByTestId(testId);
|
|
20
|
+
}
|
|
21
|
+
// All other selectors: CSS, text=, role=, xpath= — Playwright handles natively
|
|
22
|
+
return page.locator(selector);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Find the active page for interaction in a session.
|
|
26
|
+
*
|
|
27
|
+
* - If pageIdentifier is provided, looks up that specific page
|
|
28
|
+
* - If omitted and session has exactly one page, auto-selects it
|
|
29
|
+
* - If omitted and session has 0 or >1 pages, returns actionable error
|
|
30
|
+
*/
|
|
31
|
+
export function getActivePage(sessionManager, sessionId, pageIdentifier) {
|
|
32
|
+
// Validate session exists
|
|
33
|
+
const session = sessionManager.get(sessionId);
|
|
34
|
+
if (!session) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
error: `Session not found: ${sessionId}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (pageIdentifier) {
|
|
41
|
+
// Look up specific page by identifier
|
|
42
|
+
const ref = sessionManager.getPageRef(sessionId, pageIdentifier);
|
|
43
|
+
if (!ref) {
|
|
44
|
+
const refs = sessionManager.getPageRefs(sessionId);
|
|
45
|
+
const available = refs.map((r) => r.type === "electron" ? "electron" : r.url ?? "unknown");
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: `Page not found: ${pageIdentifier}`,
|
|
49
|
+
availablePages: available.length > 0 ? available : undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
page: ref.page,
|
|
55
|
+
identifier: pageIdentifier,
|
|
56
|
+
type: ref.type,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Auto-discover: no pageIdentifier provided
|
|
60
|
+
const refs = sessionManager.getPageRefs(sessionId);
|
|
61
|
+
if (refs.length === 0) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: "No pages available in this session. Launch an app first with launch_web_server, launch_electron, or screenshot_web.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (refs.length === 1) {
|
|
68
|
+
const ref = refs[0];
|
|
69
|
+
const identifier = ref.type === "electron" ? "electron" : ref.url ?? "unknown";
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
page: ref.page,
|
|
73
|
+
identifier,
|
|
74
|
+
type: ref.type,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Multiple pages — require explicit selection
|
|
78
|
+
const available = refs.map((r) => r.type === "electron" ? "electron" : r.url ?? "unknown");
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: `Multiple pages found (${refs.length}). Specify pageIdentifier to target a specific page.`,
|
|
82
|
+
availablePages: available,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction-specific type definitions
|
|
3
|
+
* Used by click_element, type_text, and navigation tools
|
|
4
|
+
*/
|
|
5
|
+
import type { Page } from "playwright";
|
|
6
|
+
/** Options for click_element tool */
|
|
7
|
+
export interface ClickOptions {
|
|
8
|
+
button?: "left" | "right" | "middle";
|
|
9
|
+
clickCount?: number;
|
|
10
|
+
position?: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
};
|
|
14
|
+
force?: boolean;
|
|
15
|
+
timeout?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Options for type_text tool */
|
|
18
|
+
export interface TypeOptions {
|
|
19
|
+
pressSequentially?: boolean;
|
|
20
|
+
delay?: number;
|
|
21
|
+
clear?: boolean;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Navigate tool actions */
|
|
25
|
+
export type NavigateAction = "goto" | "back" | "forward";
|
|
26
|
+
/** Result of finding the active page for interaction */
|
|
27
|
+
export type PageDiscoveryResult = {
|
|
28
|
+
success: true;
|
|
29
|
+
page: Page;
|
|
30
|
+
identifier: string;
|
|
31
|
+
type: "web" | "electron";
|
|
32
|
+
} | {
|
|
33
|
+
success: false;
|
|
34
|
+
error: string;
|
|
35
|
+
availablePages?: string[];
|
|
36
|
+
};
|
|
37
|
+
/** State values for locator.waitFor() */
|
|
38
|
+
export type WaitForState = "visible" | "hidden" | "attached" | "detached";
|
|
39
|
+
/** Shape of the get_element_state tool response */
|
|
40
|
+
export interface ElementStateResult {
|
|
41
|
+
selector: string;
|
|
42
|
+
visible: boolean;
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
editable: boolean;
|
|
45
|
+
checked: boolean | null;
|
|
46
|
+
textContent: string | null;
|
|
47
|
+
innerText: string;
|
|
48
|
+
inputValue: string | null;
|
|
49
|
+
attributes: Record<string, string | null>;
|
|
50
|
+
boundingBox: {
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
} | null;
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process tree cleanup utility
|
|
3
|
+
* Wraps tree-kill with timeout for best-effort process termination
|
|
4
|
+
*/
|
|
5
|
+
import { ChildProcess } from "child_process";
|
|
6
|
+
import { Resource } from "../types/index.js";
|
|
7
|
+
import { ProcessType } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Kill a process and all its children (best-effort)
|
|
10
|
+
* Always resolves -- cleanup should never block shutdown
|
|
11
|
+
*
|
|
12
|
+
* @param pid - OS process ID to kill
|
|
13
|
+
* @param timeoutMs - Maximum time to wait for kill (default 5000ms)
|
|
14
|
+
*/
|
|
15
|
+
export declare function killProcessTree(pid: number, timeoutMs?: number): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Create a Resource that cleans up a child process on disposal
|
|
18
|
+
* Integrates with SessionManager's resource tracking
|
|
19
|
+
*
|
|
20
|
+
* @param childProcess - The spawned child process
|
|
21
|
+
* @param _type - Process type (for future use in type-specific cleanup)
|
|
22
|
+
*/
|
|
23
|
+
export declare function createProcessResource(childProcess: ChildProcess, _type: ProcessType): Resource;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process tree cleanup utility
|
|
3
|
+
* Wraps tree-kill with timeout for best-effort process termination
|
|
4
|
+
*/
|
|
5
|
+
import treeKill from "tree-kill";
|
|
6
|
+
/**
|
|
7
|
+
* Kill a process and all its children (best-effort)
|
|
8
|
+
* Always resolves -- cleanup should never block shutdown
|
|
9
|
+
*
|
|
10
|
+
* @param pid - OS process ID to kill
|
|
11
|
+
* @param timeoutMs - Maximum time to wait for kill (default 5000ms)
|
|
12
|
+
*/
|
|
13
|
+
export function killProcessTree(pid, timeoutMs = 5000) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const timeout = setTimeout(() => {
|
|
16
|
+
console.error(`[Process] Tree-kill timeout after ${timeoutMs}ms for PID ${pid}, continuing anyway`);
|
|
17
|
+
resolve();
|
|
18
|
+
}, timeoutMs);
|
|
19
|
+
treeKill(pid, "SIGTERM", (error) => {
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
if (error) {
|
|
22
|
+
console.error(`[Process] Tree-kill error for PID ${pid}: ${error.message} (process may already be dead)`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error(`[Process] Killed process tree for PID ${pid}`);
|
|
26
|
+
}
|
|
27
|
+
resolve();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a Resource that cleans up a child process on disposal
|
|
33
|
+
* Integrates with SessionManager's resource tracking
|
|
34
|
+
*
|
|
35
|
+
* @param childProcess - The spawned child process
|
|
36
|
+
* @param _type - Process type (for future use in type-specific cleanup)
|
|
37
|
+
*/
|
|
38
|
+
export function createProcessResource(childProcess, _type) {
|
|
39
|
+
return {
|
|
40
|
+
cleanup: async () => {
|
|
41
|
+
// Process already exited -- nothing to clean up
|
|
42
|
+
if (childProcess.exitCode !== null) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (childProcess.pid !== undefined) {
|
|
46
|
+
await killProcessTree(childProcess.pid);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform process spawning utility
|
|
3
|
+
* Handles Windows quirks (cmd scripts, npm wrappers) transparently
|
|
4
|
+
*/
|
|
5
|
+
import { ChildProcess, SpawnOptions } from "child_process";
|
|
6
|
+
/**
|
|
7
|
+
* Spawn a process with cross-platform compatibility
|
|
8
|
+
* Automatically enables shell mode on Windows for npm/npx and .cmd/.bat files
|
|
9
|
+
*
|
|
10
|
+
* @param command - Command to execute
|
|
11
|
+
* @param args - Arguments for the command
|
|
12
|
+
* @param options - Additional spawn options
|
|
13
|
+
*/
|
|
14
|
+
export declare function spawnCrossPlatform(command: string, args: string[], options?: SpawnOptions): ChildProcess;
|
|
15
|
+
/**
|
|
16
|
+
* Attach logging listeners to a child process
|
|
17
|
+
* All output is routed to console.error to avoid corrupting stdio transport
|
|
18
|
+
*
|
|
19
|
+
* @param child - The child process to monitor
|
|
20
|
+
* @param label - Label for log messages (e.g., "web-server")
|
|
21
|
+
*/
|
|
22
|
+
export declare function attachProcessListeners(child: ChildProcess, label: string): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform process spawning utility
|
|
3
|
+
* Handles Windows quirks (cmd scripts, npm wrappers) transparently
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
/**
|
|
7
|
+
* Spawn a process with cross-platform compatibility
|
|
8
|
+
* Automatically enables shell mode on Windows for npm/npx and .cmd/.bat files
|
|
9
|
+
*
|
|
10
|
+
* @param command - Command to execute
|
|
11
|
+
* @param args - Arguments for the command
|
|
12
|
+
* @param options - Additional spawn options
|
|
13
|
+
*/
|
|
14
|
+
export function spawnCrossPlatform(command, args, options = {}) {
|
|
15
|
+
const isWindows = process.platform === "win32";
|
|
16
|
+
let useShell = options.shell ?? false;
|
|
17
|
+
if (isWindows) {
|
|
18
|
+
const lowerCmd = command.toLowerCase();
|
|
19
|
+
// npm/npx on Windows are .cmd files that require shell
|
|
20
|
+
if (lowerCmd === "npm" ||
|
|
21
|
+
lowerCmd === "npx" ||
|
|
22
|
+
lowerCmd.endsWith(".cmd") ||
|
|
23
|
+
lowerCmd.endsWith(".bat")) {
|
|
24
|
+
useShell = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return spawn(command, args, {
|
|
28
|
+
...options,
|
|
29
|
+
shell: useShell,
|
|
30
|
+
windowsHide: true,
|
|
31
|
+
stdio: options.stdio || "pipe",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Attach logging listeners to a child process
|
|
36
|
+
* All output is routed to console.error to avoid corrupting stdio transport
|
|
37
|
+
*
|
|
38
|
+
* @param child - The child process to monitor
|
|
39
|
+
* @param label - Label for log messages (e.g., "web-server")
|
|
40
|
+
*/
|
|
41
|
+
export function attachProcessListeners(child, label) {
|
|
42
|
+
child.stdout?.on("data", (data) => {
|
|
43
|
+
console.error(`[${label} stdout] ${data.toString().trim()}`);
|
|
44
|
+
});
|
|
45
|
+
child.stderr?.on("data", (data) => {
|
|
46
|
+
console.error(`[${label} stderr] ${data.toString().trim()}`);
|
|
47
|
+
});
|
|
48
|
+
child.on("error", (err) => {
|
|
49
|
+
console.error(`[${label}] Spawn error: ${err.message}`);
|
|
50
|
+
});
|
|
51
|
+
child.on("exit", (code, signal) => {
|
|
52
|
+
console.error(`[${label}] Exited: code=${code}, signal=${signal}`);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server readiness detection
|
|
3
|
+
* Uses dual strategy: stdout pattern matching + TCP port polling
|
|
4
|
+
*/
|
|
5
|
+
import { ChildProcess } from "child_process";
|
|
6
|
+
/**
|
|
7
|
+
* Detect when a spawned server is ready to accept connections
|
|
8
|
+
* Races two strategies: stdout pattern matching and TCP port availability
|
|
9
|
+
*
|
|
10
|
+
* @param child - The spawned server process
|
|
11
|
+
* @param port - Expected port the server will listen on
|
|
12
|
+
* @param timeoutMs - Maximum time to wait for readiness (default 60000ms)
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectServerReady(child: ChildProcess, port: number, timeoutMs?: number): Promise<void>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server readiness detection
|
|
3
|
+
* Uses dual strategy: stdout pattern matching + TCP port polling
|
|
4
|
+
*/
|
|
5
|
+
import waitOn from "wait-on";
|
|
6
|
+
/**
|
|
7
|
+
* Common server ready patterns across popular dev servers
|
|
8
|
+
*/
|
|
9
|
+
const readyPatterns = [
|
|
10
|
+
/Local:.*https?:\/\/localhost[:\d]*/i, // Vite
|
|
11
|
+
/webpack.*compiled/i, // webpack
|
|
12
|
+
/ready.*started.*server.*on/i, // Next.js
|
|
13
|
+
/server.*(?:listening|running|started).*:\d+/i, // generic
|
|
14
|
+
/ready on/i, // Next.js alt
|
|
15
|
+
/compiled successfully/i, // CRA
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Detect when a spawned server is ready to accept connections
|
|
19
|
+
* Races two strategies: stdout pattern matching and TCP port availability
|
|
20
|
+
*
|
|
21
|
+
* @param child - The spawned server process
|
|
22
|
+
* @param port - Expected port the server will listen on
|
|
23
|
+
* @param timeoutMs - Maximum time to wait for readiness (default 60000ms)
|
|
24
|
+
*/
|
|
25
|
+
export function detectServerReady(child, port, timeoutMs = 60000) {
|
|
26
|
+
// Strategy 1: stdout/stderr pattern matching
|
|
27
|
+
const stdoutReady = new Promise((resolve, reject) => {
|
|
28
|
+
const checkOutput = (data) => {
|
|
29
|
+
const text = data.toString();
|
|
30
|
+
for (const pattern of readyPatterns) {
|
|
31
|
+
if (pattern.test(text)) {
|
|
32
|
+
cleanup();
|
|
33
|
+
resolve();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const onExit = (code) => {
|
|
39
|
+
cleanup();
|
|
40
|
+
reject(new Error(`Server process exited before becoming ready (exit code: ${code})`));
|
|
41
|
+
};
|
|
42
|
+
const cleanup = () => {
|
|
43
|
+
child.stdout?.removeListener("data", checkOutput);
|
|
44
|
+
child.stderr?.removeListener("data", checkOutput);
|
|
45
|
+
child.removeListener("exit", onExit);
|
|
46
|
+
};
|
|
47
|
+
child.stdout?.on("data", checkOutput);
|
|
48
|
+
child.stderr?.on("data", checkOutput);
|
|
49
|
+
child.on("exit", onExit);
|
|
50
|
+
});
|
|
51
|
+
// Strategy 2: TCP port polling via wait-on
|
|
52
|
+
const portReady = waitOn({
|
|
53
|
+
resources: [`tcp:localhost:${port}`],
|
|
54
|
+
timeout: timeoutMs,
|
|
55
|
+
interval: 500,
|
|
56
|
+
log: false,
|
|
57
|
+
});
|
|
58
|
+
return Promise.race([stdoutReady, portReady]).then(() => {
|
|
59
|
+
console.error(`[Monitor] Server ready on port ${port}`);
|
|
60
|
+
}, (error) => {
|
|
61
|
+
// Provide better error context if the process has already exited
|
|
62
|
+
if (child.exitCode !== null) {
|
|
63
|
+
throw new Error(`Server process exited with code ${child.exitCode} before becoming ready on port ${port}`);
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process management type definitions
|
|
3
|
+
* Contract for all process resources tracked by the feedback server
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Types of processes the server can manage
|
|
7
|
+
*/
|
|
8
|
+
export type ProcessType = "web-server" | "electron" | "windows-exe";
|
|
9
|
+
/**
|
|
10
|
+
* Lifecycle status of a managed process
|
|
11
|
+
*/
|
|
12
|
+
export type ProcessStatus = "launching" | "ready" | "running" | "stopped" | "error";
|
|
13
|
+
/**
|
|
14
|
+
* Runtime information for a tracked process
|
|
15
|
+
*/
|
|
16
|
+
export interface ProcessInfo {
|
|
17
|
+
/** Unique identifier (session ID + process type or similar) */
|
|
18
|
+
id: string;
|
|
19
|
+
/** The session this process belongs to */
|
|
20
|
+
sessionId: string;
|
|
21
|
+
/** Process type */
|
|
22
|
+
type: ProcessType;
|
|
23
|
+
/** Current lifecycle status */
|
|
24
|
+
status: ProcessStatus;
|
|
25
|
+
/** OS process ID (undefined if not yet spawned) */
|
|
26
|
+
pid: number | undefined;
|
|
27
|
+
/** The command that was used to launch */
|
|
28
|
+
command: string;
|
|
29
|
+
/** Arguments passed to the command */
|
|
30
|
+
args: string[];
|
|
31
|
+
/** Working directory */
|
|
32
|
+
cwd: string;
|
|
33
|
+
/** Port number (for web servers) */
|
|
34
|
+
port?: number;
|
|
35
|
+
/** When the process was started */
|
|
36
|
+
startedAt: Date;
|
|
37
|
+
/** When readiness was detected */
|
|
38
|
+
readyAt?: Date;
|
|
39
|
+
/** Exit code if process has stopped */
|
|
40
|
+
exitCode?: number | null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Configuration for launching a web server process
|
|
44
|
+
*/
|
|
45
|
+
export interface LaunchWebServerConfig {
|
|
46
|
+
/** Session this process belongs to */
|
|
47
|
+
sessionId: string;
|
|
48
|
+
/** Command to execute (e.g., "npm", "npx") */
|
|
49
|
+
command: string;
|
|
50
|
+
/** Arguments (e.g., ["run", "dev"], ["vite"]) */
|
|
51
|
+
args: string[];
|
|
52
|
+
/** Working directory for the project */
|
|
53
|
+
cwd: string;
|
|
54
|
+
/** Expected port the server will listen on */
|
|
55
|
+
port: number;
|
|
56
|
+
/** Readiness timeout in ms (default 60000) */
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for launching an Electron application
|
|
61
|
+
*/
|
|
62
|
+
export interface LaunchElectronConfig {
|
|
63
|
+
/** Session this process belongs to */
|
|
64
|
+
sessionId: string;
|
|
65
|
+
/** Path to Electron main entry file */
|
|
66
|
+
entryPath: string;
|
|
67
|
+
/** Optional working directory */
|
|
68
|
+
cwd?: string;
|
|
69
|
+
/** Launch timeout in ms (default 30000) */
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Configuration for launching a Windows executable
|
|
74
|
+
*/
|
|
75
|
+
export interface LaunchWindowsExeConfig {
|
|
76
|
+
/** Session this process belongs to */
|
|
77
|
+
sessionId: string;
|
|
78
|
+
/** Path to the .exe file */
|
|
79
|
+
exePath: string;
|
|
80
|
+
/** Optional command line arguments */
|
|
81
|
+
args?: string[];
|
|
82
|
+
/** Optional working directory */
|
|
83
|
+
cwd?: string;
|
|
84
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-capture module
|
|
3
|
+
* Listens for Playwright page navigation events and captures screenshots automatically
|
|
4
|
+
*/
|
|
5
|
+
import type { Page } from "playwright";
|
|
6
|
+
import type { SessionManager } from "../session-manager.js";
|
|
7
|
+
/**
|
|
8
|
+
* Attach auto-capture listener to a Playwright page.
|
|
9
|
+
* Captures a screenshot after every main-frame navigation.
|
|
10
|
+
* Stores the optimized screenshot in the session manager.
|
|
11
|
+
*
|
|
12
|
+
* @returns Cleanup function to remove the listener
|
|
13
|
+
*/
|
|
14
|
+
export declare function setupAutoCapture(page: Page, sessionId: string, sessionManager: SessionManager): () => void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-capture module
|
|
3
|
+
* Listens for Playwright page navigation events and captures screenshots automatically
|
|
4
|
+
*/
|
|
5
|
+
import { optimizeScreenshot } from "./optimize.js";
|
|
6
|
+
/**
|
|
7
|
+
* Attach auto-capture listener to a Playwright page.
|
|
8
|
+
* Captures a screenshot after every main-frame navigation.
|
|
9
|
+
* Stores the optimized screenshot in the session manager.
|
|
10
|
+
*
|
|
11
|
+
* @returns Cleanup function to remove the listener
|
|
12
|
+
*/
|
|
13
|
+
export function setupAutoCapture(page, sessionId, sessionManager) {
|
|
14
|
+
const handler = async (frame) => {
|
|
15
|
+
if (frame !== page.mainFrame())
|
|
16
|
+
return;
|
|
17
|
+
try {
|
|
18
|
+
await page.waitForLoadState("load");
|
|
19
|
+
const rawBuffer = await page.screenshot({ type: "png" });
|
|
20
|
+
const optimized = await optimizeScreenshot(rawBuffer);
|
|
21
|
+
sessionManager.setAutoCapture(sessionId, {
|
|
22
|
+
imageBase64: optimized.data.toString("base64"),
|
|
23
|
+
mimeType: optimized.mimeType,
|
|
24
|
+
url: page.url(),
|
|
25
|
+
capturedAt: new Date(),
|
|
26
|
+
});
|
|
27
|
+
console.error(`[auto-capture] Captured ${optimized.width}x${optimized.height} ` +
|
|
28
|
+
`(${optimized.data.length} bytes) for session ${sessionId}`);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(`[auto-capture] Failed for session ${sessionId}:`, error);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
page.on("framenavigated", handler);
|
|
35
|
+
return () => {
|
|
36
|
+
page.off("framenavigated", handler);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core screenshot capture functions
|
|
3
|
+
* Playwright pages (web/Electron) and Windows desktop windows
|
|
4
|
+
*/
|
|
5
|
+
import type { Page } from "playwright";
|
|
6
|
+
/**
|
|
7
|
+
* Capture a screenshot from a Playwright Page (web or Electron)
|
|
8
|
+
* Returns raw PNG buffer for further optimization
|
|
9
|
+
*/
|
|
10
|
+
export declare function capturePlaywrightPage(page: Page, options?: {
|
|
11
|
+
fullPage?: boolean;
|
|
12
|
+
}): Promise<Buffer>;
|
|
13
|
+
/**
|
|
14
|
+
* Capture a screenshot of a Windows desktop window by PID.
|
|
15
|
+
* Uses node-screenshots to enumerate windows and match by process ID.
|
|
16
|
+
* Returns raw PNG buffer for further optimization.
|
|
17
|
+
*
|
|
18
|
+
* Note: node-screenshots Window objects may not expose PID in all versions.
|
|
19
|
+
* Falls back to matching by window title if PID is unavailable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function captureDesktopWindow(pid: number): Promise<Buffer>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core screenshot capture functions
|
|
3
|
+
* Playwright pages (web/Electron) and Windows desktop windows
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Capture a screenshot from a Playwright Page (web or Electron)
|
|
7
|
+
* Returns raw PNG buffer for further optimization
|
|
8
|
+
*/
|
|
9
|
+
export async function capturePlaywrightPage(page, options) {
|
|
10
|
+
return await page.screenshot({
|
|
11
|
+
fullPage: options?.fullPage ?? false,
|
|
12
|
+
type: "png",
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Capture a screenshot of a Windows desktop window by PID.
|
|
17
|
+
* Uses node-screenshots to enumerate windows and match by process ID.
|
|
18
|
+
* Returns raw PNG buffer for further optimization.
|
|
19
|
+
*
|
|
20
|
+
* Note: node-screenshots Window objects may not expose PID in all versions.
|
|
21
|
+
* Falls back to matching by window title if PID is unavailable.
|
|
22
|
+
*/
|
|
23
|
+
export async function captureDesktopWindow(pid) {
|
|
24
|
+
// Dynamic import to avoid load-time failures on non-Windows
|
|
25
|
+
const { Window } = await import("node-screenshots");
|
|
26
|
+
const windows = Window.all();
|
|
27
|
+
// node-screenshots Window type doesn't expose pid in current typings,
|
|
28
|
+
// but some builds include it at runtime. Use any cast for defensive check.
|
|
29
|
+
const target = windows.find((w) => {
|
|
30
|
+
const wAny = w;
|
|
31
|
+
if (typeof wAny.processId === "number")
|
|
32
|
+
return wAny.processId === pid;
|
|
33
|
+
if (typeof wAny.pid === "number")
|
|
34
|
+
return wAny.pid === pid;
|
|
35
|
+
if (typeof wAny.pid === "function")
|
|
36
|
+
return wAny.pid() === pid;
|
|
37
|
+
return false;
|
|
38
|
+
});
|
|
39
|
+
if (!target) {
|
|
40
|
+
throw new Error(`No window found for PID ${pid}. The process may not have a visible window yet.`);
|
|
41
|
+
}
|
|
42
|
+
if (target.isMinimized) {
|
|
43
|
+
throw new Error(`Window for PID ${pid} is minimized. Restore it before capturing.`);
|
|
44
|
+
}
|
|
45
|
+
const image = await target.captureImage();
|
|
46
|
+
const pngData = await image.toPng();
|
|
47
|
+
return Buffer.from(pngData);
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot optimization pipeline
|
|
3
|
+
* Resizes and converts screenshots to WebP for efficient MCP transport
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Optimize a raw screenshot buffer: resize + WebP conversion
|
|
7
|
+
* @param buffer - Raw PNG/JPEG buffer from capture
|
|
8
|
+
* @param options - Optimization options
|
|
9
|
+
* @returns Optimized buffer with metadata
|
|
10
|
+
*/
|
|
11
|
+
export declare function optimizeScreenshot(buffer: Buffer, options?: {
|
|
12
|
+
maxWidth?: number;
|
|
13
|
+
quality?: number;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
data: Buffer;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot optimization pipeline
|
|
3
|
+
* Resizes and converts screenshots to WebP for efficient MCP transport
|
|
4
|
+
*/
|
|
5
|
+
import sharp from "sharp";
|
|
6
|
+
/**
|
|
7
|
+
* Optimize a raw screenshot buffer: resize + WebP conversion
|
|
8
|
+
* @param buffer - Raw PNG/JPEG buffer from capture
|
|
9
|
+
* @param options - Optimization options
|
|
10
|
+
* @returns Optimized buffer with metadata
|
|
11
|
+
*/
|
|
12
|
+
export async function optimizeScreenshot(buffer, options) {
|
|
13
|
+
const maxWidth = options?.maxWidth ?? 1280;
|
|
14
|
+
const quality = options?.quality ?? 80;
|
|
15
|
+
const optimized = await sharp(buffer)
|
|
16
|
+
.resize(maxWidth, undefined, {
|
|
17
|
+
fit: "inside",
|
|
18
|
+
withoutEnlargement: true,
|
|
19
|
+
})
|
|
20
|
+
.webp({ quality })
|
|
21
|
+
.toBuffer({ resolveWithObject: true });
|
|
22
|
+
return {
|
|
23
|
+
data: optimized.data,
|
|
24
|
+
mimeType: "image/webp",
|
|
25
|
+
width: optimized.info.width,
|
|
26
|
+
height: optimized.info.height,
|
|
27
|
+
};
|
|
28
|
+
}
|