@houwert/conductor 0.6.0 → 0.7.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/dist/daemon/server.js +13 -3
- package/dist/daemon/web-server.js +106 -26
- package/dist/index.js +12 -1
- package/drivers/ios/conductor-driver-ios.zip +0 -0
- package/drivers/ios/conductor-driver-iosUITests-Runner.zip +0 -0
- package/drivers/tvos/conductor-driver-tvos.zip +0 -0
- package/drivers/tvos/conductor-driver-tvosUITests-Runner.zip +0 -0
- package/package.json +1 -1
- package/skills/conductor/SKILL.md +1 -1
- package/skills/skills.yaml +1 -1
package/dist/daemon/server.js
CHANGED
|
@@ -25,6 +25,14 @@ const web_server_js_1 = require("./web-server.js");
|
|
|
25
25
|
const log_collector_js_1 = require("./log-collector.js");
|
|
26
26
|
const session_js_1 = require("../session.js");
|
|
27
27
|
const sessionName = process.argv[2] ?? 'default';
|
|
28
|
+
/**
|
|
29
|
+
* CDP URL for connecting to an external browser (e.g. Stagehand's embedded
|
|
30
|
+
* webview). When set, the web driver attaches via Playwright's connectOverCDP
|
|
31
|
+
* instead of launching its own browser.
|
|
32
|
+
*
|
|
33
|
+
* Set by the host IDE (Stagehand) via the agent subprocess environment.
|
|
34
|
+
*/
|
|
35
|
+
const cdpUrl = process.env.CONDUCTOR_CDP_URL || undefined;
|
|
28
36
|
const SOCKET_PATH = (0, protocol_js_1.socketPath)(sessionName);
|
|
29
37
|
const PID_FILE = (0, protocol_js_1.pidFile)(sessionName);
|
|
30
38
|
const LOG_FILE = (0, protocol_js_1.logFile)(sessionName);
|
|
@@ -75,7 +83,7 @@ async function ensureDriverRunning() {
|
|
|
75
83
|
await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* dismissAfterLaunch */ false);
|
|
76
84
|
}
|
|
77
85
|
else if (driverPlatform === 'web') {
|
|
78
|
-
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog);
|
|
86
|
+
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl);
|
|
79
87
|
}
|
|
80
88
|
else {
|
|
81
89
|
await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
|
|
@@ -277,7 +285,9 @@ async function main() {
|
|
|
277
285
|
await (0, bootstrap_js_1.installDriver)(sessionName);
|
|
278
286
|
dlog(`Driver installation complete`);
|
|
279
287
|
}
|
|
280
|
-
else if (platform === 'web') {
|
|
288
|
+
else if (platform === 'web' && !cdpUrl) {
|
|
289
|
+
// Only install Playwright browser when launching standalone.
|
|
290
|
+
// In CDP mode we attach to the host app's browser (e.g. Electron).
|
|
281
291
|
const browser = (0, bootstrap_js_1.webBrowserName)(sessionName);
|
|
282
292
|
await (0, bootstrap_js_1.ensurePlaywrightBrowser)(browser, dlog);
|
|
283
293
|
}
|
|
@@ -291,7 +301,7 @@ async function main() {
|
|
|
291
301
|
await (0, bootstrap_js_1.startTvOSDriver)(sessionName, driverPort, /* dismissAfterLaunch */ true);
|
|
292
302
|
}
|
|
293
303
|
else if (platform === 'web') {
|
|
294
|
-
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog);
|
|
304
|
+
await (0, web_server_js_1.startWebServer)(driverPort, (0, bootstrap_js_1.webBrowserName)(sessionName), dlog, cdpUrl);
|
|
295
305
|
}
|
|
296
306
|
else {
|
|
297
307
|
await (0, bootstrap_js_1.startAndroidDriver)(sessionName, driverPort);
|
|
@@ -426,19 +426,59 @@ let _browser = null;
|
|
|
426
426
|
let _context = null;
|
|
427
427
|
let _page = null;
|
|
428
428
|
let _server = null;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
429
|
+
/**
|
|
430
|
+
* True when connected to an external browser via CDP (e.g. Stagehand's
|
|
431
|
+
* embedded webview). In this mode we must NOT close the browser on shutdown
|
|
432
|
+
* — we only disconnect.
|
|
433
|
+
*/
|
|
434
|
+
let _cdpMode = false;
|
|
435
|
+
async function startWebServer(port, browserName = 'chromium', dlog = () => { }, cdpUrl) {
|
|
436
|
+
if (cdpUrl) {
|
|
437
|
+
// ── CDP mode: attach to an existing browser (e.g. Electron webview) ───
|
|
438
|
+
dlog(`Connecting to existing browser via CDP: ${cdpUrl}`);
|
|
439
|
+
_browser = await playwright_core_1.chromium.connectOverCDP(cdpUrl);
|
|
440
|
+
_cdpMode = true;
|
|
441
|
+
// Use the first existing context and page. The host app (e.g. Stagehand)
|
|
442
|
+
// already created them — we just take a handle.
|
|
443
|
+
const contexts = _browser.contexts();
|
|
444
|
+
if (contexts.length === 0) {
|
|
445
|
+
throw new Error('No browser contexts found via CDP — is the webview loaded?');
|
|
446
|
+
}
|
|
447
|
+
// Find the context with a real page (not about:blank, not the host app).
|
|
448
|
+
for (const ctx of contexts) {
|
|
449
|
+
const pages = ctx.pages();
|
|
450
|
+
const candidate = pages.find((p) => p.url() !== 'about:blank' && !p.url().startsWith('file://'));
|
|
451
|
+
if (candidate) {
|
|
452
|
+
_context = ctx;
|
|
453
|
+
_page = candidate;
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// Fallback: just use the first context's first page.
|
|
458
|
+
if (!_page) {
|
|
459
|
+
_context = contexts[0];
|
|
460
|
+
const pages = _context.pages();
|
|
461
|
+
_page = pages[0] ?? (await _context.newPage());
|
|
462
|
+
}
|
|
463
|
+
attachConsoleListeners(_page);
|
|
464
|
+
dlog(`CDP connected — page: ${_page.url()}`);
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
// ── Standalone mode: launch a fresh browser ───────────────────────────
|
|
468
|
+
const browserType = browserName === 'firefox' ? playwright_core_1.firefox : browserName === 'webkit' ? playwright_core_1.webkit : playwright_core_1.chromium;
|
|
469
|
+
dlog(`Launching ${browserName} browser...`);
|
|
470
|
+
_browser = await browserType.launch({
|
|
471
|
+
headless: false,
|
|
472
|
+
args: browserName === 'chromium' ? ['--disable-search-engine-choice-screen'] : undefined,
|
|
473
|
+
});
|
|
474
|
+
_context = await _browser.newContext({
|
|
475
|
+
viewport: DEFAULT_VIEWPORT,
|
|
476
|
+
});
|
|
477
|
+
_page = await _context.newPage();
|
|
478
|
+
attachConsoleListeners(_page);
|
|
479
|
+
dlog(`Browser ready, page created`);
|
|
480
|
+
_cdpMode = false;
|
|
481
|
+
}
|
|
442
482
|
_server = http_1.default.createServer(async (req, res) => {
|
|
443
483
|
try {
|
|
444
484
|
await handleRequest(req, res, dlog);
|
|
@@ -462,18 +502,38 @@ async function stopWebServer() {
|
|
|
462
502
|
_server.close();
|
|
463
503
|
_server = null;
|
|
464
504
|
}
|
|
465
|
-
if (
|
|
466
|
-
|
|
505
|
+
if (_cdpMode) {
|
|
506
|
+
// CDP mode: we don't own the browser — just release our handles.
|
|
507
|
+
// Do NOT close the page, context, or browser.
|
|
467
508
|
_page = null;
|
|
468
|
-
}
|
|
469
|
-
if (_context) {
|
|
470
|
-
await _context.close().catch(() => { });
|
|
471
509
|
_context = null;
|
|
510
|
+
if (_browser) {
|
|
511
|
+
// Playwright's connectOverCDP browser supports disconnect() but not close().
|
|
512
|
+
try {
|
|
513
|
+
_browser.close().catch(() => { });
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
/* not all CDP browsers support close gracefully */
|
|
517
|
+
}
|
|
518
|
+
_browser = null;
|
|
519
|
+
}
|
|
472
520
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
521
|
+
else {
|
|
522
|
+
// Standalone mode: we launched the browser, so tear it all down.
|
|
523
|
+
if (_page) {
|
|
524
|
+
await _page.close().catch(() => { });
|
|
525
|
+
_page = null;
|
|
526
|
+
}
|
|
527
|
+
if (_context) {
|
|
528
|
+
await _context.close().catch(() => { });
|
|
529
|
+
_context = null;
|
|
530
|
+
}
|
|
531
|
+
if (_browser) {
|
|
532
|
+
await _browser.close().catch(() => { });
|
|
533
|
+
_browser = null;
|
|
534
|
+
}
|
|
476
535
|
}
|
|
536
|
+
_cdpMode = false;
|
|
477
537
|
}
|
|
478
538
|
/** Playwright / CDP errors when the tab, context, or session died but our JS refs still exist. */
|
|
479
539
|
function isClosedLikeError(err) {
|
|
@@ -483,20 +543,40 @@ function isClosedLikeError(err) {
|
|
|
483
543
|
/**
|
|
484
544
|
* Drop the context and open a fresh one. Used when newPage/goto fails after the user closed
|
|
485
545
|
* the last tab (Chromium can quit the window) or the CDP target is gone while isConnected stays true.
|
|
546
|
+
*
|
|
547
|
+
* In CDP mode, we can't create new contexts — the host app owns the browser.
|
|
548
|
+
* Instead we try to re-acquire an existing context/page.
|
|
486
549
|
*/
|
|
487
550
|
async function recreateBrowserContext(dlog) {
|
|
488
551
|
dlog?.('Web driver: recreating browser context');
|
|
489
|
-
if (_context) {
|
|
490
|
-
await _context.close().catch(() => { });
|
|
491
|
-
_context = null;
|
|
492
|
-
}
|
|
493
|
-
_page = null;
|
|
494
552
|
if (!_browser) {
|
|
495
553
|
throw new Error('No page available');
|
|
496
554
|
}
|
|
497
555
|
if (!_browser.isConnected()) {
|
|
498
556
|
throw new Error('Browser has been closed. Restart the web driver (e.g. conductor daemon-start --device web).');
|
|
499
557
|
}
|
|
558
|
+
if (_cdpMode) {
|
|
559
|
+
// In CDP mode, try to re-acquire a page from existing contexts.
|
|
560
|
+
_page = null;
|
|
561
|
+
_context = null;
|
|
562
|
+
const contexts = _browser.contexts();
|
|
563
|
+
for (const ctx of contexts) {
|
|
564
|
+
const pages = ctx.pages();
|
|
565
|
+
const candidate = pages.find((p) => !p.isClosed());
|
|
566
|
+
if (candidate) {
|
|
567
|
+
_context = ctx;
|
|
568
|
+
_page = candidate;
|
|
569
|
+
attachConsoleListeners(_page);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
throw new Error('No live pages found via CDP — is the webview still open?');
|
|
574
|
+
}
|
|
575
|
+
if (_context) {
|
|
576
|
+
await _context.close().catch(() => { });
|
|
577
|
+
_context = null;
|
|
578
|
+
}
|
|
579
|
+
_page = null;
|
|
500
580
|
_context = await _browser.newContext({
|
|
501
581
|
viewport: DEFAULT_VIEWPORT,
|
|
502
582
|
});
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,9 @@ const delete_device_js_1 = require("./commands/delete-device.js");
|
|
|
46
46
|
const logs_js_1 = require("./commands/logs.js");
|
|
47
47
|
const device_picker_js_1 = require("./device-picker.js");
|
|
48
48
|
const update_check_js_1 = require("./update-check.js");
|
|
49
|
+
const pkg_root_js_1 = require("./pkg-root.js");
|
|
50
|
+
const fs_1 = __importDefault(require("fs"));
|
|
51
|
+
const path_1 = __importDefault(require("path"));
|
|
49
52
|
const COMMAND_HELP = {
|
|
50
53
|
'start-device': start_device_js_1.HELP,
|
|
51
54
|
'delete-device': delete_device_js_1.HELP,
|
|
@@ -96,6 +99,7 @@ const OPTIONS_HELP = `Options:
|
|
|
96
99
|
--platform <p> Filter to devices of this platform (ios, android, tvos, web)
|
|
97
100
|
--json Output as machine-readable JSON
|
|
98
101
|
--verbose, -v Log daemon calls, fallbacks, and raw output
|
|
102
|
+
--version, -V Print version number
|
|
99
103
|
--help, -h Show this help`;
|
|
100
104
|
const HELP = `Usage: conductor <command> [args] [options]
|
|
101
105
|
|
|
@@ -109,6 +113,7 @@ async function main() {
|
|
|
109
113
|
boolean: [
|
|
110
114
|
'json',
|
|
111
115
|
'help',
|
|
116
|
+
'version',
|
|
112
117
|
'clear',
|
|
113
118
|
'list',
|
|
114
119
|
'verbose',
|
|
@@ -155,12 +160,18 @@ async function main() {
|
|
|
155
160
|
'metro-port',
|
|
156
161
|
'level',
|
|
157
162
|
],
|
|
158
|
-
alias: { h: 'help', v: 'verbose' },
|
|
163
|
+
alias: { h: 'help', v: 'verbose', V: 'version' },
|
|
159
164
|
});
|
|
160
165
|
if (argv['verbose'])
|
|
161
166
|
(0, verbose_js_1.setVerbose)(true);
|
|
162
167
|
const [command, ...rest] = argv._;
|
|
163
168
|
const opts = { json: argv['json'] };
|
|
169
|
+
if (argv['version']) {
|
|
170
|
+
const pkgRoot = (0, pkg_root_js_1.findPkgRoot)(__dirname);
|
|
171
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(pkgRoot, 'package.json'), 'utf-8'));
|
|
172
|
+
console.log(pkg.version);
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
164
175
|
// Handle help and unknown commands before device resolution —
|
|
165
176
|
// no point prompting for a device if we're just printing help or erroring out.
|
|
166
177
|
if (!command || argv['help']) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED