@pinepaper.studio/mcp-server 1.4.3 → 1.5.1
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 +89 -0
- package/dist/__tests__/fixtures/mock-generators.d.ts +147 -0
- package/dist/__tests__/fixtures/mock-generators.d.ts.map +1 -1
- package/dist/__tests__/fixtures/mock-generators.js +85 -0
- package/dist/__tests__/fixtures/mock-generators.js.map +1 -1
- package/dist/agent/export-manager.d.ts +77 -0
- package/dist/agent/export-manager.d.ts.map +1 -0
- package/dist/agent/export-manager.js +465 -0
- package/dist/agent/export-manager.js.map +1 -0
- package/dist/agent/index.d.ts +9 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +12 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/session-manager.d.ts +109 -0
- package/dist/agent/session-manager.d.ts.map +1 -0
- package/dist/agent/session-manager.js +311 -0
- package/dist/agent/session-manager.js.map +1 -0
- package/dist/agent/types.d.ts +385 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +141 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/puppeteer-controller.d.ts +89 -5
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +412 -11
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/cli.js +61 -65
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2769 -150
- package/dist/index.js.map +1 -1
- package/dist/metrics/types.d.ts +3 -1
- package/dist/metrics/types.d.ts.map +1 -1
- package/dist/prompts/index.d.ts +30 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +163 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/tools/compact-descriptions.d.ts +9 -0
- package/dist/tools/compact-descriptions.d.ts.map +1 -0
- package/dist/tools/compact-descriptions.js +291 -0
- package/dist/tools/compact-descriptions.js.map +1 -0
- package/dist/tools/definitions.d.ts +14 -0
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +4954 -1552
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +1302 -19
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/index.d.ts +4 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts +9 -0
- package/dist/tools/minimal-descriptions.d.ts.map +1 -0
- package/dist/tools/minimal-descriptions.js +158 -0
- package/dist/tools/minimal-descriptions.js.map +1 -0
- package/dist/tools/toolkits.d.ts +51 -0
- package/dist/tools/toolkits.d.ts.map +1 -0
- package/dist/tools/toolkits.js +336 -0
- package/dist/tools/toolkits.js.map +1 -0
- package/dist/types/code-generator.d.ts +193 -1
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +2167 -26
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +5278 -1141
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +863 -5
- package/dist/types/schemas.js.map +1 -1
- package/manifest.json +94 -2
- package/package.json +1 -1
|
@@ -15,6 +15,32 @@ export interface BrowserControllerConfig {
|
|
|
15
15
|
viewportHeight?: number;
|
|
16
16
|
/** Timeout for page operations in ms (default: 30000) */
|
|
17
17
|
timeout?: number;
|
|
18
|
+
/** Enable agent mode with ?agent=1 URL parameter */
|
|
19
|
+
agentMode?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface AgentConnectOptions {
|
|
22
|
+
/** Run browser in headless mode (default: true for agent workflows) */
|
|
23
|
+
headless?: boolean;
|
|
24
|
+
/** Canvas preset to apply (optional) */
|
|
25
|
+
canvasPreset?: string;
|
|
26
|
+
/** Custom viewport width */
|
|
27
|
+
viewportWidth?: number;
|
|
28
|
+
/** Custom viewport height */
|
|
29
|
+
viewportHeight?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface BatchExecuteResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
results: Array<{
|
|
34
|
+
index: number;
|
|
35
|
+
success: boolean;
|
|
36
|
+
result?: unknown;
|
|
37
|
+
itemId?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
}>;
|
|
40
|
+
itemIds: string[];
|
|
41
|
+
executionTime: number;
|
|
42
|
+
error?: string;
|
|
43
|
+
screenshot?: string;
|
|
18
44
|
}
|
|
19
45
|
export interface ExecuteResult {
|
|
20
46
|
success: boolean;
|
|
@@ -35,17 +61,32 @@ export declare class PinePaperBrowserController {
|
|
|
35
61
|
private page;
|
|
36
62
|
private config;
|
|
37
63
|
private isConnected;
|
|
64
|
+
private isAgentMode;
|
|
65
|
+
private connectingPromise;
|
|
38
66
|
constructor(config?: BrowserControllerConfig);
|
|
67
|
+
/**
|
|
68
|
+
* Get URL with agent mode parameter
|
|
69
|
+
*/
|
|
70
|
+
private getAgentUrl;
|
|
71
|
+
/**
|
|
72
|
+
* Check if in agent mode
|
|
73
|
+
*/
|
|
74
|
+
get agentMode(): boolean;
|
|
39
75
|
/**
|
|
40
76
|
* Check if connected to browser
|
|
41
77
|
*/
|
|
42
78
|
get connected(): boolean;
|
|
43
79
|
/**
|
|
44
|
-
* Get current studio URL
|
|
80
|
+
* Get current studio URL (always includes agent parameters for MCP server)
|
|
45
81
|
*/
|
|
46
82
|
get studioUrl(): string;
|
|
47
83
|
/**
|
|
48
|
-
*
|
|
84
|
+
* Get the actual URL being used (same as studioUrl since MCP always uses agent mode)
|
|
85
|
+
*/
|
|
86
|
+
get actualUrl(): string;
|
|
87
|
+
/**
|
|
88
|
+
* Launch browser and navigate to PinePaper Studio.
|
|
89
|
+
* When agentMode is enabled (default), this automatically uses connectAgent().
|
|
49
90
|
*/
|
|
50
91
|
connect(): Promise<void>;
|
|
51
92
|
/**
|
|
@@ -81,13 +122,56 @@ export declare class PinePaperBrowserController {
|
|
|
81
122
|
* Check if PinePaper API is available
|
|
82
123
|
*/
|
|
83
124
|
checkPinePaperReady(): Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Connect in agent mode with optimized settings for automation.
|
|
127
|
+
* Uses headless mode by default and adds ?agent=1 URL parameter.
|
|
128
|
+
*/
|
|
129
|
+
connectAgent(options?: AgentConnectOptions): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Wait for PinePaper API to be available
|
|
132
|
+
*/
|
|
133
|
+
private waitForPinePaper;
|
|
134
|
+
/**
|
|
135
|
+
* Execute multiple code blocks as a batch for optimal performance.
|
|
136
|
+
* Combines all code into a single execution context.
|
|
137
|
+
*/
|
|
138
|
+
executeBatch(codes: string[], options?: {
|
|
139
|
+
takeScreenshot?: boolean;
|
|
140
|
+
atomic?: boolean;
|
|
141
|
+
}): Promise<BatchExecuteResult>;
|
|
142
|
+
/**
|
|
143
|
+
* Execute code with a timeout
|
|
144
|
+
*/
|
|
145
|
+
executeWithTimeout(code: string, timeoutMs: number, takeScreenshot?: boolean): Promise<ExecuteResult>;
|
|
146
|
+
/**
|
|
147
|
+
* Quick reset: Clear canvas without full page refresh
|
|
148
|
+
*/
|
|
149
|
+
quickReset(options?: {
|
|
150
|
+
preserveBackground?: boolean;
|
|
151
|
+
}): Promise<ExecuteResult>;
|
|
152
|
+
/**
|
|
153
|
+
* Set canvas size from platform preset
|
|
154
|
+
*/
|
|
155
|
+
setCanvasPreset(preset: string): Promise<ExecuteResult>;
|
|
84
156
|
}
|
|
85
157
|
/**
|
|
86
|
-
* Get or create the global browser controller instance
|
|
158
|
+
* Get or create the global browser controller instance.
|
|
159
|
+
*
|
|
160
|
+
* IMPORTANT: This is a singleton - only ONE browser controller exists.
|
|
161
|
+
* If a controller already exists, the config parameter is ignored.
|
|
162
|
+
* Call resetBrowserController() first if you need to change config.
|
|
163
|
+
*
|
|
164
|
+
* NOTE: MCP server ALWAYS uses agent mode with ?agent=1&mode=agent URL parameters.
|
|
165
|
+
* This uses headless browser by default for optimal performance.
|
|
87
166
|
*/
|
|
88
167
|
export declare function getBrowserController(config?: BrowserControllerConfig): PinePaperBrowserController;
|
|
89
168
|
/**
|
|
90
|
-
* Reset the global controller (for testing)
|
|
169
|
+
* Reset the global controller (for testing or when config needs to change).
|
|
170
|
+
* This will close any existing browser and clear the singleton.
|
|
171
|
+
*/
|
|
172
|
+
export declare function resetBrowserController(): Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* Synchronous reset for testing (fire and forget)
|
|
91
175
|
*/
|
|
92
|
-
export declare function
|
|
176
|
+
export declare function resetBrowserControllerSync(): void;
|
|
93
177
|
//# sourceMappingURL=puppeteer-controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,19 +20,48 @@ export class PinePaperBrowserController {
|
|
|
20
20
|
page = null;
|
|
21
21
|
config;
|
|
22
22
|
isConnected = false;
|
|
23
|
+
isAgentMode = false;
|
|
24
|
+
connectingPromise = null;
|
|
23
25
|
constructor(config = {}) {
|
|
24
26
|
// Ensure URL points to /editor where the code console and API are available
|
|
25
27
|
let studioUrl = config.studioUrl || 'https://pinepaper.studio';
|
|
28
|
+
const originalUrl = studioUrl;
|
|
26
29
|
if (!studioUrl.includes('/editor')) {
|
|
27
30
|
studioUrl = studioUrl.replace(/\/$/, '') + '/editor';
|
|
28
31
|
}
|
|
32
|
+
// MCP server ALWAYS uses agent mode - add agent parameter to URL
|
|
33
|
+
// This is critical for proper PinePaper agent integration
|
|
34
|
+
const url = new URL(studioUrl);
|
|
35
|
+
url.searchParams.set('agent', '1');
|
|
36
|
+
url.searchParams.set('mode', 'agent');
|
|
37
|
+
studioUrl = url.toString();
|
|
29
38
|
this.config = {
|
|
30
39
|
studioUrl,
|
|
31
|
-
headless: config.headless ?? false, // Default to visible browser
|
|
40
|
+
headless: config.headless ?? false, // Default to visible browser so user can see PinePaper Studio
|
|
32
41
|
viewportWidth: config.viewportWidth || 1280,
|
|
33
42
|
viewportHeight: config.viewportHeight || 800,
|
|
34
43
|
timeout: config.timeout || 30000,
|
|
44
|
+
agentMode: true, // MCP server always uses agent mode
|
|
35
45
|
};
|
|
46
|
+
console.error(`[PinePaper] Controller initialized:`);
|
|
47
|
+
console.error(`[PinePaper] Original URL: ${originalUrl}`);
|
|
48
|
+
console.error(`[PinePaper] Agent URL: ${studioUrl}`);
|
|
49
|
+
console.error(`[PinePaper] Headless: ${this.config.headless}`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get URL with agent mode parameter
|
|
53
|
+
*/
|
|
54
|
+
getAgentUrl(baseUrl) {
|
|
55
|
+
const url = new URL(baseUrl);
|
|
56
|
+
url.searchParams.set('agent', '1');
|
|
57
|
+
url.searchParams.set('mode', 'agent');
|
|
58
|
+
return url.toString();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if in agent mode
|
|
62
|
+
*/
|
|
63
|
+
get agentMode() {
|
|
64
|
+
return this.isAgentMode;
|
|
36
65
|
}
|
|
37
66
|
/**
|
|
38
67
|
* Check if connected to browser
|
|
@@ -41,20 +70,62 @@ export class PinePaperBrowserController {
|
|
|
41
70
|
return this.isConnected && this.page !== null;
|
|
42
71
|
}
|
|
43
72
|
/**
|
|
44
|
-
* Get current studio URL
|
|
73
|
+
* Get current studio URL (always includes agent parameters for MCP server)
|
|
45
74
|
*/
|
|
46
75
|
get studioUrl() {
|
|
47
76
|
return this.config.studioUrl;
|
|
48
77
|
}
|
|
49
78
|
/**
|
|
50
|
-
*
|
|
79
|
+
* Get the actual URL being used (same as studioUrl since MCP always uses agent mode)
|
|
80
|
+
*/
|
|
81
|
+
get actualUrl() {
|
|
82
|
+
return this.config.studioUrl;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Launch browser and navigate to PinePaper Studio.
|
|
86
|
+
* When agentMode is enabled (default), this automatically uses connectAgent().
|
|
51
87
|
*/
|
|
52
88
|
async connect() {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
89
|
+
// Prevent concurrent connection attempts (race condition when multiple tools fire at once)
|
|
90
|
+
if (this.connectingPromise) {
|
|
91
|
+
console.error('[PinePaper] Connection already in progress, waiting...');
|
|
92
|
+
return this.connectingPromise;
|
|
93
|
+
}
|
|
94
|
+
console.error(`[PinePaper] connect() called - agentMode=${this.config.agentMode}, headless=${this.config.headless}`);
|
|
95
|
+
// If agent mode is configured, delegate to connectAgent()
|
|
96
|
+
if (this.config.agentMode) {
|
|
97
|
+
console.error('[PinePaper] Delegating to connectAgent()');
|
|
98
|
+
this.connectingPromise = this.connectAgent({ headless: this.config.headless })
|
|
99
|
+
.finally(() => { this.connectingPromise = null; });
|
|
100
|
+
return this.connectingPromise;
|
|
101
|
+
}
|
|
102
|
+
console.error('[PinePaper] Using standard connect (non-agent mode)');
|
|
103
|
+
// Check if already connected and page is still valid
|
|
104
|
+
if (this.isConnected && this.page) {
|
|
105
|
+
try {
|
|
106
|
+
// Verify the page is still responsive
|
|
107
|
+
await this.page.evaluate(() => true);
|
|
108
|
+
console.error('[PinePaper] Already connected');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Page is stale, need to reconnect
|
|
113
|
+
console.error('[PinePaper] Existing connection stale, reconnecting...');
|
|
114
|
+
this.isConnected = false;
|
|
115
|
+
}
|
|
56
116
|
}
|
|
57
117
|
try {
|
|
118
|
+
// Clean up any existing browser instance first to prevent multiple windows
|
|
119
|
+
if (this.browser) {
|
|
120
|
+
try {
|
|
121
|
+
await this.browser.close();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Ignore close errors
|
|
125
|
+
}
|
|
126
|
+
this.browser = null;
|
|
127
|
+
this.page = null;
|
|
128
|
+
}
|
|
58
129
|
// Dynamic import to avoid issues when puppeteer isn't installed
|
|
59
130
|
const puppeteer = await import('puppeteer');
|
|
60
131
|
console.error('[PinePaper] Launching browser...');
|
|
@@ -66,7 +137,18 @@ export class PinePaperBrowserController {
|
|
|
66
137
|
`--window-size=${this.config.viewportWidth},${this.config.viewportHeight}`,
|
|
67
138
|
],
|
|
68
139
|
});
|
|
69
|
-
|
|
140
|
+
// Get existing pages first, reuse if available (avoid creating multiple tabs)
|
|
141
|
+
const pages = await this.browser.pages();
|
|
142
|
+
if (pages.length > 0) {
|
|
143
|
+
this.page = pages[0];
|
|
144
|
+
// Close any extra pages that might have been created
|
|
145
|
+
for (let i = 1; i < pages.length; i++) {
|
|
146
|
+
await pages[i].close();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.page = await this.browser.newPage();
|
|
151
|
+
}
|
|
70
152
|
await this.page.setViewport({
|
|
71
153
|
width: this.config.viewportWidth,
|
|
72
154
|
height: this.config.viewportHeight,
|
|
@@ -107,6 +189,7 @@ export class PinePaperBrowserController {
|
|
|
107
189
|
this.browser = null;
|
|
108
190
|
this.page = null;
|
|
109
191
|
this.isConnected = false;
|
|
192
|
+
this.connectingPromise = null;
|
|
110
193
|
console.error('[PinePaper] Disconnected from browser');
|
|
111
194
|
}
|
|
112
195
|
/**
|
|
@@ -254,24 +337,342 @@ export class PinePaperBrowserController {
|
|
|
254
337
|
return false;
|
|
255
338
|
}
|
|
256
339
|
}
|
|
340
|
+
// =============================================================================
|
|
341
|
+
// AGENT MODE METHODS
|
|
342
|
+
// =============================================================================
|
|
343
|
+
/**
|
|
344
|
+
* Connect in agent mode with optimized settings for automation.
|
|
345
|
+
* Uses headless mode by default and adds ?agent=1 URL parameter.
|
|
346
|
+
*/
|
|
347
|
+
async connectAgent(options = {}) {
|
|
348
|
+
// Check if already connected and page is still valid
|
|
349
|
+
if (this.isConnected && this.page) {
|
|
350
|
+
try {
|
|
351
|
+
// Verify the page is still responsive
|
|
352
|
+
await this.page.evaluate(() => true);
|
|
353
|
+
// If already connected but not in agent mode, navigate to agent URL
|
|
354
|
+
if (!this.isAgentMode) {
|
|
355
|
+
const agentUrl = this.getAgentUrl(this.config.studioUrl);
|
|
356
|
+
console.error(`[PinePaper] Switching to agent mode: ${agentUrl}`);
|
|
357
|
+
await this.page.goto(agentUrl, {
|
|
358
|
+
waitUntil: 'networkidle2',
|
|
359
|
+
timeout: this.config.timeout,
|
|
360
|
+
});
|
|
361
|
+
await this.waitForPinePaper();
|
|
362
|
+
this.isAgentMode = true;
|
|
363
|
+
}
|
|
364
|
+
console.error('[PinePaper] Already connected in agent mode');
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// Page is stale, need to reconnect
|
|
369
|
+
console.error('[PinePaper] Existing connection stale, reconnecting...');
|
|
370
|
+
this.isConnected = false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
// Clean up any existing browser instance first to prevent multiple windows
|
|
375
|
+
if (this.browser) {
|
|
376
|
+
try {
|
|
377
|
+
await this.browser.close();
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
// Ignore close errors
|
|
381
|
+
}
|
|
382
|
+
this.browser = null;
|
|
383
|
+
this.page = null;
|
|
384
|
+
}
|
|
385
|
+
// Dynamic import to avoid issues when puppeteer isn't installed
|
|
386
|
+
const puppeteer = await import('puppeteer');
|
|
387
|
+
// Agent mode defaults to visible browser so user can see PinePaper Studio
|
|
388
|
+
const useHeadless = options.headless ?? this.config.headless;
|
|
389
|
+
const viewportWidth = options.viewportWidth ?? this.config.viewportWidth;
|
|
390
|
+
const viewportHeight = options.viewportHeight ?? this.config.viewportHeight;
|
|
391
|
+
console.error(`[PinePaper] Launching browser in agent mode (headless: ${useHeadless})...`);
|
|
392
|
+
this.browser = await puppeteer.default.launch({
|
|
393
|
+
headless: useHeadless,
|
|
394
|
+
args: [
|
|
395
|
+
'--no-sandbox',
|
|
396
|
+
'--disable-setuid-sandbox',
|
|
397
|
+
`--window-size=${viewportWidth},${viewportHeight}`,
|
|
398
|
+
// Additional optimization flags for agent mode
|
|
399
|
+
'--disable-gpu',
|
|
400
|
+
'--disable-dev-shm-usage',
|
|
401
|
+
'--disable-extensions',
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
// Get existing pages first, reuse if available (avoid creating multiple tabs)
|
|
405
|
+
const pages = await this.browser.pages();
|
|
406
|
+
// Use the first page (about:blank) and close ALL others
|
|
407
|
+
if (pages.length > 0) {
|
|
408
|
+
this.page = pages[0];
|
|
409
|
+
// Close ALL extra pages
|
|
410
|
+
for (let i = 1; i < pages.length; i++) {
|
|
411
|
+
try {
|
|
412
|
+
await pages[i].close();
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
// Ignore close errors
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
this.page = await this.browser.newPage();
|
|
421
|
+
}
|
|
422
|
+
await this.page.setViewport({
|
|
423
|
+
width: viewportWidth,
|
|
424
|
+
height: viewportHeight,
|
|
425
|
+
});
|
|
426
|
+
// Ensure URL has agent parameters (double-check)
|
|
427
|
+
const targetUrl = this.config.studioUrl.includes('agent=')
|
|
428
|
+
? this.config.studioUrl
|
|
429
|
+
: this.getAgentUrl(this.config.studioUrl);
|
|
430
|
+
console.error(`[PinePaper] Navigating to agent URL: ${targetUrl}`);
|
|
431
|
+
// Navigate the existing page to PinePaper (converts about:blank to PinePaper)
|
|
432
|
+
await this.page.goto(targetUrl, {
|
|
433
|
+
waitUntil: 'networkidle2',
|
|
434
|
+
timeout: this.config.timeout,
|
|
435
|
+
});
|
|
436
|
+
// Wait for PinePaper to be ready
|
|
437
|
+
await this.waitForPinePaper();
|
|
438
|
+
this.isConnected = true;
|
|
439
|
+
this.isAgentMode = true;
|
|
440
|
+
console.error('[PinePaper] Connected to PinePaper Studio in agent mode');
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
await this.disconnect();
|
|
444
|
+
throw new Error(`Failed to connect in agent mode: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Wait for PinePaper API to be available
|
|
449
|
+
*/
|
|
450
|
+
async waitForPinePaper() {
|
|
451
|
+
if (!this.page)
|
|
452
|
+
return;
|
|
453
|
+
console.error('[PinePaper] Waiting for PinePaper Studio to initialize...');
|
|
454
|
+
await this.page.waitForFunction(() => {
|
|
455
|
+
return (typeof window.app !== 'undefined' ||
|
|
456
|
+
typeof window.pinepaper !== 'undefined' ||
|
|
457
|
+
typeof window.paper !== 'undefined');
|
|
458
|
+
}, { timeout: this.config.timeout });
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Execute multiple code blocks as a batch for optimal performance.
|
|
462
|
+
* Combines all code into a single execution context.
|
|
463
|
+
*/
|
|
464
|
+
async executeBatch(codes, options = {}) {
|
|
465
|
+
const startTime = Date.now();
|
|
466
|
+
if (!this.page || !this.isConnected) {
|
|
467
|
+
return {
|
|
468
|
+
success: false,
|
|
469
|
+
results: [],
|
|
470
|
+
itemIds: [],
|
|
471
|
+
executionTime: 0,
|
|
472
|
+
error: 'Not connected to PinePaper Studio',
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const atomic = options.atomic ?? true;
|
|
476
|
+
// Wrap all codes in a single IIFE for batch execution
|
|
477
|
+
const batchCode = `
|
|
478
|
+
(async function() {
|
|
479
|
+
const _results = [];
|
|
480
|
+
const _itemIds = [];
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
${codes.map((code, i) => `
|
|
484
|
+
// === Operation ${i + 1} ===
|
|
485
|
+
try {
|
|
486
|
+
const _result${i} = await (async () => {
|
|
487
|
+
${code}
|
|
488
|
+
})();
|
|
489
|
+
_results.push({ index: ${i}, success: true, result: _result${i} });
|
|
490
|
+
if (_result${i} && typeof _result${i} === 'object') {
|
|
491
|
+
if (_result${i}.itemId) _itemIds.push(_result${i}.itemId);
|
|
492
|
+
if (_result${i}.registryId) _itemIds.push(_result${i}.registryId);
|
|
493
|
+
}
|
|
494
|
+
} catch (e) {
|
|
495
|
+
_results.push({ index: ${i}, success: false, error: e.message });
|
|
496
|
+
${atomic ? 'throw e; // Atomic mode: stop on first error' : '// Continue despite error'}
|
|
497
|
+
}
|
|
498
|
+
`).join('\n')}
|
|
499
|
+
|
|
500
|
+
// Save history once for all operations
|
|
501
|
+
if (app.historyManager) {
|
|
502
|
+
app.historyManager.saveState();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return { success: true, itemIds: _itemIds, results: _results };
|
|
506
|
+
} catch (e) {
|
|
507
|
+
return {
|
|
508
|
+
success: false,
|
|
509
|
+
error: e.message,
|
|
510
|
+
itemIds: _itemIds,
|
|
511
|
+
results: _results
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
})();
|
|
515
|
+
`.trim();
|
|
516
|
+
try {
|
|
517
|
+
const result = await this.page.evaluate((codeToRun) => {
|
|
518
|
+
try {
|
|
519
|
+
// eslint-disable-next-line no-eval
|
|
520
|
+
const evalResult = eval(codeToRun);
|
|
521
|
+
if (evalResult instanceof Promise) {
|
|
522
|
+
return evalResult;
|
|
523
|
+
}
|
|
524
|
+
return evalResult;
|
|
525
|
+
}
|
|
526
|
+
catch (e) {
|
|
527
|
+
return {
|
|
528
|
+
success: false,
|
|
529
|
+
error: e instanceof Error ? e.message : 'Batch execution error',
|
|
530
|
+
results: [],
|
|
531
|
+
itemIds: [],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}, batchCode);
|
|
535
|
+
const executionTime = Date.now() - startTime;
|
|
536
|
+
// Take screenshot if requested
|
|
537
|
+
let screenshot;
|
|
538
|
+
if (options.takeScreenshot && result.success) {
|
|
539
|
+
screenshot = await this.takeScreenshot();
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
success: result.success ?? false,
|
|
543
|
+
results: result.results ?? [],
|
|
544
|
+
itemIds: result.itemIds ?? [],
|
|
545
|
+
executionTime,
|
|
546
|
+
error: result.error,
|
|
547
|
+
screenshot,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
return {
|
|
552
|
+
success: false,
|
|
553
|
+
results: [],
|
|
554
|
+
itemIds: [],
|
|
555
|
+
executionTime: Date.now() - startTime,
|
|
556
|
+
error: error instanceof Error ? error.message : 'Unknown batch error',
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Execute code with a timeout
|
|
562
|
+
*/
|
|
563
|
+
async executeWithTimeout(code, timeoutMs, takeScreenshot = false) {
|
|
564
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
565
|
+
setTimeout(() => reject(new Error('Execution timeout')), timeoutMs);
|
|
566
|
+
});
|
|
567
|
+
const executePromise = this.executeCode(code, takeScreenshot);
|
|
568
|
+
try {
|
|
569
|
+
return await Promise.race([executePromise, timeoutPromise]);
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
return {
|
|
573
|
+
success: false,
|
|
574
|
+
error: error instanceof Error ? error.message : 'Timeout error',
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Quick reset: Clear canvas without full page refresh
|
|
580
|
+
*/
|
|
581
|
+
async quickReset(options = {}) {
|
|
582
|
+
const resetCode = `
|
|
583
|
+
(function() {
|
|
584
|
+
// Clear all items
|
|
585
|
+
if (app.clearCanvas) {
|
|
586
|
+
app.clearCanvas();
|
|
587
|
+
} else {
|
|
588
|
+
if (app.textItemGroup) app.textItemGroup.removeChildren();
|
|
589
|
+
if (app.patternGroup) app.patternGroup.removeChildren();
|
|
590
|
+
if (app.itemRegistry) app.itemRegistry.clear();
|
|
591
|
+
if (app.relationRegistry) app.relationRegistry.clear();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
${!options.preserveBackground ? `
|
|
595
|
+
// Reset background
|
|
596
|
+
if (app.setBackgroundColor) {
|
|
597
|
+
app.setBackgroundColor('#1a1a2e');
|
|
598
|
+
}
|
|
599
|
+
` : ''}
|
|
600
|
+
|
|
601
|
+
// Save state
|
|
602
|
+
if (app.historyManager) {
|
|
603
|
+
app.historyManager.saveState();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return { success: true, reset: true };
|
|
607
|
+
})();
|
|
608
|
+
`.trim();
|
|
609
|
+
return this.executeCode(resetCode, false);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Set canvas size from platform preset
|
|
613
|
+
*/
|
|
614
|
+
async setCanvasPreset(preset) {
|
|
615
|
+
const code = `
|
|
616
|
+
(function() {
|
|
617
|
+
if (app.setCanvasSize) {
|
|
618
|
+
app.setCanvasSize('${preset}');
|
|
619
|
+
return { success: true, preset: '${preset}' };
|
|
620
|
+
}
|
|
621
|
+
return { success: false, error: 'setCanvasSize not available' };
|
|
622
|
+
})();
|
|
623
|
+
`.trim();
|
|
624
|
+
return this.executeCode(code, false);
|
|
625
|
+
}
|
|
257
626
|
}
|
|
258
627
|
// =============================================================================
|
|
259
628
|
// SINGLETON INSTANCE
|
|
260
629
|
// =============================================================================
|
|
261
630
|
let globalController = null;
|
|
262
631
|
/**
|
|
263
|
-
* Get or create the global browser controller instance
|
|
632
|
+
* Get or create the global browser controller instance.
|
|
633
|
+
*
|
|
634
|
+
* IMPORTANT: This is a singleton - only ONE browser controller exists.
|
|
635
|
+
* If a controller already exists, the config parameter is ignored.
|
|
636
|
+
* Call resetBrowserController() first if you need to change config.
|
|
637
|
+
*
|
|
638
|
+
* NOTE: MCP server ALWAYS uses agent mode with ?agent=1&mode=agent URL parameters.
|
|
639
|
+
* This uses headless browser by default for optimal performance.
|
|
264
640
|
*/
|
|
265
641
|
export function getBrowserController(config) {
|
|
266
642
|
if (!globalController) {
|
|
267
|
-
|
|
643
|
+
// MCP server always uses headless by default
|
|
644
|
+
const mcpConfig = {
|
|
645
|
+
...config,
|
|
646
|
+
headless: config?.headless ?? true,
|
|
647
|
+
};
|
|
648
|
+
globalController = new PinePaperBrowserController(mcpConfig);
|
|
649
|
+
}
|
|
650
|
+
else if (config) {
|
|
651
|
+
// Log that config is being ignored since controller already exists
|
|
652
|
+
console.error('[PinePaper] Browser controller already exists, config parameter ignored. Call resetBrowserController() to create a new controller with different config.');
|
|
268
653
|
}
|
|
269
654
|
return globalController;
|
|
270
655
|
}
|
|
271
656
|
/**
|
|
272
|
-
* Reset the global controller (for testing)
|
|
657
|
+
* Reset the global controller (for testing or when config needs to change).
|
|
658
|
+
* This will close any existing browser and clear the singleton.
|
|
659
|
+
*/
|
|
660
|
+
export async function resetBrowserController() {
|
|
661
|
+
if (globalController) {
|
|
662
|
+
try {
|
|
663
|
+
await globalController.disconnect();
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
// Ignore disconnect errors during reset
|
|
667
|
+
}
|
|
668
|
+
globalController = null;
|
|
669
|
+
console.error('[PinePaper] Browser controller reset');
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Synchronous reset for testing (fire and forget)
|
|
273
674
|
*/
|
|
274
|
-
export function
|
|
675
|
+
export function resetBrowserControllerSync() {
|
|
275
676
|
if (globalController) {
|
|
276
677
|
globalController.disconnect().catch(() => { });
|
|
277
678
|
globalController = null;
|