pikiclaw 0.2.64 → 0.2.65

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 CHANGED
@@ -98,10 +98,6 @@ npx pikiclaw@latest
98
98
 
99
99
  <img src="docs/promo-dashboard-config.png" alt="Config" width="700">
100
100
 
101
- **插件中心** — 浏览器操控、桌面自动化
102
-
103
- <img src="docs/promo-dashboard-extensions.png" alt="Extensions" width="700">
104
-
105
101
  **会话管理** — 按 Agent 分组的会话泳道
106
102
 
107
103
  <img src="docs/promo-dashboard-sessions.png" alt="Sessions" width="700">
@@ -162,7 +158,7 @@ npx pikiclaw@latest --doctor
162
158
 
163
159
  可选 GUI 能力:
164
160
 
165
- - 浏览器自动化:通过 `@playwright/mcp` 补充接入,默认支持 Chrome extension mode,也可切到 headless / isolated 模式
161
+ - 浏览器自动化:通过 `@playwright/mcp` 管理一个专用的持久化 Chrome profile;第一次使用时在这个自动化浏览器里登录需要的网站,后续任务会复用同一个 profile
166
162
  - macOS 桌面自动化:通过 Appium Mac2 提供 `desktop_open_app`、`desktop_snapshot`、`desktop_click`、`desktop_type`、`desktop_screenshot` 等工具
167
163
 
168
164
  ---
@@ -196,17 +192,13 @@ npx pikiclaw@latest --doctor
196
192
  ## Config And Setup Notes
197
193
 
198
194
  - 持久化配置在 `~/.pikiclaw/setting.json`
199
- - Dashboard 是主配置入口,环境变量仍然可用
200
- - 浏览器 GUI 相关常用变量:
201
- - `PIKICLAW_BROWSER_GUI`
202
- - `PIKICLAW_BROWSER_USE_EXTENSION`
203
- - `PIKICLAW_BROWSER_HEADLESS`
204
- - `PIKICLAW_BROWSER_ISOLATED`
205
- - `PLAYWRIGHT_MCP_EXTENSION_TOKEN`
195
+ - Dashboard 是主配置入口,其他运行时配置仍然可用
206
196
  - 桌面 GUI 相关常用变量:
207
197
  - `PIKICLAW_DESKTOP_GUI`
208
198
  - `PIKICLAW_DESKTOP_APPIUM_URL`
209
199
 
200
+ 浏览器自动化由 dashboard 和本地运行时共同管理,会自动创建并复用专用的 Chrome profile 目录。你只需要在这个专用浏览器里登录需要自动化的网站账号一次。
201
+
210
202
  如果要启用 macOS 桌面自动化,需要先准备 Appium Mac2:
211
203
 
212
204
  ```bash
package/dist/bot.js CHANGED
@@ -10,6 +10,7 @@ import { execSync, spawn } from 'node:child_process';
10
10
  import { getActiveUserConfig, onUserConfigChange, resolveUserWorkdir, setUserWorkdir } from './user-config.js';
11
11
  import { doStream, getSessions, getSessionTail, getUsage, initializeProjectSkills, listAgents, listModels, listSkills, stageSessionFiles, isPendingSessionId, normalizeClaudeModelId, } from './code-agent.js';
12
12
  import { getDriver, hasDriver, allDriverIds } from './agent-driver.js';
13
+ import { resolveGuiIntegrationConfig } from './mcp-bridge.js';
13
14
  import { terminateProcessTree } from './process-control.js';
14
15
  import { VERSION } from './version.js';
15
16
  import { buildHumanLoopResponse, createEmptyHumanLoopAnswer, currentHumanLoopQuestion, isHumanLoopAwaitingText, setHumanLoopOption, setHumanLoopText, skipHumanLoopQuestion, } from './human-loop.js';
@@ -221,6 +222,23 @@ function buildMcpDeliveryPrompt() {
221
222
  'This is an IM conversation, so pay attention to the IM tools.',
222
223
  ].join('\n');
223
224
  }
225
+ function buildBrowserAutomationPrompt(browserEnabled) {
226
+ if (!browserEnabled) {
227
+ return [
228
+ '[Browser Automation]',
229
+ 'Managed browser automation is disabled by default for this session.',
230
+ process.platform === 'darwin'
231
+ ? 'On macOS, operate your main browser directly with native commands such as open, osascript, and screencapture when needed.'
232
+ : 'Use native OS or browser commands directly when browser automation is not enabled.',
233
+ ].join('\n');
234
+ }
235
+ return [
236
+ '[Browser Automation]',
237
+ 'A Playwright MCP browser server is already configured to use the local Chrome channel with a persistent profile.',
238
+ 'Do not call browser_install unless a browser tool explicitly reports that Chrome or the browser is missing.',
239
+ 'If you need a new tab, use browser_tabs with action="new".',
240
+ ].join('\n');
241
+ }
224
242
  function configModelValue(config, agent) {
225
243
  switch (agent) {
226
244
  case 'claude': return normalizeClaudeModelId(config.claudeModel || process.env.CLAUDE_MODEL || 'claude-opus-4-6');
@@ -1072,11 +1090,15 @@ export class Bot {
1072
1090
  const resolvedModel = cs.modelId || this.modelForAgent(cs.agent);
1073
1091
  const agentConfig = this.agentConfigs[cs.agent] || {};
1074
1092
  const extraArgs = agentConfig.extraArgs || [];
1093
+ const browserEnabled = resolveGuiIntegrationConfig(getActiveUserConfig()).browserEnabled;
1075
1094
  this.log(`[runStream] agent=${cs.agent} session=${cs.sessionId || '(new)'} workdir=${this.workdir} timeout=${this.runTimeout}s attachments=${attachments.length}`);
1076
1095
  this.log(`[runStream] ${cs.agent} config: model=${resolvedModel} extraArgs=[${extraArgs.join(' ')}]`);
1077
1096
  const isFirstTurnOfSession = !cs.sessionId || isPendingSessionId(cs.sessionId);
1097
+ const mcpSystemPrompt = mcpSendFile
1098
+ ? appendExtraPrompt(buildMcpDeliveryPrompt(), buildBrowserAutomationPrompt(browserEnabled))
1099
+ : '';
1078
1100
  const effectiveSystemPrompt = isFirstTurnOfSession
1079
- ? (mcpSendFile ? appendExtraPrompt(systemPrompt, buildMcpDeliveryPrompt()) : systemPrompt)
1101
+ ? appendExtraPrompt(systemPrompt, mcpSystemPrompt)
1080
1102
  : undefined;
1081
1103
  const opts = {
1082
1104
  agent: cs.agent, prompt, workdir: this.workdir, timeout: this.runTimeout,
@@ -0,0 +1,458 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import { spawn, spawnSync } from 'node:child_process';
6
+ import { MANAGED_BROWSER_PROFILE_SUBPATH, PLAYWRIGHT_MCP_PACKAGE_NAME, PLAYWRIGHT_MCP_PACKAGE_SPEC, PLAYWRIGHT_MCP_BROWSER_ARGS, } from './constants.js';
7
+ const MANAGED_BROWSER_SETUP_STATE_FILENAME = 'managed-browser-setup.json';
8
+ const MANAGED_BROWSER_SHUTDOWN_TIMEOUT_MS = 5_000;
9
+ const MANAGED_BROWSER_SHUTDOWN_POLL_MS = 100;
10
+ const MANAGED_BROWSER_DEBUG_PORT = 39222;
11
+ const require = createRequire(import.meta.url);
12
+ function normalizeBrowserCdpEndpoint(endpoint) {
13
+ const value = String(endpoint || '').trim();
14
+ if (!value)
15
+ return '';
16
+ return value.replace(/\/+$/, '');
17
+ }
18
+ async function resolveBrowserCdpEndpoint(endpoint) {
19
+ const normalizedEndpoint = normalizeBrowserCdpEndpoint(endpoint);
20
+ if (!normalizedEndpoint)
21
+ return null;
22
+ const controller = new AbortController();
23
+ const timeout = setTimeout(() => controller.abort(), 1_500);
24
+ try {
25
+ const response = await fetch(`${normalizedEndpoint}/json/version`, { signal: controller.signal });
26
+ if (!response.ok)
27
+ return null;
28
+ const payload = await response.json().catch(() => null);
29
+ return typeof payload?.webSocketDebuggerUrl === 'string' ? normalizedEndpoint : null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ finally {
35
+ clearTimeout(timeout);
36
+ }
37
+ }
38
+ function resolveOnPath(command) {
39
+ const checker = process.platform === 'win32' ? 'where' : 'which';
40
+ try {
41
+ const result = spawnSync(checker, [command], { encoding: 'utf8' });
42
+ if (result.status !== 0)
43
+ return null;
44
+ const lines = String(result.stdout || '').split(/\r?\n/).map(line => line.trim()).filter(Boolean);
45
+ return lines[0] || null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ function resolveCommonChromePaths() {
52
+ if (process.platform === 'darwin') {
53
+ return [
54
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
55
+ path.join(os.homedir(), 'Applications', 'Google Chrome.app', 'Contents', 'MacOS', 'Google Chrome'),
56
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
57
+ path.join(os.homedir(), 'Applications', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'),
58
+ ];
59
+ }
60
+ if (process.platform === 'win32') {
61
+ const programFiles = process.env.ProgramFiles || '';
62
+ const programFilesX86 = process.env['ProgramFiles(x86)'] || '';
63
+ const localAppData = process.env.LOCALAPPDATA || '';
64
+ return [
65
+ programFiles ? path.join(programFiles, 'Google', 'Chrome', 'Application', 'chrome.exe') : '',
66
+ programFilesX86 ? path.join(programFilesX86, 'Google', 'Chrome', 'Application', 'chrome.exe') : '',
67
+ localAppData ? path.join(localAppData, 'Google', 'Chrome', 'Application', 'chrome.exe') : '',
68
+ ].filter(Boolean);
69
+ }
70
+ return [];
71
+ }
72
+ function resolveMacBrowserAppName(chromeExecutable) {
73
+ if (process.platform !== 'darwin')
74
+ return null;
75
+ if (chromeExecutable.includes('/Chromium.app/'))
76
+ return 'Chromium';
77
+ if (chromeExecutable.includes('/Google Chrome.app/'))
78
+ return 'Google Chrome';
79
+ return null;
80
+ }
81
+ function normalizeManagedBrowserWindow(chromeExecutable) {
82
+ if (process.platform !== 'darwin')
83
+ return;
84
+ const appName = resolveMacBrowserAppName(chromeExecutable);
85
+ if (!appName)
86
+ return;
87
+ const script = [
88
+ 'set screenBounds to {0, 0, 1440, 900}',
89
+ 'try',
90
+ ' tell application "Finder" to set screenBounds to bounds of window of desktop',
91
+ 'end try',
92
+ `tell application "${appName}"`,
93
+ ' activate',
94
+ ' delay 0.4',
95
+ ' try',
96
+ ' set zoomed of front window to true',
97
+ ' end try',
98
+ ' try',
99
+ ' set bounds of front window to screenBounds',
100
+ ' end try',
101
+ 'end tell',
102
+ ].join('\n');
103
+ setTimeout(() => {
104
+ try {
105
+ const proc = spawn('osascript', ['-e', script], {
106
+ detached: true,
107
+ stdio: 'ignore',
108
+ windowsHide: true,
109
+ });
110
+ proc.unref();
111
+ }
112
+ catch { }
113
+ }, 700);
114
+ }
115
+ export function getManagedBrowserProfileDir() {
116
+ return path.join(os.homedir(), MANAGED_BROWSER_PROFILE_SUBPATH);
117
+ }
118
+ export function ensureManagedBrowserProfileDir() {
119
+ const profileDir = getManagedBrowserProfileDir();
120
+ fs.mkdirSync(profileDir, { recursive: true });
121
+ return profileDir;
122
+ }
123
+ export function getManagedBrowserMcpArgs(profileDir = getManagedBrowserProfileDir(), options = {}) {
124
+ if (options.cdpEndpoint) {
125
+ return ['--cdp-endpoint', options.cdpEndpoint];
126
+ }
127
+ return [
128
+ ...PLAYWRIGHT_MCP_BROWSER_ARGS,
129
+ ...(options.headless ? ['--headless'] : []),
130
+ '--user-data-dir',
131
+ profileDir,
132
+ ];
133
+ }
134
+ export function resolveManagedBrowserMcpCliPath() {
135
+ try {
136
+ const packageJsonPath = require.resolve(`${PLAYWRIGHT_MCP_PACKAGE_NAME}/package.json`);
137
+ const cliPath = path.join(path.dirname(packageJsonPath), 'cli.js');
138
+ return fs.existsSync(cliPath) ? cliPath : null;
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ export function resolveManagedBrowserMcpCommand(profileDir = getManagedBrowserProfileDir(), options = {}) {
145
+ const cliPath = resolveManagedBrowserMcpCliPath();
146
+ const runtimeArgs = getManagedBrowserMcpArgs(profileDir, options);
147
+ if (cliPath) {
148
+ return {
149
+ command: process.execPath,
150
+ args: [cliPath, ...runtimeArgs],
151
+ source: 'local',
152
+ };
153
+ }
154
+ return {
155
+ command: 'npx',
156
+ args: ['-y', PLAYWRIGHT_MCP_PACKAGE_SPEC, ...runtimeArgs],
157
+ source: 'npx',
158
+ };
159
+ }
160
+ export function getManagedBrowserLaunchArgs(profileDir = getManagedBrowserProfileDir()) {
161
+ const windowArgs = process.platform === 'darwin'
162
+ ? ['--start-maximized', '--start-fullscreen', '--window-position=0,0']
163
+ : ['--start-maximized'];
164
+ return [
165
+ `--user-data-dir=${profileDir}`,
166
+ `--remote-debugging-port=${MANAGED_BROWSER_DEBUG_PORT}`,
167
+ '--no-first-run',
168
+ '--no-default-browser-check',
169
+ '--new-window',
170
+ ...windowArgs,
171
+ 'about:blank',
172
+ ];
173
+ }
174
+ export function findChromeExecutable() {
175
+ for (const candidate of resolveCommonChromePaths()) {
176
+ if (fs.existsSync(candidate))
177
+ return candidate;
178
+ }
179
+ const commands = process.platform === 'win32'
180
+ ? ['chrome', 'google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser']
181
+ : ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'chrome'];
182
+ for (const command of commands) {
183
+ const resolved = resolveOnPath(command);
184
+ if (resolved)
185
+ return resolved;
186
+ }
187
+ return null;
188
+ }
189
+ function getManagedBrowserSetupStatePath(profileDir = getManagedBrowserProfileDir()) {
190
+ return path.join(path.dirname(profileDir), MANAGED_BROWSER_SETUP_STATE_FILENAME);
191
+ }
192
+ function readManagedBrowserSetupState(profileDir = getManagedBrowserProfileDir()) {
193
+ try {
194
+ const raw = fs.readFileSync(getManagedBrowserSetupStatePath(profileDir), 'utf8');
195
+ const parsed = JSON.parse(raw);
196
+ if (!parsed || typeof parsed.pid !== 'number' || parsed.pid <= 0)
197
+ return null;
198
+ if (typeof parsed.profileDir !== 'string' || !parsed.profileDir.trim())
199
+ return null;
200
+ if (typeof parsed.chromeExecutable !== 'string' || !parsed.chromeExecutable.trim())
201
+ return null;
202
+ return {
203
+ pid: parsed.pid,
204
+ profileDir: parsed.profileDir,
205
+ chromeExecutable: parsed.chromeExecutable,
206
+ debugPort: typeof parsed.debugPort === 'number' && parsed.debugPort > 0 ? parsed.debugPort : MANAGED_BROWSER_DEBUG_PORT,
207
+ launchedAt: typeof parsed.launchedAt === 'string' ? parsed.launchedAt : new Date().toISOString(),
208
+ };
209
+ }
210
+ catch {
211
+ return null;
212
+ }
213
+ }
214
+ function writeManagedBrowserSetupState(state) {
215
+ const statePath = getManagedBrowserSetupStatePath(state.profileDir);
216
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
217
+ fs.writeFileSync(statePath, JSON.stringify(state), 'utf8');
218
+ }
219
+ function clearManagedBrowserSetupState(profileDir = getManagedBrowserProfileDir()) {
220
+ try {
221
+ fs.rmSync(getManagedBrowserSetupStatePath(profileDir), { force: true });
222
+ }
223
+ catch { }
224
+ }
225
+ function isPidAlive(pid) {
226
+ try {
227
+ process.kill(pid, 0);
228
+ return true;
229
+ }
230
+ catch {
231
+ return false;
232
+ }
233
+ }
234
+ function readProcessCommand(pid) {
235
+ try {
236
+ if (process.platform === 'win32') {
237
+ const result = spawnSync('powershell', ['-NoProfile', '-Command', `(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`], { encoding: 'utf8' });
238
+ if (result.status !== 0)
239
+ return '';
240
+ return String(result.stdout || '').trim();
241
+ }
242
+ const result = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8' });
243
+ if (result.status !== 0)
244
+ return '';
245
+ return String(result.stdout || '').trim();
246
+ }
247
+ catch {
248
+ return '';
249
+ }
250
+ }
251
+ function commandUsesManagedProfile(command, profileDir) {
252
+ const normalizedCommand = command.trim();
253
+ return normalizedCommand.includes(`--user-data-dir=${profileDir}`)
254
+ || normalizedCommand.includes(`--user-data-dir ${profileDir}`);
255
+ }
256
+ function isManagedBrowserRootProcess(command, profileDir) {
257
+ if (!commandUsesManagedProfile(command, profileDir))
258
+ return false;
259
+ return !command.includes(' --type=');
260
+ }
261
+ function findManagedBrowserRootPids(profileDir = getManagedBrowserProfileDir()) {
262
+ if (process.platform === 'win32') {
263
+ const tracked = readManagedBrowserSetupState(profileDir);
264
+ if (!tracked || !isPidAlive(tracked.pid))
265
+ return [];
266
+ return commandUsesManagedProfile(readProcessCommand(tracked.pid), profileDir) ? [tracked.pid] : [];
267
+ }
268
+ try {
269
+ const result = spawnSync('ps', ['-ax', '-o', 'pid=,command='], { encoding: 'utf8' });
270
+ if (result.status !== 0)
271
+ return [];
272
+ const lines = String(result.stdout || '').split(/\r?\n/).map(line => line.trim()).filter(Boolean);
273
+ const pids = new Set();
274
+ for (const line of lines) {
275
+ const match = line.match(/^(\d+)\s+(.*)$/);
276
+ if (!match)
277
+ continue;
278
+ const pid = Number(match[1]);
279
+ const command = match[2] || '';
280
+ if (!Number.isFinite(pid) || pid <= 0)
281
+ continue;
282
+ if (!isManagedBrowserRootProcess(command, profileDir))
283
+ continue;
284
+ pids.add(pid);
285
+ }
286
+ return [...pids];
287
+ }
288
+ catch {
289
+ return [];
290
+ }
291
+ }
292
+ function resolveManagedBrowserRunningState(profileDir = getManagedBrowserProfileDir()) {
293
+ const tracked = readManagedBrowserSetupState(profileDir);
294
+ if (tracked) {
295
+ const command = readProcessCommand(tracked.pid);
296
+ if (isPidAlive(tracked.pid) && commandUsesManagedProfile(command, profileDir)) {
297
+ return { running: true, pid: tracked.pid };
298
+ }
299
+ clearManagedBrowserSetupState(profileDir);
300
+ }
301
+ const rootPids = findManagedBrowserRootPids(profileDir);
302
+ if (!rootPids.length)
303
+ return { running: false, pid: null };
304
+ return { running: true, pid: rootPids[0] ?? null };
305
+ }
306
+ function sleep(ms) {
307
+ return new Promise(resolve => setTimeout(resolve, ms));
308
+ }
309
+ async function waitForPidExit(pid, timeoutMs = MANAGED_BROWSER_SHUTDOWN_TIMEOUT_MS) {
310
+ const deadline = Date.now() + timeoutMs;
311
+ while (Date.now() < deadline) {
312
+ if (!isPidAlive(pid))
313
+ return true;
314
+ await sleep(MANAGED_BROWSER_SHUTDOWN_POLL_MS);
315
+ }
316
+ return !isPidAlive(pid);
317
+ }
318
+ async function terminatePid(pid) {
319
+ if (!isPidAlive(pid))
320
+ return true;
321
+ try {
322
+ process.kill(pid, 'SIGTERM');
323
+ }
324
+ catch {
325
+ return !isPidAlive(pid);
326
+ }
327
+ if (await waitForPidExit(pid))
328
+ return true;
329
+ try {
330
+ process.kill(pid, 'SIGKILL');
331
+ }
332
+ catch { }
333
+ return waitForPidExit(pid, 1_000);
334
+ }
335
+ function getManagedBrowserDebugEndpoint(port) {
336
+ return `http://127.0.0.1:${port}`;
337
+ }
338
+ async function resolveManagedBrowserCdpEndpoint(port) {
339
+ return resolveBrowserCdpEndpoint(getManagedBrowserDebugEndpoint(port));
340
+ }
341
+ async function waitForManagedBrowserCdpEndpoint(port, timeoutMs = 6_000) {
342
+ const deadline = Date.now() + timeoutMs;
343
+ while (Date.now() < deadline) {
344
+ const endpoint = await resolveManagedBrowserCdpEndpoint(port);
345
+ if (endpoint)
346
+ return endpoint;
347
+ await sleep(200);
348
+ }
349
+ return resolveManagedBrowserCdpEndpoint(port);
350
+ }
351
+ async function closeManagedBrowserProcesses(profileDir) {
352
+ const tracked = readManagedBrowserSetupState(profileDir);
353
+ const candidates = new Set(findManagedBrowserRootPids(profileDir));
354
+ if (tracked?.pid)
355
+ candidates.add(tracked.pid);
356
+ const closedPids = [];
357
+ for (const pid of candidates) {
358
+ if (await terminatePid(pid))
359
+ closedPids.push(pid);
360
+ }
361
+ const remaining = findManagedBrowserRootPids(profileDir);
362
+ if (remaining.length) {
363
+ throw new Error(`Managed browser profile is still in use by pid ${remaining.join(', ')}. Close the setup browser before retrying.`);
364
+ }
365
+ clearManagedBrowserSetupState(profileDir);
366
+ return closedPids;
367
+ }
368
+ export async function prepareManagedBrowserForAutomation(profileDir = getManagedBrowserProfileDir(), options = {}) {
369
+ const tracked = readManagedBrowserSetupState(profileDir);
370
+ const runningState = resolveManagedBrowserRunningState(profileDir);
371
+ if (runningState.running) {
372
+ const cdpEndpoint = await resolveManagedBrowserCdpEndpoint(tracked?.debugPort || MANAGED_BROWSER_DEBUG_PORT);
373
+ if (cdpEndpoint) {
374
+ return {
375
+ profileDir,
376
+ closedPids: [],
377
+ cdpEndpoint,
378
+ connectionMode: 'attach',
379
+ };
380
+ }
381
+ }
382
+ const closedPids = await closeManagedBrowserProcesses(profileDir);
383
+ if (!options.headless) {
384
+ launchManagedBrowserSetup();
385
+ const cdpEndpoint = await waitForManagedBrowserCdpEndpoint(MANAGED_BROWSER_DEBUG_PORT);
386
+ if (cdpEndpoint) {
387
+ return {
388
+ profileDir,
389
+ closedPids,
390
+ cdpEndpoint,
391
+ connectionMode: 'attach',
392
+ };
393
+ }
394
+ }
395
+ return {
396
+ profileDir,
397
+ closedPids,
398
+ cdpEndpoint: null,
399
+ connectionMode: 'launch',
400
+ };
401
+ }
402
+ export function getManagedBrowserStatus() {
403
+ const profileDir = getManagedBrowserProfileDir();
404
+ const profileCreated = fs.existsSync(profileDir);
405
+ const chromeExecutable = findChromeExecutable();
406
+ const chromeInstalled = !!chromeExecutable;
407
+ const runningState = resolveManagedBrowserRunningState(profileDir);
408
+ return {
409
+ status: chromeInstalled
410
+ ? profileCreated
411
+ ? 'ready'
412
+ : 'needs_setup'
413
+ : 'chrome_missing',
414
+ profileDir,
415
+ profileCreated,
416
+ chromeInstalled,
417
+ running: runningState.running,
418
+ pid: runningState.pid,
419
+ detail: chromeInstalled
420
+ ? runningState.running
421
+ ? 'Managed browser is open for sign-in. pikiclaw will close it automatically before browser automation starts.'
422
+ : profileCreated
423
+ ? 'Managed browser profile is ready. Launch it to confirm login state. If it is still open later, pikiclaw will close it automatically before browser automation starts.'
424
+ : 'Chrome is installed. Launch the managed browser once and sign in to the sites you need. If it is still open later, pikiclaw will close it automatically before browser automation starts.'
425
+ : 'Chrome is not available on this machine. Install Google Chrome or Chromium to use browser automation.',
426
+ chromeExecutable,
427
+ launchCommand: chromeExecutable ? [chromeExecutable, ...getManagedBrowserLaunchArgs(profileDir)] : [],
428
+ };
429
+ }
430
+ export function launchManagedBrowserSetup() {
431
+ const profileDir = ensureManagedBrowserProfileDir();
432
+ const chromeExecutable = findChromeExecutable();
433
+ if (!chromeExecutable) {
434
+ throw new Error('Chrome is not available on this machine');
435
+ }
436
+ const existing = resolveManagedBrowserRunningState(profileDir);
437
+ if (existing.running) {
438
+ normalizeManagedBrowserWindow(chromeExecutable);
439
+ return { ...getManagedBrowserStatus(), pid: existing.pid };
440
+ }
441
+ const child = spawn(chromeExecutable, getManagedBrowserLaunchArgs(profileDir), {
442
+ detached: true,
443
+ stdio: 'ignore',
444
+ windowsHide: true,
445
+ });
446
+ child.unref();
447
+ normalizeManagedBrowserWindow(chromeExecutable);
448
+ if (child.pid) {
449
+ writeManagedBrowserSetupState({
450
+ pid: child.pid,
451
+ profileDir,
452
+ chromeExecutable,
453
+ debugPort: MANAGED_BROWSER_DEBUG_PORT,
454
+ launchedAt: new Date().toISOString(),
455
+ });
456
+ }
457
+ return { ...getManagedBrowserStatus(), pid: child.pid ?? null };
458
+ }
package/dist/cli.js CHANGED
@@ -198,6 +198,10 @@ async function handleMcpServeMode() {
198
198
  await import('./mcp-session-server.js');
199
199
  return true;
200
200
  }
201
+ if (process.argv.includes('--playwright-mcp-proxy')) {
202
+ await import('./mcp-playwright-proxy.js');
203
+ return true;
204
+ }
201
205
  return false;
202
206
  }
203
207
  /** Print help text and exit. */
package/dist/constants.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * Grouped by domain / module so each subsystem can import only the
5
5
  * bucket it needs.
6
6
  */
7
+ import path from 'node:path';
7
8
  // ---------------------------------------------------------------------------
8
9
  // MCP bridge
9
10
  // ---------------------------------------------------------------------------
@@ -45,11 +46,21 @@ export const DASHBOARD_TIMEOUTS = {
45
46
  appiumReachable: 3_000,
46
47
  /** Delay between Appium server startup polls. */
47
48
  appiumStartPoll: 1_000,
48
- /** Timeout for the Playwright MCP extension validation spawn. */
49
- extensionValidationSpawn: 12_000,
50
- /** Grace period to let Playwright MCP server prove it stays alive. */
51
- extensionValidationAlive: 5_000,
52
49
  };
50
+ // ---------------------------------------------------------------------------
51
+ // Browser automation
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Stable relative path for the managed Chrome profile under the home directory.
55
+ * Keep this outside config-specific directories so `npm run dev` and the main
56
+ * runtime share the same browser login state.
57
+ */
58
+ export const MANAGED_BROWSER_PROFILE_SUBPATH = path.join('.pikiclaw', 'browser', 'chrome-profile');
59
+ /** Base Playwright MCP args for the managed browser integration. */
60
+ export const PLAYWRIGHT_MCP_PACKAGE_NAME = '@playwright/mcp';
61
+ export const PLAYWRIGHT_MCP_PACKAGE_VERSION = '0.0.68';
62
+ export const PLAYWRIGHT_MCP_PACKAGE_SPEC = `${PLAYWRIGHT_MCP_PACKAGE_NAME}@${PLAYWRIGHT_MCP_PACKAGE_VERSION}`;
63
+ export const PLAYWRIGHT_MCP_BROWSER_ARGS = ['--browser', 'chrome'];
53
64
  /** Dashboard session pagination limits. */
54
65
  export const DASHBOARD_PAGINATION = {
55
66
  defaultPageSize: 6,