@workglow/browser-control 0.2.30 → 0.2.32

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.
@@ -1,3468 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/task/register.server.ts
5
- import path from "node:path";
6
-
7
- // src/task/PlaywrightBackend.ts
8
- var playwrightModule;
9
- async function getPlaywright() {
10
- if (!playwrightModule) {
11
- playwrightModule = await import("playwright");
12
- }
13
- return playwrightModule;
14
- }
15
- function parseAriaLine(line) {
16
- const match = line.match(/^(\s*)-\s+(.*)$/);
17
- if (!match)
18
- return null;
19
- const indent = match[1].length;
20
- let rest = match[2].trim();
21
- const hasChildren = rest.endsWith(":");
22
- if (hasChildren)
23
- rest = rest.slice(0, -1).trim();
24
- const attrs = {};
25
- rest = rest.replace(/\[([^\]]+)\]/g, (_m, attr) => {
26
- const eqIdx = attr.indexOf("=");
27
- if (eqIdx !== -1) {
28
- attrs[attr.slice(0, eqIdx).trim()] = attr.slice(eqIdx + 1).trim();
29
- } else {
30
- attrs[attr.trim()] = "true";
31
- }
32
- return "";
33
- }).trim();
34
- const roleNameMatch = rest.match(/^(\S+)(?:\s+"((?:[^"\\]|\\.)*)")?/);
35
- if (!roleNameMatch)
36
- return null;
37
- const role = roleNameMatch[1];
38
- const name = roleNameMatch[2] !== undefined ? roleNameMatch[2].replace(/\\"/g, '"') : "";
39
- return { indent, role, name, attrs, hasChildren };
40
- }
41
- function parseAriaYaml(yaml, refCounter, refMap) {
42
- const lines = yaml.split(`
43
- `);
44
- const stack = [];
45
- let root = null;
46
- for (const line of lines) {
47
- if (!line.trim())
48
- continue;
49
- const parsed = parseAriaLine(line);
50
- if (!parsed)
51
- continue;
52
- const ref = `e${++refCounter.count}`;
53
- const locatorStr = buildLocatorString(parsed.role, parsed.name);
54
- refMap.set(ref, locatorStr);
55
- const node = {
56
- ref,
57
- role: parsed.role,
58
- name: parsed.name
59
- };
60
- if (parsed.attrs.level !== undefined) {
61
- node.level = parseInt(parsed.attrs.level, 10);
62
- }
63
- if (parsed.attrs.checked !== undefined) {
64
- node.checked = parsed.attrs.checked === "mixed" ? "mixed" : parsed.attrs.checked === "true";
65
- }
66
- if (parsed.attrs.disabled !== undefined) {
67
- node.disabled = parsed.attrs.disabled === "true";
68
- }
69
- if (parsed.attrs.expanded !== undefined) {
70
- node.expanded = parsed.attrs.expanded === "true";
71
- }
72
- if (parsed.attrs.pressed !== undefined) {
73
- node.pressed = parsed.attrs.pressed === "mixed" ? "mixed" : parsed.attrs.pressed === "true";
74
- }
75
- if (parsed.attrs.selected !== undefined) {
76
- node.selected = parsed.attrs.selected === "true";
77
- }
78
- if (parsed.attrs.value !== undefined) {
79
- const numVal = Number(parsed.attrs.value);
80
- node.value = isNaN(numVal) ? parsed.attrs.value : numVal;
81
- }
82
- if (parsed.hasChildren) {
83
- node.children = [];
84
- }
85
- while (stack.length > 0 && stack[stack.length - 1].indent >= parsed.indent) {
86
- stack.pop();
87
- }
88
- if (stack.length === 0) {
89
- root = node;
90
- } else {
91
- const parent = stack[stack.length - 1].node;
92
- if (!parent.children)
93
- parent.children = [];
94
- parent.children.push(node);
95
- }
96
- stack.push({ node, indent: parsed.indent });
97
- }
98
- if (!root) {
99
- const ref = `e${++refCounter.count}`;
100
- refMap.set(ref, 'locator("body")');
101
- return { ref, role: "document", name: "" };
102
- }
103
- return root;
104
- }
105
- function buildLocatorString(role, name) {
106
- if (role === "text" || role === "StaticText") {
107
- return `getByText:${name}`;
108
- }
109
- if (name) {
110
- return `getByRole:${role}:${name}`;
111
- }
112
- return `getByRole:${role}:`;
113
- }
114
-
115
- class PlaywrightBackend {
116
- sharedLocalBrowser;
117
- constructor(sharedLocalBrowser) {
118
- this.sharedLocalBrowser = sharedLocalBrowser;
119
- }
120
- _browser = null;
121
- _context = null;
122
- _page = null;
123
- _connected = false;
124
- _launchedLocalChromium = false;
125
- _refMap = new Map;
126
- _refCounter = { count: 0 };
127
- _dialogHandler = null;
128
- async connect(options = {}) {
129
- const pw = await getPlaywright();
130
- const { headless = true, cdpUrl, backend = "local" } = options;
131
- if (backend === "cloud" || cdpUrl) {
132
- if (this.sharedLocalBrowser) {
133
- throw new Error("PlaywrightBackend: sharedLocalBrowser is only supported for local backend");
134
- }
135
- this._launchedLocalChromium = false;
136
- if (!cdpUrl) {
137
- throw new Error("PlaywrightBackend: cdpUrl is required for cloud backend");
138
- }
139
- this._browser = await pw.chromium.connectOverCDP(cdpUrl);
140
- const contexts = this._browser.contexts();
141
- this._context = contexts.length > 0 ? contexts[0] : await this._browser.newContext();
142
- const pages = this._context.pages();
143
- this._page = pages.length > 0 ? pages[0] : await this._context.newPage();
144
- } else {
145
- if (this.sharedLocalBrowser) {
146
- this._browser = this.sharedLocalBrowser;
147
- this._launchedLocalChromium = false;
148
- } else {
149
- this._launchedLocalChromium = true;
150
- this._browser = await pw.chromium.launch({
151
- headless,
152
- args: ["--disable-dev-shm-usage"]
153
- });
154
- }
155
- this._context = await this._browser.newContext();
156
- this._page = await this._context.newPage();
157
- }
158
- this._page.on("dialog", async (dialog) => {
159
- const info = {
160
- type: dialog.type(),
161
- message: dialog.message(),
162
- defaultValue: dialog.defaultValue() ?? undefined
163
- };
164
- if (this._dialogHandler) {
165
- const action = await this._dialogHandler(info);
166
- if (action.accept) {
167
- await dialog.accept("promptText" in action ? action.promptText : undefined);
168
- } else {
169
- await dialog.dismiss();
170
- }
171
- } else {
172
- await dialog.dismiss();
173
- }
174
- });
175
- this._connected = true;
176
- }
177
- async disconnect() {
178
- this._connected = false;
179
- const page = this._page;
180
- const context = this._context;
181
- const browser = this._browser;
182
- const launchedLocal = this._launchedLocalChromium;
183
- const sharedLocal = this.sharedLocalBrowser !== undefined;
184
- this._page = null;
185
- this._context = null;
186
- this._browser = null;
187
- this._launchedLocalChromium = false;
188
- try {
189
- if (launchedLocal || sharedLocal) {
190
- if (page) {
191
- try {
192
- await page.close({ runBeforeUnload: false });
193
- } catch {}
194
- }
195
- if (context) {
196
- try {
197
- await context.close();
198
- } catch {}
199
- }
200
- }
201
- if (browser && !sharedLocal) {
202
- await browser.close();
203
- }
204
- } finally {
205
- this._refMap.clear();
206
- this._refCounter.count = 0;
207
- }
208
- }
209
- isConnected() {
210
- return this._connected;
211
- }
212
- get page() {
213
- if (!this._page)
214
- throw new Error("PlaywrightBackend: not connected — call connect() first");
215
- return this._page;
216
- }
217
- get context() {
218
- if (!this._context)
219
- throw new Error("PlaywrightBackend: not connected — call connect() first");
220
- return this._context;
221
- }
222
- resolveRef(ref) {
223
- const descriptor = this._refMap.get(ref);
224
- if (!descriptor) {
225
- throw new Error(`PlaywrightBackend: unknown ref "${ref}"`);
226
- }
227
- return this.descriptorToLocator(descriptor);
228
- }
229
- descriptorToLocator(descriptor) {
230
- const page = this.page;
231
- if (descriptor.startsWith("getByRole:")) {
232
- const rest = descriptor.slice("getByRole:".length);
233
- const colonIdx = rest.indexOf(":");
234
- const role = rest.slice(0, colonIdx);
235
- const name = rest.slice(colonIdx + 1);
236
- if (name) {
237
- return page.getByRole(role, { name });
238
- }
239
- return page.getByRole(role);
240
- }
241
- if (descriptor.startsWith("getByText:")) {
242
- const text = descriptor.slice("getByText:".length);
243
- return page.getByText(text);
244
- }
245
- if (descriptor.startsWith("css:")) {
246
- const selector = descriptor.slice("css:".length);
247
- return page.locator(selector);
248
- }
249
- if (descriptor.startsWith("nth:")) {
250
- const withoutPrefix = descriptor.slice("nth:".length);
251
- const lastColon = withoutPrefix.lastIndexOf(":");
252
- const inner = withoutPrefix.slice(0, lastColon);
253
- const idx = parseInt(withoutPrefix.slice(lastColon + 1), 10);
254
- return this.descriptorToLocator(inner).nth(idx);
255
- }
256
- return page.locator(descriptor);
257
- }
258
- async navigate(url, options = {}) {
259
- const { waitUntil = "load", timeout = 30000 } = options;
260
- await this.page.goto(url, { waitUntil, timeout });
261
- }
262
- async goBack(options = {}) {
263
- const { waitUntil = "load", timeout = 30000 } = options;
264
- await this.page.goBack({ waitUntil, timeout });
265
- }
266
- async goForward(options = {}) {
267
- const { waitUntil = "load", timeout = 30000 } = options;
268
- await this.page.goForward({ waitUntil, timeout });
269
- }
270
- async reload(options = {}) {
271
- const { waitUntil = "load", timeout = 30000 } = options;
272
- await this.page.reload({ waitUntil, timeout });
273
- }
274
- async currentUrl() {
275
- return this.page.url();
276
- }
277
- async title() {
278
- return this.page.title();
279
- }
280
- async snapshot(options = {}) {
281
- let locator;
282
- if (options.ref) {
283
- locator = this.resolveRef(options.ref);
284
- } else {
285
- locator = this.page.locator("body");
286
- }
287
- const yaml = await locator.ariaSnapshot();
288
- const root = parseAriaYaml(yaml, this._refCounter, this._refMap);
289
- return { root, yaml };
290
- }
291
- async click(ref, options = {}) {
292
- const locator = this.resolveRef(ref);
293
- const { modifiers, button, clickCount, timeout } = options;
294
- await locator.click({
295
- ...modifiers !== undefined ? { modifiers } : {},
296
- ...button !== undefined ? { button } : {},
297
- ...clickCount !== undefined ? { clickCount } : {},
298
- ...timeout !== undefined ? { timeout } : {}
299
- });
300
- }
301
- async fill(ref, value, options = {}) {
302
- const locator = this.resolveRef(ref);
303
- const { timeout } = options;
304
- await locator.fill(value, ...timeout !== undefined ? [{ timeout }] : []);
305
- }
306
- async selectOption(ref, values, options = {}) {
307
- const locator = this.resolveRef(ref);
308
- const { timeout } = options;
309
- await locator.selectOption(values, ...timeout !== undefined ? [{ timeout }] : []);
310
- }
311
- async hover(ref, options = {}) {
312
- const locator = this.resolveRef(ref);
313
- const { timeout } = options;
314
- await locator.hover(...timeout !== undefined ? [{ timeout }] : []);
315
- }
316
- async clickByRole(role, name, options = {}) {
317
- const { modifiers, button, clickCount, timeout } = options;
318
- await this.page.getByRole(role, { name }).click({
319
- ...modifiers !== undefined ? { modifiers } : {},
320
- ...button !== undefined ? { button } : {},
321
- ...clickCount !== undefined ? { clickCount } : {},
322
- ...timeout !== undefined ? { timeout } : {}
323
- });
324
- }
325
- async fillByLabel(label, value, options = {}) {
326
- const { timeout } = options;
327
- await this.page.getByLabel(label).fill(value, ...timeout !== undefined ? [{ timeout }] : []);
328
- }
329
- async content() {
330
- return this.page.content();
331
- }
332
- async innerHTML(ref) {
333
- const locator = this.resolveRef(ref);
334
- return locator.innerHTML();
335
- }
336
- async textContent(ref) {
337
- const locator = this.resolveRef(ref);
338
- return locator.textContent();
339
- }
340
- async attribute(ref, name) {
341
- const locator = this.resolveRef(ref);
342
- return locator.getAttribute(name);
343
- }
344
- async querySelector(selector) {
345
- const locator = this.page.locator(selector);
346
- const count = await locator.count();
347
- if (count === 0)
348
- return null;
349
- const ref = `e${++this._refCounter.count}`;
350
- this._refMap.set(ref, `css:${selector}`);
351
- return ref;
352
- }
353
- async querySelectorAll(selector) {
354
- const locator = this.page.locator(selector);
355
- const count = await locator.count();
356
- const refs = [];
357
- for (let i = 0;i < count; i++) {
358
- const ref = `e${++this._refCounter.count}`;
359
- this._refMap.set(ref, `nth:css:${selector}:${i}`);
360
- refs.push(ref);
361
- }
362
- return refs;
363
- }
364
- async evaluate(expression) {
365
- return this.page.evaluate(expression);
366
- }
367
- async screenshot(options = {}) {
368
- const { format = "png", quality, fullPage = false } = options;
369
- const screenshotOptions = {
370
- type: format,
371
- fullPage
372
- };
373
- if (format === "jpeg" && quality !== undefined) {
374
- screenshotOptions.quality = quality;
375
- }
376
- return this.page.screenshot(screenshotOptions);
377
- }
378
- async pressKey(key, _options = {}) {
379
- await this.page.keyboard.press(key);
380
- }
381
- async type(text, _options = {}) {
382
- await this.page.keyboard.type(text);
383
- }
384
- async scroll(x, y, ref) {
385
- if (ref) {
386
- const locator = this.resolveRef(ref);
387
- await locator.evaluate((el, args) => {
388
- el.scrollBy(args.x, args.y);
389
- }, { x, y });
390
- } else {
391
- await this.page.mouse.wheel(x, y);
392
- }
393
- }
394
- async uploadFile(ref, paths) {
395
- const locator = this.resolveRef(ref);
396
- await locator.setInputFiles(paths);
397
- }
398
- async download(trigger, options = {}) {
399
- const { timeout } = options;
400
- const [download] = await Promise.all([
401
- this.page.waitForEvent("download", ...timeout !== undefined ? [{ timeout }] : []),
402
- trigger()
403
- ]);
404
- const path = await download.path();
405
- const suggestedFilename = download.suggestedFilename();
406
- if (!path) {
407
- throw new Error("PlaywrightBackend: download failed — path is null");
408
- }
409
- return { path, suggestedFilename };
410
- }
411
- onDialog(handler) {
412
- this._dialogHandler = handler;
413
- }
414
- async tabs() {
415
- const pages = this.context.pages();
416
- return Promise.all(pages.map(async (p, idx) => ({
417
- tabId: String(idx),
418
- url: p.url(),
419
- title: await p.title()
420
- })));
421
- }
422
- async switchTab(tabId) {
423
- const pages = this.context.pages();
424
- const idx = parseInt(tabId, 10);
425
- if (isNaN(idx) || idx < 0 || idx >= pages.length) {
426
- throw new Error(`PlaywrightBackend: no tab with id "${tabId}"`);
427
- }
428
- this._page = pages[idx];
429
- await this._page.bringToFront();
430
- }
431
- async newTab(url) {
432
- const newPage = await this.context.newPage();
433
- if (url) {
434
- await newPage.goto(url, { waitUntil: "load" });
435
- }
436
- const pages = this.context.pages();
437
- const idx = pages.indexOf(newPage);
438
- const tabId = String(idx >= 0 ? idx : pages.length - 1);
439
- return {
440
- tabId,
441
- url: newPage.url(),
442
- title: await newPage.title()
443
- };
444
- }
445
- async closeTab(tabId) {
446
- const pages = this.context.pages();
447
- const idx = parseInt(tabId, 10);
448
- if (isNaN(idx) || idx < 0 || idx >= pages.length) {
449
- throw new Error(`PlaywrightBackend: no tab with id "${tabId}"`);
450
- }
451
- const target = pages[idx];
452
- await target.close();
453
- if (this._page === target) {
454
- const remaining = this.context.pages();
455
- this._page = remaining.length > 0 ? remaining[remaining.length - 1] : null;
456
- }
457
- }
458
- async waitForNavigation(options = {}) {
459
- const { timeout } = options;
460
- await this.page.waitForLoadState("load", ...timeout !== undefined ? [{ timeout }] : []);
461
- }
462
- async waitForSelector(selector, options = {}) {
463
- const { timeout } = options;
464
- await this.page.waitForSelector(selector, ...timeout !== undefined ? [{ timeout }] : []);
465
- const ref = `e${++this._refCounter.count}`;
466
- this._refMap.set(ref, `css:${selector}`);
467
- return ref;
468
- }
469
- async waitForIdle(options = {}) {
470
- const { timeout } = options;
471
- await this.page.waitForLoadState("networkidle", ...timeout !== undefined ? [{ timeout }] : []);
472
- }
473
- networkRequests = (_filter) => {
474
- return Promise.resolve([]);
475
- };
476
- consoleMessages = () => {
477
- return Promise.resolve([]);
478
- };
479
- }
480
-
481
- // src/task/BrowserTaskDeps.ts
482
- import { createServiceToken, globalServiceRegistry } from "@workglow/util";
483
- var BROWSER_CONTROL_TASK_DEPS = createServiceToken("@workglow/browser-control");
484
- function registerBrowserDeps(deps) {
485
- globalServiceRegistry.registerInstance(BROWSER_CONTROL_TASK_DEPS, deps);
486
- }
487
- function getBrowserDeps() {
488
- if (!globalServiceRegistry.has(BROWSER_CONTROL_TASK_DEPS)) {
489
- throw new Error("Browser task dependencies not registered. Import @workglow/browser-control/task-server (Node/Bun) or @workglow/browser-control/task-electron before using browser-control tasks.");
490
- }
491
- return globalServiceRegistry.get(BROWSER_CONTROL_TASK_DEPS);
492
- }
493
-
494
- // src/task/register.server.ts
495
- var SAFE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
496
- function safeName(value, label) {
497
- if (!SAFE_NAME_RE.test(value)) {
498
- throw new Error(`Invalid ${label}: must contain only alphanumeric characters, hyphens, and underscores`);
499
- }
500
- return value;
501
- }
502
- registerBrowserDeps({
503
- createContext: (_options) => new PlaywrightBackend,
504
- availableBackends: ["local", "cloud"],
505
- defaultBackend: "local",
506
- profileStorage: {
507
- async save(projectId, profileName, state) {
508
- const fs = await import("node:fs/promises");
509
- const dir = path.join(process.cwd(), ".workglow", "browser-profiles", safeName(projectId, "projectId"));
510
- await fs.mkdir(dir, { recursive: true });
511
- await fs.writeFile(path.join(dir, `${safeName(profileName, "profileName")}.json`), state, "utf-8");
512
- },
513
- async load(projectId, profileName) {
514
- const fs = await import("node:fs/promises");
515
- try {
516
- return await fs.readFile(path.join(process.cwd(), ".workglow", "browser-profiles", safeName(projectId, "projectId"), `${safeName(profileName, "profileName")}.json`), "utf-8");
517
- } catch {
518
- return null;
519
- }
520
- },
521
- async delete(projectId, profileName) {
522
- const fs = await import("node:fs/promises");
523
- try {
524
- await fs.unlink(path.join(process.cwd(), ".workglow", "browser-profiles", safeName(projectId, "projectId"), `${safeName(profileName, "profileName")}.json`));
525
- } catch {}
526
- }
527
- }
528
- });
529
-
530
- // src/task/BrowserSessionRegistry.ts
531
- import { uuid4 } from "@workglow/util";
532
- var sessions = new Map;
533
- var BrowserSessionRegistry = {
534
- register(context) {
535
- const id = uuid4();
536
- sessions.set(id, context);
537
- return id;
538
- },
539
- get(sessionId) {
540
- const context = sessions.get(sessionId);
541
- if (!context) {
542
- throw new Error(`BrowserSessionRegistry: no session found for id "${sessionId}"`);
543
- }
544
- if (!context.isConnected()) {
545
- sessions.delete(sessionId);
546
- throw new Error(`BrowserSessionRegistry: session "${sessionId}" is no longer connected`);
547
- }
548
- return context;
549
- },
550
- unregister(sessionId) {
551
- sessions.delete(sessionId);
552
- },
553
- has(sessionId) {
554
- return sessions.has(sessionId);
555
- },
556
- async disconnectAll() {
557
- const disconnects = Array.from(sessions.values()).map((ctx) => ctx.disconnect());
558
- await Promise.allSettled(disconnects);
559
- sessions.clear();
560
- },
561
- clear() {
562
- sessions.clear();
563
- }
564
- };
565
- // src/task/BunWebViewBackend.ts
566
- import { sleep } from "@workglow/util";
567
-
568
- // src/task/CDPBrowserBackend.ts
569
- var IGNORED_ROLES = new Set(["none", "generic", "ignored", "InlineTextBox"]);
570
- function parseCDPAXTree(nodes, refCounter, refMap) {
571
- const nodeMap = new Map;
572
- let rootNode;
573
- for (const node of nodes) {
574
- nodeMap.set(node.nodeId, node);
575
- if (!rootNode) {
576
- rootNode = node;
577
- }
578
- }
579
- const childIds = new Set;
580
- for (const node of nodes) {
581
- for (const childId of node.childIds ?? []) {
582
- childIds.add(childId);
583
- }
584
- }
585
- rootNode = nodes.find((n) => !childIds.has(n.nodeId)) ?? nodes[0];
586
- function buildNode(cdpNode) {
587
- const role = cdpNode.role?.value ?? "";
588
- if (cdpNode.ignored || IGNORED_ROLES.has(role)) {
589
- return null;
590
- }
591
- const ref = `e${++refCounter.count}`;
592
- refMap.set(ref, cdpNode.backendDOMNodeId ?? null);
593
- const name = typeof cdpNode.name?.value === "string" ? cdpNode.name.value : "";
594
- const node = {
595
- ref,
596
- role,
597
- name
598
- };
599
- for (const prop of cdpNode.properties ?? []) {
600
- switch (prop.name) {
601
- case "level":
602
- if (typeof prop.value.value === "number") {
603
- node.level = prop.value.value;
604
- }
605
- break;
606
- case "checked":
607
- if (prop.value.value === "mixed") {
608
- node.checked = "mixed";
609
- } else if (typeof prop.value.value === "boolean") {
610
- node.checked = prop.value.value;
611
- }
612
- break;
613
- case "disabled":
614
- node.disabled = prop.value.value === true;
615
- break;
616
- case "expanded":
617
- node.expanded = prop.value.value === true;
618
- break;
619
- case "pressed":
620
- if (prop.value.value === "mixed") {
621
- node.pressed = "mixed";
622
- } else if (typeof prop.value.value === "boolean") {
623
- node.pressed = prop.value.value;
624
- }
625
- break;
626
- case "selected":
627
- node.selected = prop.value.value === true;
628
- break;
629
- case "valuetext":
630
- case "value":
631
- if (typeof prop.value.value === "string" || typeof prop.value.value === "number") {
632
- node.value = prop.value.value;
633
- }
634
- break;
635
- }
636
- }
637
- const childNodes = [];
638
- for (const childId of cdpNode.childIds ?? []) {
639
- const childCdp = nodeMap.get(childId);
640
- if (childCdp) {
641
- const child = buildNode(childCdp);
642
- if (child)
643
- childNodes.push(child);
644
- }
645
- }
646
- if (childNodes.length > 0) {
647
- node.children = childNodes;
648
- }
649
- return node;
650
- }
651
- if (!rootNode) {
652
- const ref = `e${++refCounter.count}`;
653
- refMap.set(ref, null);
654
- return { ref, role: "document", name: "" };
655
- }
656
- const built = buildNode(rootNode);
657
- if (!built) {
658
- const ref = `e${++refCounter.count}`;
659
- refMap.set(ref, null);
660
- return { ref, role: "document", name: "" };
661
- }
662
- return built;
663
- }
664
- function serializeAXTree(node, indent = 0) {
665
- const spaces = " ".repeat(indent);
666
- let line = `${spaces}- ${node.role}`;
667
- if (node.name) {
668
- line += ` "${node.name.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
669
- }
670
- if (node.level !== undefined)
671
- line += ` [level=${node.level}]`;
672
- if (node.checked !== undefined)
673
- line += ` [checked=${node.checked}]`;
674
- if (node.disabled)
675
- line += ` [disabled=true]`;
676
- if (node.expanded !== undefined)
677
- line += ` [expanded=${node.expanded}]`;
678
- if (node.pressed !== undefined)
679
- line += ` [pressed=${node.pressed}]`;
680
- if (node.selected)
681
- line += ` [selected=true]`;
682
- if (node.value !== undefined)
683
- line += ` [value=${node.value}]`;
684
- const lines = [line];
685
- for (const child of node.children ?? []) {
686
- lines.push(serializeAXTree(child, indent + 1));
687
- }
688
- return lines.join(`
689
- `);
690
- }
691
- function buildModifiersMask(modifiers) {
692
- if (!modifiers)
693
- return 0;
694
- let mask = 0;
695
- for (const mod of modifiers) {
696
- if (mod === "Alt")
697
- mask |= 1;
698
- else if (mod === "Control")
699
- mask |= 2;
700
- else if (mod === "Meta")
701
- mask |= 4;
702
- else if (mod === "Shift")
703
- mask |= 8;
704
- }
705
- return mask;
706
- }
707
- var KEY_CODE_MAP = {
708
- Enter: "Enter",
709
- Tab: "Tab",
710
- Escape: "Escape",
711
- Backspace: "Backspace",
712
- Delete: "Delete",
713
- ArrowUp: "ArrowUp",
714
- ArrowDown: "ArrowDown",
715
- ArrowLeft: "ArrowLeft",
716
- ArrowRight: "ArrowRight",
717
- Home: "Home",
718
- End: "End",
719
- PageUp: "PageUp",
720
- PageDown: "PageDown",
721
- Space: " ",
722
- F1: "F1",
723
- F2: "F2",
724
- F3: "F3",
725
- F4: "F4",
726
- F5: "F5",
727
- F6: "F6",
728
- F7: "F7",
729
- F8: "F8",
730
- F9: "F9",
731
- F10: "F10",
732
- F11: "F11",
733
- F12: "F12"
734
- };
735
- function keyToCode(key) {
736
- if (key.length === 1) {
737
- const upper = key.toUpperCase();
738
- return `Key${upper}`;
739
- }
740
- const codeMap = {
741
- Enter: "Enter",
742
- Tab: "Tab",
743
- Escape: "Escape",
744
- Backspace: "Backspace",
745
- Delete: "Delete",
746
- ArrowUp: "ArrowUp",
747
- ArrowDown: "ArrowDown",
748
- ArrowLeft: "ArrowLeft",
749
- ArrowRight: "ArrowRight",
750
- Home: "Home",
751
- End: "End",
752
- PageUp: "PageUp",
753
- PageDown: "PageDown",
754
- Space: "Space"
755
- };
756
- return codeMap[key] ?? key;
757
- }
758
-
759
- class CDPBrowserBackend {
760
- _refMap = new Map;
761
- _refCounter = { count: 0 };
762
- resolveRefToNodeId(ref) {
763
- if (!this._refMap.has(ref)) {
764
- throw new Error(`${this.backendName}: unknown ref "${ref}"`);
765
- }
766
- const nodeId = this._refMap.get(ref);
767
- if (nodeId == null) {
768
- throw new Error(`${this.backendName}: ref "${ref}" has no associated DOM node`);
769
- }
770
- return nodeId;
771
- }
772
- async getBoundingBox(backendNodeId) {
773
- const result = await this.cdp("DOM.getBoxModel", { backendNodeId });
774
- const content = result.model.content;
775
- const x = content[0];
776
- const y = content[1];
777
- const width = content[2] - content[0];
778
- const height = content[5] - content[1];
779
- return { x, y, width, height };
780
- }
781
- async getDocumentRootNodeId() {
782
- const doc = await this.cdp("DOM.getDocument", { depth: 0 });
783
- return doc.root.nodeId;
784
- }
785
- async snapshot(_options = {}) {
786
- this._refMap.clear();
787
- const result = await this.cdp("Accessibility.getFullAXTree");
788
- const nodes = result.nodes ?? [];
789
- const root = parseCDPAXTree(nodes, this._refCounter, this._refMap);
790
- const yaml = serializeAXTree(root);
791
- return { root, yaml };
792
- }
793
- async click(ref, options = {}) {
794
- const backendNodeId = this.resolveRefToNodeId(ref);
795
- const { x, y, width, height } = await this.getBoundingBox(backendNodeId);
796
- const cx = x + width / 2;
797
- const cy = y + height / 2;
798
- const button = options.button ?? "left";
799
- const clickCount = options.clickCount ?? 1;
800
- const modifiers = buildModifiersMask(options.modifiers);
801
- for (let i = 0;i < clickCount; i++) {
802
- await this.cdp("Input.dispatchMouseEvent", {
803
- type: "mousePressed",
804
- x: cx,
805
- y: cy,
806
- button,
807
- clickCount: 1,
808
- modifiers
809
- });
810
- await this.cdp("Input.dispatchMouseEvent", {
811
- type: "mouseReleased",
812
- x: cx,
813
- y: cy,
814
- button,
815
- clickCount: 1,
816
- modifiers
817
- });
818
- }
819
- }
820
- async fill(ref, value, _options = {}) {
821
- const backendNodeId = this.resolveRefToNodeId(ref);
822
- await this.cdp("DOM.focus", { backendNodeId });
823
- await this.cdp("Input.dispatchKeyEvent", {
824
- type: "keyDown",
825
- key: "a",
826
- code: "KeyA",
827
- modifiers: 2
828
- });
829
- await this.cdp("Input.dispatchKeyEvent", {
830
- type: "keyUp",
831
- key: "a",
832
- code: "KeyA",
833
- modifiers: 2
834
- });
835
- await this.cdp("Input.insertText", { text: value });
836
- }
837
- async selectOption(ref, values, _options = {}) {
838
- const backendNodeId = this.resolveRefToNodeId(ref);
839
- const valuesArray = Array.isArray(values) ? values : [values];
840
- const result = await this.cdp("DOM.resolveNode", { backendNodeId });
841
- const objectId = result.object.objectId;
842
- await this.cdp("Runtime.callFunctionOn", {
843
- objectId,
844
- functionDeclaration: `function(vals) {
845
- const opts = Array.from(this.options);
846
- for (const opt of opts) {
847
- opt.selected = vals.includes(opt.value);
848
- }
849
- this.dispatchEvent(new Event('change', { bubbles: true }));
850
- }`,
851
- arguments: [{ value: valuesArray }]
852
- });
853
- }
854
- async hover(ref, _options = {}) {
855
- const backendNodeId = this.resolveRefToNodeId(ref);
856
- const { x, y, width, height } = await this.getBoundingBox(backendNodeId);
857
- const cx = x + width / 2;
858
- const cy = y + height / 2;
859
- await this.cdp("Input.dispatchMouseEvent", {
860
- type: "mouseMoved",
861
- x: cx,
862
- y: cy
863
- });
864
- }
865
- async clickByRole(role, name, options = {}) {
866
- const result = await this.cdp("Accessibility.queryAXTree", {
867
- role,
868
- name
869
- });
870
- const axNode = result.nodes?.[0];
871
- if (!axNode) {
872
- throw new Error(`${this.backendName}: no element with role "${role}" and name "${name}"`);
873
- }
874
- if (axNode.backendDOMNodeId == null) {
875
- throw new Error(`${this.backendName}: element with role "${role}" and name "${name}" has no DOM node`);
876
- }
877
- const { x, y, width, height } = await this.getBoundingBox(axNode.backendDOMNodeId);
878
- const cx = x + width / 2;
879
- const cy = y + height / 2;
880
- const button = options.button ?? "left";
881
- const clickCount = options.clickCount ?? 1;
882
- const modifiers = buildModifiersMask(options.modifiers);
883
- for (let i = 0;i < clickCount; i++) {
884
- await this.cdp("Input.dispatchMouseEvent", {
885
- type: "mousePressed",
886
- x: cx,
887
- y: cy,
888
- button,
889
- clickCount: 1,
890
- modifiers
891
- });
892
- await this.cdp("Input.dispatchMouseEvent", {
893
- type: "mouseReleased",
894
- x: cx,
895
- y: cy,
896
- button,
897
- clickCount: 1,
898
- modifiers
899
- });
900
- }
901
- }
902
- async fillByLabel(label, value, _options = {}) {
903
- const labelResult = await this.cdp("Accessibility.queryAXTree", {
904
- role: "label",
905
- name: label
906
- });
907
- if (labelResult.nodes?.[0]?.backendDOMNodeId != null) {
908
- const labelNodeId = labelResult.nodes[0].backendDOMNodeId;
909
- const resolveResult = await this.cdp("DOM.resolveNode", {
910
- backendNodeId: labelNodeId
911
- });
912
- const inputResult = await this.cdp("Runtime.callFunctionOn", {
913
- objectId: resolveResult.object.objectId,
914
- functionDeclaration: `function() {
915
- const forAttr = this.htmlFor || this.getAttribute('for');
916
- if (forAttr) {
917
- return document.getElementById(forAttr);
918
- }
919
- return this.querySelector('input, textarea, select');
920
- }`,
921
- returnByValue: false
922
- });
923
- if (inputResult.result.objectId && inputResult.result.subtype !== "null") {
924
- const inputNodeResult = await this.cdp("DOM.requestNode", {
925
- objectId: inputResult.result.objectId
926
- });
927
- const describeResult = await this.cdp("DOM.describeNode", {
928
- nodeId: inputNodeResult.nodeId
929
- });
930
- await this.cdp("DOM.focus", { backendNodeId: describeResult.node.backendNodeId });
931
- await this.cdp("Input.dispatchKeyEvent", {
932
- type: "keyDown",
933
- key: "a",
934
- code: "KeyA",
935
- modifiers: 2
936
- });
937
- await this.cdp("Input.dispatchKeyEvent", {
938
- type: "keyUp",
939
- key: "a",
940
- code: "KeyA",
941
- modifiers: 2
942
- });
943
- await this.cdp("Input.insertText", { text: value });
944
- return;
945
- }
946
- }
947
- const script = `(function() {
948
- const labels = Array.from(document.querySelectorAll('label'));
949
- const label = labels.find(l => l.textContent.trim() === ${JSON.stringify(label)});
950
- if (!label) return false;
951
- const forAttr = label.htmlFor;
952
- let input = forAttr ? document.getElementById(forAttr) : label.querySelector('input, textarea, select');
953
- if (!input) return false;
954
- input.focus();
955
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
956
- Object.getPrototypeOf(input), 'value'
957
- )?.set;
958
- if (nativeInputValueSetter) {
959
- nativeInputValueSetter.call(input, ${JSON.stringify(value)});
960
- } else {
961
- input.value = ${JSON.stringify(value)};
962
- }
963
- input.dispatchEvent(new Event('input', { bubbles: true }));
964
- input.dispatchEvent(new Event('change', { bubbles: true }));
965
- return true;
966
- })()`;
967
- const filled = await this.evaluateInPage(script);
968
- if (!filled) {
969
- throw new Error(`${this.backendName}: no input found for label "${label}"`);
970
- }
971
- }
972
- async innerHTML(ref) {
973
- const backendNodeId = this.resolveRefToNodeId(ref);
974
- const result = await this.cdp("DOM.getOuterHTML", { backendNodeId });
975
- const outer = result.outerHTML;
976
- const startTagEnd = outer.indexOf(">");
977
- const endTagStart = outer.lastIndexOf("<");
978
- if (startTagEnd === -1 || endTagStart <= startTagEnd) {
979
- return outer;
980
- }
981
- return outer.slice(startTagEnd + 1, endTagStart);
982
- }
983
- async textContent(ref) {
984
- const backendNodeId = this.resolveRefToNodeId(ref);
985
- const resolveResult = await this.cdp("DOM.resolveNode", { backendNodeId });
986
- const objectId = resolveResult.object.objectId;
987
- const result = await this.cdp("Runtime.callFunctionOn", {
988
- objectId,
989
- functionDeclaration: "function() { return this.textContent; }",
990
- returnByValue: true
991
- });
992
- return result.result.value;
993
- }
994
- async attribute(ref, name) {
995
- const backendNodeId = this.resolveRefToNodeId(ref);
996
- const resolveResult = await this.cdp("DOM.resolveNode", { backendNodeId });
997
- const objectId = resolveResult.object.objectId;
998
- const result = await this.cdp("Runtime.callFunctionOn", {
999
- objectId,
1000
- functionDeclaration: "function(attrName) { return this.getAttribute(attrName); }",
1001
- arguments: [{ value: name }],
1002
- returnByValue: true
1003
- });
1004
- return result.result.value;
1005
- }
1006
- async querySelector(selector) {
1007
- const rootNodeId = await this.getDocumentRootNodeId();
1008
- const result = await this.cdp("DOM.querySelector", {
1009
- nodeId: rootNodeId,
1010
- selector
1011
- });
1012
- if (!result.nodeId || result.nodeId === 0)
1013
- return null;
1014
- const describeResult = await this.cdp("DOM.describeNode", { nodeId: result.nodeId });
1015
- const ref = `e${++this._refCounter.count}`;
1016
- this._refMap.set(ref, describeResult.node.backendNodeId);
1017
- return ref;
1018
- }
1019
- async querySelectorAll(selector) {
1020
- const rootNodeId = await this.getDocumentRootNodeId();
1021
- const result = await this.cdp("DOM.querySelectorAll", {
1022
- nodeId: rootNodeId,
1023
- selector
1024
- });
1025
- const nodeIds = result.nodeIds ?? [];
1026
- const refs = [];
1027
- for (const nodeId of nodeIds) {
1028
- const describeResult = await this.cdp("DOM.describeNode", { nodeId });
1029
- const ref = `e${++this._refCounter.count}`;
1030
- this._refMap.set(ref, describeResult.node.backendNodeId);
1031
- refs.push(ref);
1032
- }
1033
- return refs;
1034
- }
1035
- async pressKey(key, _options = {}) {
1036
- const keyCode = KEY_CODE_MAP[key] ?? key;
1037
- await this.cdp("Input.dispatchKeyEvent", {
1038
- type: "keyDown",
1039
- key: keyCode,
1040
- code: keyToCode(key)
1041
- });
1042
- await this.cdp("Input.dispatchKeyEvent", {
1043
- type: "keyUp",
1044
- key: keyCode,
1045
- code: keyToCode(key)
1046
- });
1047
- }
1048
- async type(text, _options = {}) {
1049
- await this.cdp("Input.insertText", { text });
1050
- }
1051
- async scroll(x, y, ref) {
1052
- if (ref) {
1053
- const backendNodeId = this.resolveRefToNodeId(ref);
1054
- const resolveResult = await this.cdp("DOM.resolveNode", { backendNodeId });
1055
- const objectId = resolveResult.object.objectId;
1056
- await this.cdp("Runtime.callFunctionOn", {
1057
- objectId,
1058
- functionDeclaration: `function(dx, dy) { this.scrollBy(dx, dy); }`,
1059
- arguments: [{ value: x }, { value: y }]
1060
- });
1061
- } else {
1062
- await this.cdp("Input.dispatchMouseEvent", {
1063
- type: "mouseWheel",
1064
- x: 0,
1065
- y: 0,
1066
- deltaX: x,
1067
- deltaY: y
1068
- });
1069
- }
1070
- }
1071
- async uploadFile(ref, paths) {
1072
- const backendNodeId = this.resolveRefToNodeId(ref);
1073
- const files = Array.isArray(paths) ? paths : [paths];
1074
- await this.cdp("DOM.setFileInputFiles", {
1075
- backendNodeId,
1076
- files
1077
- });
1078
- }
1079
- }
1080
-
1081
- // src/task/BunWebViewBackend.ts
1082
- class BunWebViewBackend extends CDPBrowserBackend {
1083
- defaultChromePath;
1084
- _wv = null;
1085
- _connected = false;
1086
- _dialogHandler = null;
1087
- backendName = "BunWebViewBackend";
1088
- constructor(defaultChromePath) {
1089
- super();
1090
- this.defaultChromePath = defaultChromePath;
1091
- }
1092
- async cdp(method, params = {}) {
1093
- return this.wv.cdp(method, params);
1094
- }
1095
- async evaluateInPage(script) {
1096
- return this.wv.evaluate(script);
1097
- }
1098
- async connect(options = {}) {
1099
- const BunWebView = globalThis.Bun?.WebView;
1100
- if (!BunWebView) {
1101
- throw new Error("BunWebViewBackend: Bun.WebView is not available — " + "this backend requires Bun with WebView support");
1102
- }
1103
- const { headless = true, chromePath = this.defaultChromePath } = options;
1104
- const webViewOptions = {
1105
- headless,
1106
- url: "about:blank",
1107
- backend: chromePath ? { type: "chrome", path: chromePath } : { type: "chrome" }
1108
- };
1109
- this._wv = new BunWebView(webViewOptions);
1110
- await new Promise((resolve, reject) => {
1111
- const timeout = setTimeout(() => {
1112
- reject(new Error("BunWebViewBackend: initial navigation timed out"));
1113
- }, 30000);
1114
- this._wv.onNavigated = () => {
1115
- clearTimeout(timeout);
1116
- this._wv.onNavigated = null;
1117
- this._wv.onNavigationFailed = null;
1118
- resolve();
1119
- };
1120
- this._wv.onNavigationFailed = (error) => {
1121
- clearTimeout(timeout);
1122
- this._wv.onNavigated = null;
1123
- this._wv.onNavigationFailed = null;
1124
- reject(new Error(`BunWebViewBackend: initial navigation failed — ${error}`));
1125
- };
1126
- });
1127
- await this.cdp("Accessibility.enable");
1128
- this._connected = true;
1129
- }
1130
- async disconnect() {
1131
- this._connected = false;
1132
- try {
1133
- if (this._wv) {
1134
- this._wv.close();
1135
- }
1136
- } finally {
1137
- this._wv = null;
1138
- this._refMap.clear();
1139
- this._refCounter.count = 0;
1140
- }
1141
- }
1142
- isConnected() {
1143
- return this._connected && this._wv !== null;
1144
- }
1145
- get wv() {
1146
- if (!this._wv || !this._connected) {
1147
- throw new Error("BunWebViewBackend: not connected — call connect() first");
1148
- }
1149
- return this._wv;
1150
- }
1151
- async navigate(url, options = {}) {
1152
- const timeout = options.timeout ?? 30000;
1153
- await new Promise((resolve, reject) => {
1154
- const timer = setTimeout(() => {
1155
- reject(new Error("BunWebViewBackend: navigate timed out"));
1156
- }, timeout);
1157
- this.wv.onNavigated = () => {
1158
- clearTimeout(timer);
1159
- this.wv.onNavigated = null;
1160
- resolve();
1161
- };
1162
- this.wv.onNavigationFailed = (error) => {
1163
- clearTimeout(timer);
1164
- this.wv.onNavigationFailed = null;
1165
- this.wv.onNavigated = null;
1166
- reject(new Error(`BunWebViewBackend: navigation failed — ${error}`));
1167
- };
1168
- this.wv.navigate(url);
1169
- });
1170
- }
1171
- async goBack(_options = {}) {
1172
- this.wv.back();
1173
- await this.waitForNavigation();
1174
- }
1175
- async goForward(_options = {}) {
1176
- this.wv.forward();
1177
- await this.waitForNavigation();
1178
- }
1179
- async reload(_options = {}) {
1180
- this.wv.reload();
1181
- await this.waitForNavigation();
1182
- }
1183
- async currentUrl() {
1184
- return this.wv.url;
1185
- }
1186
- async title() {
1187
- return this.wv.title;
1188
- }
1189
- async content() {
1190
- return this.wv.evaluate("document.documentElement.outerHTML");
1191
- }
1192
- async evaluate(expression) {
1193
- return this.wv.evaluate(expression);
1194
- }
1195
- async screenshot(options = {}) {
1196
- const { format = "png", quality } = options;
1197
- return this.wv.screenshot({
1198
- encoding: "buffer",
1199
- format,
1200
- ...quality !== undefined && { quality }
1201
- });
1202
- }
1203
- async pressKey(key, _options = {}) {
1204
- await this.wv.press(key);
1205
- }
1206
- async type(text, _options = {}) {
1207
- await this.wv.type(text);
1208
- }
1209
- async download(_trigger, _options = {}) {
1210
- throw new Error("BunWebViewBackend: download is not supported");
1211
- }
1212
- onDialog(handler) {
1213
- this._dialogHandler = handler;
1214
- this.cdp("Page.enable").then(() => {
1215
- this.wv.addEventListener("Page.javascriptDialogOpening", async (params) => {
1216
- const info = {
1217
- type: params.type,
1218
- message: params.message,
1219
- defaultValue: params.defaultPrompt || undefined
1220
- };
1221
- if (this._dialogHandler) {
1222
- const action = await this._dialogHandler(info);
1223
- const accept = action.accept;
1224
- const promptText = accept && "promptText" in action ? action.promptText : undefined;
1225
- await this.cdp("Page.handleJavaScriptDialog", {
1226
- accept,
1227
- ...promptText !== undefined && { promptText }
1228
- });
1229
- } else {
1230
- await this.cdp("Page.handleJavaScriptDialog", { accept: false });
1231
- }
1232
- });
1233
- }).catch(() => {});
1234
- }
1235
- async tabs() {
1236
- const url = this.wv.url;
1237
- const title = this.wv.title;
1238
- return [{ tabId: "0", url, title }];
1239
- }
1240
- async switchTab(_tabId) {}
1241
- async newTab(url) {
1242
- if (url) {
1243
- await this.navigate(url);
1244
- }
1245
- return {
1246
- tabId: "0",
1247
- url: this.wv.url,
1248
- title: this.wv.title
1249
- };
1250
- }
1251
- async closeTab(_tabId) {
1252
- await this.disconnect();
1253
- }
1254
- async waitForNavigation(options = {}) {
1255
- const timeout = options.timeout ?? 30000;
1256
- return new Promise((resolve, reject) => {
1257
- const timer = setTimeout(() => {
1258
- this.wv.onNavigated = null;
1259
- this.wv.onNavigationFailed = null;
1260
- reject(new Error("BunWebViewBackend: waitForNavigation timed out"));
1261
- }, timeout);
1262
- this.wv.onNavigated = () => {
1263
- clearTimeout(timer);
1264
- this.wv.onNavigated = null;
1265
- this.wv.onNavigationFailed = null;
1266
- resolve();
1267
- };
1268
- this.wv.onNavigationFailed = (error) => {
1269
- clearTimeout(timer);
1270
- this.wv.onNavigated = null;
1271
- this.wv.onNavigationFailed = null;
1272
- reject(new Error(`BunWebViewBackend: navigation failed — ${error}`));
1273
- };
1274
- });
1275
- }
1276
- async waitForSelector(selector, options = {}) {
1277
- const timeout = options.timeout ?? 30000;
1278
- const interval = 100;
1279
- const deadline = Date.now() + timeout;
1280
- while (Date.now() < deadline) {
1281
- const found = await this.wv.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
1282
- if (found) {
1283
- const ref = await this.querySelector(selector);
1284
- if (ref)
1285
- return ref;
1286
- }
1287
- await sleep(interval);
1288
- }
1289
- throw new Error(`BunWebViewBackend: waitForSelector timed out for "${selector}"`);
1290
- }
1291
- async waitForIdle(options = {}) {
1292
- const timeout = options.timeout ?? 30000;
1293
- const interval = 100;
1294
- const deadline = Date.now() + timeout;
1295
- while (Date.now() < deadline) {
1296
- const ready = await this.wv.evaluate(`document.readyState === "complete"`);
1297
- if (ready)
1298
- return;
1299
- await sleep(interval);
1300
- }
1301
- throw new Error("BunWebViewBackend: waitForIdle timed out");
1302
- }
1303
- networkRequests = (_filter) => {
1304
- return Promise.resolve([]);
1305
- };
1306
- consoleMessages = () => {
1307
- return Promise.resolve([]);
1308
- };
1309
- }
1310
- // src/task/tasks/BrowserSessionTask.ts
1311
- import {
1312
- Entitlements,
1313
- mergeEntitlements,
1314
- Task,
1315
- TaskConfigSchema
1316
- } from "@workglow/task-graph";
1317
- var browserSessionTaskConfigSchema = {
1318
- type: "object",
1319
- properties: {
1320
- ...TaskConfigSchema["properties"],
1321
- backend: {
1322
- type: "string",
1323
- enum: ["local", "cloud", "electron-native"],
1324
- title: "Backend",
1325
- description: "The browser backend to use"
1326
- },
1327
- projectId: {
1328
- type: "string",
1329
- title: "Project ID",
1330
- description: "Project identifier for profile storage"
1331
- },
1332
- profileName: {
1333
- type: "string",
1334
- title: "Profile Name",
1335
- description: "Named browser profile to use"
1336
- },
1337
- headless: {
1338
- type: "boolean",
1339
- title: "Headless",
1340
- description: "Run the browser in headless mode",
1341
- default: true
1342
- }
1343
- },
1344
- additionalProperties: false
1345
- };
1346
- var inputSchema = {
1347
- type: "object",
1348
- properties: {},
1349
- additionalProperties: false
1350
- };
1351
- var outputSchema = {
1352
- type: "object",
1353
- properties: {
1354
- sessionId: {
1355
- type: "string",
1356
- title: "Session ID",
1357
- description: "The unique identifier for the created browser session"
1358
- }
1359
- },
1360
- required: ["sessionId"],
1361
- additionalProperties: false
1362
- };
1363
-
1364
- class BrowserSessionTask extends Task {
1365
- static type = "BrowserSessionTask";
1366
- static category = "Browser";
1367
- static title = "Browser Session";
1368
- static description = "Creates a new browser session and returns its session ID";
1369
- static cacheable = false;
1370
- static hasDynamicEntitlements = true;
1371
- static configSchema() {
1372
- return browserSessionTaskConfigSchema;
1373
- }
1374
- static inputSchema() {
1375
- return inputSchema;
1376
- }
1377
- static outputSchema() {
1378
- return outputSchema;
1379
- }
1380
- static entitlements() {
1381
- return {
1382
- entitlements: [
1383
- { id: Entitlements.BROWSER_CONTROL, reason: "Creates and manages a browser session" }
1384
- ]
1385
- };
1386
- }
1387
- entitlements() {
1388
- const base = BrowserSessionTask.entitlements();
1389
- const backend = this.config.backend;
1390
- if (backend === "local" || backend === "electron-native") {
1391
- return mergeEntitlements(base, {
1392
- entitlements: [
1393
- { id: Entitlements.BROWSER_CONTROL_LOCAL, reason: "Launches a local browser process" }
1394
- ]
1395
- });
1396
- }
1397
- if (backend === "cloud") {
1398
- return mergeEntitlements(base, {
1399
- entitlements: [
1400
- {
1401
- id: Entitlements.BROWSER_CONTROL_CLOUD,
1402
- reason: "Connects to a remote cloud browser service"
1403
- }
1404
- ]
1405
- });
1406
- }
1407
- return mergeEntitlements(base, mergeEntitlements({
1408
- entitlements: [
1409
- { id: Entitlements.BROWSER_CONTROL_LOCAL, reason: "Launches a local browser process" }
1410
- ]
1411
- }, {
1412
- entitlements: [
1413
- {
1414
- id: Entitlements.BROWSER_CONTROL_CLOUD,
1415
- reason: "Connects to a remote cloud browser service"
1416
- }
1417
- ]
1418
- }));
1419
- }
1420
- async execute(_input, executeContext) {
1421
- const deps = getBrowserDeps();
1422
- const backend = this.config.backend ?? deps.defaultBackend;
1423
- if (!deps.availableBackends.includes(backend)) {
1424
- throw new Error(`BrowserSessionTask: backend "${backend}" is not available. Available backends: ${deps.availableBackends.join(", ")}`);
1425
- }
1426
- const options = {
1427
- backend,
1428
- projectId: this.config.projectId,
1429
- profileName: this.config.profileName,
1430
- headless: this.config.headless ?? true
1431
- };
1432
- const ctx = deps.createContext(options);
1433
- await ctx.connect(options);
1434
- const sessionId = BrowserSessionRegistry.register(ctx);
1435
- executeContext.resourceScope?.register(`browser:${sessionId}`, async () => {
1436
- await ctx.disconnect();
1437
- BrowserSessionRegistry.unregister(sessionId);
1438
- });
1439
- return { sessionId };
1440
- }
1441
- }
1442
- // src/task/tasks/BrowserCloseTask.ts
1443
- import { Task as Task2, TaskConfigSchema as TaskConfigSchema2 } from "@workglow/task-graph";
1444
- var inputSchema2 = {
1445
- type: "object",
1446
- properties: {
1447
- sessionId: {
1448
- type: "string",
1449
- title: "Session ID",
1450
- description: "The session ID of the browser session to close"
1451
- }
1452
- },
1453
- required: ["sessionId"],
1454
- additionalProperties: false
1455
- };
1456
- var outputSchema2 = {
1457
- type: "object",
1458
- properties: {},
1459
- additionalProperties: false
1460
- };
1461
-
1462
- class BrowserCloseTask extends Task2 {
1463
- static type = "BrowserCloseTask";
1464
- static category = "Browser";
1465
- static title = "Browser Close";
1466
- static description = "Disconnects and closes an existing browser session";
1467
- static cacheable = false;
1468
- static configSchema() {
1469
- return TaskConfigSchema2;
1470
- }
1471
- static inputSchema() {
1472
- return inputSchema2;
1473
- }
1474
- static outputSchema() {
1475
- return outputSchema2;
1476
- }
1477
- async execute(input, _executeContext) {
1478
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1479
- await ctx.disconnect();
1480
- BrowserSessionRegistry.unregister(input.sessionId);
1481
- return {};
1482
- }
1483
- }
1484
- // src/task/tasks/BrowserNavigateTask.ts
1485
- import {
1486
- Entitlements as Entitlements2,
1487
- Task as Task3,
1488
- TaskConfigSchema as TaskConfigSchema3
1489
- } from "@workglow/task-graph";
1490
- var browserNavigateTaskConfigSchema = {
1491
- type: "object",
1492
- properties: {
1493
- ...TaskConfigSchema3["properties"],
1494
- waitUntil: {
1495
- type: "string",
1496
- enum: ["load", "domcontentloaded", "networkidle"],
1497
- title: "Wait Until",
1498
- description: "When to consider navigation complete",
1499
- default: "load"
1500
- }
1501
- },
1502
- additionalProperties: false
1503
- };
1504
- var inputSchema3 = {
1505
- type: "object",
1506
- properties: {
1507
- sessionId: {
1508
- type: "string",
1509
- title: "Session ID",
1510
- description: "The browser session to use"
1511
- },
1512
- url: {
1513
- type: "string",
1514
- format: "uri",
1515
- title: "URL",
1516
- description: "The URL to navigate to"
1517
- }
1518
- },
1519
- required: ["sessionId", "url"],
1520
- additionalProperties: false
1521
- };
1522
- var outputSchema3 = {
1523
- type: "object",
1524
- properties: {
1525
- sessionId: {
1526
- type: "string",
1527
- title: "Session ID",
1528
- description: "The browser session ID"
1529
- },
1530
- title: {
1531
- type: "string",
1532
- title: "Page Title",
1533
- description: "The title of the navigated page"
1534
- },
1535
- url: {
1536
- type: "string",
1537
- title: "URL",
1538
- description: "The current URL after navigation"
1539
- }
1540
- },
1541
- required: ["sessionId", "title", "url"],
1542
- additionalProperties: false
1543
- };
1544
-
1545
- class BrowserNavigateTask extends Task3 {
1546
- static type = "BrowserNavigateTask";
1547
- static category = "Browser";
1548
- static title = "Browser Navigate";
1549
- static description = "Navigates the browser to a URL and returns the page title and URL";
1550
- static cacheable = false;
1551
- static configSchema() {
1552
- return browserNavigateTaskConfigSchema;
1553
- }
1554
- static inputSchema() {
1555
- return inputSchema3;
1556
- }
1557
- static outputSchema() {
1558
- return outputSchema3;
1559
- }
1560
- static entitlements() {
1561
- return {
1562
- entitlements: [
1563
- { id: Entitlements2.BROWSER_CONTROL_NAVIGATE, reason: "Navigates to a URL in the browser" }
1564
- ]
1565
- };
1566
- }
1567
- async execute(input, _executeContext) {
1568
- const parsed = new URL(input.url, "https://placeholder");
1569
- if (parsed.protocol === "javascript:") {
1570
- throw new Error("BrowserNavigateTask: javascript: URLs are not allowed");
1571
- }
1572
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1573
- const waitUntil = this.config.waitUntil ?? "load";
1574
- await ctx.navigate(input.url, { waitUntil });
1575
- const title = await ctx.title();
1576
- const url = await ctx.currentUrl();
1577
- return { sessionId: input.sessionId, title, url };
1578
- }
1579
- }
1580
- // src/task/tasks/BrowserBackTask.ts
1581
- import { Task as Task4, TaskConfigSchema as TaskConfigSchema4 } from "@workglow/task-graph";
1582
- var inputSchema4 = {
1583
- type: "object",
1584
- properties: {
1585
- sessionId: {
1586
- type: "string",
1587
- title: "Session ID",
1588
- description: "The browser session to use"
1589
- }
1590
- },
1591
- required: ["sessionId"],
1592
- additionalProperties: false
1593
- };
1594
- var outputSchema4 = {
1595
- type: "object",
1596
- properties: {
1597
- sessionId: {
1598
- type: "string",
1599
- title: "Session ID",
1600
- description: "The browser session ID"
1601
- },
1602
- url: {
1603
- type: "string",
1604
- title: "URL",
1605
- description: "The current URL after navigating back"
1606
- }
1607
- },
1608
- required: ["sessionId", "url"],
1609
- additionalProperties: false
1610
- };
1611
-
1612
- class BrowserBackTask extends Task4 {
1613
- static type = "BrowserBackTask";
1614
- static category = "Browser";
1615
- static title = "Browser Back";
1616
- static description = "Navigates back in the browser history and returns the current URL";
1617
- static cacheable = false;
1618
- static configSchema() {
1619
- return TaskConfigSchema4;
1620
- }
1621
- static inputSchema() {
1622
- return inputSchema4;
1623
- }
1624
- static outputSchema() {
1625
- return outputSchema4;
1626
- }
1627
- async execute(input, _executeContext) {
1628
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1629
- await ctx.goBack();
1630
- const url = await ctx.currentUrl();
1631
- return { sessionId: input.sessionId, url };
1632
- }
1633
- }
1634
- // src/task/tasks/BrowserForwardTask.ts
1635
- import { Task as Task5, TaskConfigSchema as TaskConfigSchema5 } from "@workglow/task-graph";
1636
- var inputSchema5 = {
1637
- type: "object",
1638
- properties: {
1639
- sessionId: {
1640
- type: "string",
1641
- title: "Session ID",
1642
- description: "The browser session to use"
1643
- }
1644
- },
1645
- required: ["sessionId"],
1646
- additionalProperties: false
1647
- };
1648
- var outputSchema5 = {
1649
- type: "object",
1650
- properties: {
1651
- sessionId: {
1652
- type: "string",
1653
- title: "Session ID",
1654
- description: "The browser session ID"
1655
- },
1656
- url: {
1657
- type: "string",
1658
- title: "URL",
1659
- description: "The current URL after navigating forward"
1660
- }
1661
- },
1662
- required: ["sessionId", "url"],
1663
- additionalProperties: false
1664
- };
1665
-
1666
- class BrowserForwardTask extends Task5 {
1667
- static type = "BrowserForwardTask";
1668
- static category = "Browser";
1669
- static title = "Browser Forward";
1670
- static description = "Navigates forward in the browser history and returns the current URL";
1671
- static cacheable = false;
1672
- static configSchema() {
1673
- return TaskConfigSchema5;
1674
- }
1675
- static inputSchema() {
1676
- return inputSchema5;
1677
- }
1678
- static outputSchema() {
1679
- return outputSchema5;
1680
- }
1681
- async execute(input, _executeContext) {
1682
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1683
- await ctx.goForward();
1684
- const url = await ctx.currentUrl();
1685
- return { sessionId: input.sessionId, url };
1686
- }
1687
- }
1688
- // src/task/tasks/BrowserReloadTask.ts
1689
- import { Task as Task6, TaskConfigSchema as TaskConfigSchema6 } from "@workglow/task-graph";
1690
- var inputSchema6 = {
1691
- type: "object",
1692
- properties: {
1693
- sessionId: {
1694
- type: "string",
1695
- title: "Session ID",
1696
- description: "The browser session to use"
1697
- }
1698
- },
1699
- required: ["sessionId"],
1700
- additionalProperties: false
1701
- };
1702
- var outputSchema6 = {
1703
- type: "object",
1704
- properties: {
1705
- sessionId: {
1706
- type: "string",
1707
- title: "Session ID",
1708
- description: "The browser session ID"
1709
- }
1710
- },
1711
- required: ["sessionId"],
1712
- additionalProperties: false
1713
- };
1714
-
1715
- class BrowserReloadTask extends Task6 {
1716
- static type = "BrowserReloadTask";
1717
- static category = "Browser";
1718
- static title = "Browser Reload";
1719
- static description = "Reloads the current page in the browser";
1720
- static cacheable = false;
1721
- static configSchema() {
1722
- return TaskConfigSchema6;
1723
- }
1724
- static inputSchema() {
1725
- return inputSchema6;
1726
- }
1727
- static outputSchema() {
1728
- return outputSchema6;
1729
- }
1730
- async execute(input, _executeContext) {
1731
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1732
- await ctx.reload();
1733
- return { sessionId: input.sessionId };
1734
- }
1735
- }
1736
- // src/task/tasks/BrowserSnapshotTask.ts
1737
- import { Task as Task7, TaskConfigSchema as TaskConfigSchema7 } from "@workglow/task-graph";
1738
- var inputSchema7 = {
1739
- type: "object",
1740
- properties: {
1741
- sessionId: {
1742
- type: "string",
1743
- title: "Session ID",
1744
- description: "The browser session to use"
1745
- }
1746
- },
1747
- required: ["sessionId"],
1748
- additionalProperties: false
1749
- };
1750
- var outputSchema7 = {
1751
- type: "object",
1752
- properties: {
1753
- sessionId: {
1754
- type: "string",
1755
- title: "Session ID",
1756
- description: "The browser session ID"
1757
- },
1758
- tree: {
1759
- type: "object",
1760
- title: "Accessibility Tree",
1761
- description: "The accessibility tree of the current page",
1762
- additionalProperties: true
1763
- }
1764
- },
1765
- required: ["sessionId", "tree"],
1766
- additionalProperties: false
1767
- };
1768
-
1769
- class BrowserSnapshotTask extends Task7 {
1770
- static type = "BrowserSnapshotTask";
1771
- static category = "Browser";
1772
- static title = "Browser Snapshot";
1773
- static description = "Returns the accessibility tree of the current browser page";
1774
- static cacheable = false;
1775
- static configSchema() {
1776
- return TaskConfigSchema7;
1777
- }
1778
- static inputSchema() {
1779
- return inputSchema7;
1780
- }
1781
- static outputSchema() {
1782
- return outputSchema7;
1783
- }
1784
- async execute(input, _executeContext) {
1785
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1786
- const tree = await ctx.snapshot();
1787
- return { sessionId: input.sessionId, tree };
1788
- }
1789
- }
1790
- // src/task/tasks/BrowserScreenshotTask.ts
1791
- import { Task as Task8, TaskConfigSchema as TaskConfigSchema8 } from "@workglow/task-graph";
1792
- var browserScreenshotTaskConfigSchema = {
1793
- type: "object",
1794
- properties: {
1795
- ...TaskConfigSchema8["properties"],
1796
- format: {
1797
- type: "string",
1798
- enum: ["png", "jpeg"],
1799
- title: "Format",
1800
- description: "The image format for the screenshot",
1801
- default: "png"
1802
- },
1803
- fullPage: {
1804
- type: "boolean",
1805
- title: "Full Page",
1806
- description: "Whether to capture the full scrollable page",
1807
- default: false
1808
- }
1809
- },
1810
- additionalProperties: false
1811
- };
1812
- var inputSchema8 = {
1813
- type: "object",
1814
- properties: {
1815
- sessionId: {
1816
- type: "string",
1817
- title: "Session ID",
1818
- description: "The browser session to use"
1819
- }
1820
- },
1821
- required: ["sessionId"],
1822
- additionalProperties: false
1823
- };
1824
- var outputSchema8 = {
1825
- type: "object",
1826
- properties: {
1827
- sessionId: {
1828
- type: "string",
1829
- title: "Session ID",
1830
- description: "The browser session ID"
1831
- },
1832
- image: {
1833
- type: "string",
1834
- format: "binary",
1835
- title: "Image",
1836
- description: "The screenshot image data"
1837
- }
1838
- },
1839
- required: ["sessionId", "image"],
1840
- additionalProperties: false
1841
- };
1842
-
1843
- class BrowserScreenshotTask extends Task8 {
1844
- static type = "BrowserScreenshotTask";
1845
- static category = "Browser";
1846
- static title = "Browser Screenshot";
1847
- static description = "Takes a screenshot of the current browser page";
1848
- static cacheable = false;
1849
- static configSchema() {
1850
- return browserScreenshotTaskConfigSchema;
1851
- }
1852
- static inputSchema() {
1853
- return inputSchema8;
1854
- }
1855
- static outputSchema() {
1856
- return outputSchema8;
1857
- }
1858
- async execute(input, _executeContext) {
1859
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1860
- const format = this.config.format ?? "png";
1861
- const fullPage = this.config.fullPage ?? false;
1862
- const image = await ctx.screenshot({ format, fullPage });
1863
- return { sessionId: input.sessionId, image };
1864
- }
1865
- }
1866
- // src/task/tasks/BrowserClickTask.ts
1867
- import { Task as Task9, TaskConfigSchema as TaskConfigSchema9 } from "@workglow/task-graph";
1868
- var browserClickTaskConfigSchema = {
1869
- type: "object",
1870
- properties: {
1871
- ...TaskConfigSchema9["properties"],
1872
- modifiers: {
1873
- type: "array",
1874
- items: {
1875
- type: "string",
1876
- enum: ["Alt", "Control", "Meta", "Shift"]
1877
- },
1878
- title: "Modifiers",
1879
- description: "Keyboard modifiers to hold during the click"
1880
- }
1881
- },
1882
- additionalProperties: false
1883
- };
1884
- var inputSchema9 = {
1885
- type: "object",
1886
- properties: {
1887
- sessionId: {
1888
- type: "string",
1889
- title: "Session ID",
1890
- description: "The browser session to use"
1891
- },
1892
- ref: {
1893
- type: "string",
1894
- title: "Element Ref",
1895
- description: "The element reference to click"
1896
- },
1897
- role: {
1898
- type: "string",
1899
- title: "ARIA Role",
1900
- description: "The ARIA role of the element to click"
1901
- },
1902
- name: {
1903
- type: "string",
1904
- title: "Accessible Name",
1905
- description: "The accessible name of the element to click"
1906
- }
1907
- },
1908
- required: ["sessionId"],
1909
- additionalProperties: false
1910
- };
1911
- var outputSchema9 = {
1912
- type: "object",
1913
- properties: {
1914
- sessionId: {
1915
- type: "string",
1916
- title: "Session ID",
1917
- description: "The browser session ID"
1918
- }
1919
- },
1920
- required: ["sessionId"],
1921
- additionalProperties: false
1922
- };
1923
-
1924
- class BrowserClickTask extends Task9 {
1925
- static type = "BrowserClickTask";
1926
- static category = "Browser";
1927
- static title = "Browser Click";
1928
- static description = "Clicks an element in the browser by ref or by ARIA role and name";
1929
- static cacheable = false;
1930
- static configSchema() {
1931
- return browserClickTaskConfigSchema;
1932
- }
1933
- static inputSchema() {
1934
- return inputSchema9;
1935
- }
1936
- static outputSchema() {
1937
- return outputSchema9;
1938
- }
1939
- async execute(input, _executeContext) {
1940
- const ctx = BrowserSessionRegistry.get(input.sessionId);
1941
- const opts = this.config.modifiers ? { modifiers: this.config.modifiers } : undefined;
1942
- if (input.ref) {
1943
- await ctx.click(input.ref, opts);
1944
- } else if (input.role && input.name) {
1945
- await ctx.clickByRole(input.role, input.name, opts);
1946
- } else {
1947
- throw new Error("BrowserClickTask: either ref or role+name must be provided");
1948
- }
1949
- return { sessionId: input.sessionId };
1950
- }
1951
- }
1952
- // src/task/tasks/BrowserFillTask.ts
1953
- import { Task as Task10, TaskConfigSchema as TaskConfigSchema10 } from "@workglow/task-graph";
1954
- var browserFillTaskConfigSchema = {
1955
- type: "object",
1956
- properties: {
1957
- ...TaskConfigSchema10["properties"],
1958
- clearFirst: {
1959
- type: "boolean",
1960
- title: "Clear First",
1961
- description: "Whether to clear the field before filling",
1962
- default: true
1963
- }
1964
- },
1965
- additionalProperties: false
1966
- };
1967
- var inputSchema10 = {
1968
- type: "object",
1969
- properties: {
1970
- sessionId: {
1971
- type: "string",
1972
- title: "Session ID",
1973
- description: "The browser session to use"
1974
- },
1975
- ref: {
1976
- type: "string",
1977
- title: "Element Ref",
1978
- description: "The element reference to fill"
1979
- },
1980
- label: {
1981
- type: "string",
1982
- title: "Label",
1983
- description: "The label text of the input to fill"
1984
- },
1985
- value: {
1986
- type: "string",
1987
- title: "Value",
1988
- description: "The value to fill into the input"
1989
- }
1990
- },
1991
- required: ["sessionId", "value"],
1992
- additionalProperties: false
1993
- };
1994
- var outputSchema10 = {
1995
- type: "object",
1996
- properties: {
1997
- sessionId: {
1998
- type: "string",
1999
- title: "Session ID",
2000
- description: "The browser session ID"
2001
- }
2002
- },
2003
- required: ["sessionId"],
2004
- additionalProperties: false
2005
- };
2006
-
2007
- class BrowserFillTask extends Task10 {
2008
- static type = "BrowserFillTask";
2009
- static category = "Browser";
2010
- static title = "Browser Fill";
2011
- static description = "Fills a text input in the browser by ref or by label";
2012
- static cacheable = false;
2013
- static configSchema() {
2014
- return browserFillTaskConfigSchema;
2015
- }
2016
- static inputSchema() {
2017
- return inputSchema10;
2018
- }
2019
- static outputSchema() {
2020
- return outputSchema10;
2021
- }
2022
- async execute(input, _executeContext) {
2023
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2024
- if (input.ref) {
2025
- await ctx.fill(input.ref, input.value);
2026
- } else if (input.label) {
2027
- await ctx.fillByLabel(input.label, input.value);
2028
- } else {
2029
- throw new Error("BrowserFillTask: either ref or label must be provided");
2030
- }
2031
- return { sessionId: input.sessionId };
2032
- }
2033
- }
2034
- // src/task/tasks/BrowserSelectTask.ts
2035
- import { Task as Task11, TaskConfigSchema as TaskConfigSchema11 } from "@workglow/task-graph";
2036
- var inputSchema11 = {
2037
- type: "object",
2038
- properties: {
2039
- sessionId: {
2040
- type: "string",
2041
- title: "Session ID",
2042
- description: "The browser session to use"
2043
- },
2044
- ref: {
2045
- type: "string",
2046
- title: "Element Ref",
2047
- description: "The element reference of the select element"
2048
- },
2049
- label: {
2050
- type: "string",
2051
- title: "Label",
2052
- description: "The label text of the select element (not yet supported, use ref)"
2053
- },
2054
- value: {
2055
- type: "string",
2056
- title: "Value",
2057
- description: "The option value to select"
2058
- }
2059
- },
2060
- required: ["sessionId", "value"],
2061
- additionalProperties: false
2062
- };
2063
- var outputSchema11 = {
2064
- type: "object",
2065
- properties: {
2066
- sessionId: {
2067
- type: "string",
2068
- title: "Session ID",
2069
- description: "The browser session ID"
2070
- }
2071
- },
2072
- required: ["sessionId"],
2073
- additionalProperties: false
2074
- };
2075
-
2076
- class BrowserSelectTask extends Task11 {
2077
- static type = "BrowserSelectTask";
2078
- static category = "Browser";
2079
- static title = "Browser Select";
2080
- static description = "Selects an option in a select element identified by ref";
2081
- static cacheable = false;
2082
- static configSchema() {
2083
- return TaskConfigSchema11;
2084
- }
2085
- static inputSchema() {
2086
- return inputSchema11;
2087
- }
2088
- static outputSchema() {
2089
- return outputSchema11;
2090
- }
2091
- async execute(input, _executeContext) {
2092
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2093
- if (input.ref) {
2094
- await ctx.selectOption(input.ref, input.value);
2095
- } else {
2096
- throw new Error("BrowserSelectTask: ref must be provided");
2097
- }
2098
- return { sessionId: input.sessionId };
2099
- }
2100
- }
2101
- // src/task/tasks/BrowserHoverTask.ts
2102
- import { Task as Task12, TaskConfigSchema as TaskConfigSchema12 } from "@workglow/task-graph";
2103
- var inputSchema12 = {
2104
- type: "object",
2105
- properties: {
2106
- sessionId: {
2107
- type: "string",
2108
- title: "Session ID",
2109
- description: "The browser session to use"
2110
- },
2111
- ref: {
2112
- type: "string",
2113
- title: "Element Ref",
2114
- description: "The element reference to hover over"
2115
- },
2116
- role: {
2117
- type: "string",
2118
- title: "ARIA Role",
2119
- description: "The ARIA role of the element to hover (not yet supported, use ref)"
2120
- },
2121
- name: {
2122
- type: "string",
2123
- title: "Accessible Name",
2124
- description: "The accessible name of the element to hover (not yet supported, use ref)"
2125
- }
2126
- },
2127
- required: ["sessionId"],
2128
- additionalProperties: false
2129
- };
2130
- var outputSchema12 = {
2131
- type: "object",
2132
- properties: {
2133
- sessionId: {
2134
- type: "string",
2135
- title: "Session ID",
2136
- description: "The browser session ID"
2137
- }
2138
- },
2139
- required: ["sessionId"],
2140
- additionalProperties: false
2141
- };
2142
-
2143
- class BrowserHoverTask extends Task12 {
2144
- static type = "BrowserHoverTask";
2145
- static category = "Browser";
2146
- static title = "Browser Hover";
2147
- static description = "Hovers over an element in the browser identified by ref";
2148
- static cacheable = false;
2149
- static configSchema() {
2150
- return TaskConfigSchema12;
2151
- }
2152
- static inputSchema() {
2153
- return inputSchema12;
2154
- }
2155
- static outputSchema() {
2156
- return outputSchema12;
2157
- }
2158
- async execute(input, _executeContext) {
2159
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2160
- if (input.ref) {
2161
- await ctx.hover(input.ref);
2162
- } else {
2163
- throw new Error("BrowserHoverTask: ref must be provided");
2164
- }
2165
- return { sessionId: input.sessionId };
2166
- }
2167
- }
2168
- // src/task/tasks/BrowserExtractTextTask.ts
2169
- import { Task as Task13, TaskConfigSchema as TaskConfigSchema13 } from "@workglow/task-graph";
2170
- var browserExtractTextTaskConfigSchema = {
2171
- type: "object",
2172
- properties: {
2173
- ...TaskConfigSchema13["properties"],
2174
- ref: {
2175
- type: "string",
2176
- title: "Element Ref",
2177
- description: "The element reference to extract text from (extracts from full page if not provided)"
2178
- }
2179
- },
2180
- additionalProperties: false
2181
- };
2182
- var inputSchema13 = {
2183
- type: "object",
2184
- properties: {
2185
- sessionId: {
2186
- type: "string",
2187
- title: "Session ID",
2188
- description: "The browser session to use"
2189
- }
2190
- },
2191
- required: ["sessionId"],
2192
- additionalProperties: false
2193
- };
2194
- var outputSchema13 = {
2195
- type: "object",
2196
- properties: {
2197
- sessionId: {
2198
- type: "string",
2199
- title: "Session ID",
2200
- description: "The browser session ID"
2201
- },
2202
- text: {
2203
- type: "string",
2204
- title: "Text",
2205
- description: "The extracted text content"
2206
- }
2207
- },
2208
- required: ["sessionId", "text"],
2209
- additionalProperties: false
2210
- };
2211
-
2212
- class BrowserExtractTextTask extends Task13 {
2213
- static type = "BrowserExtractTextTask";
2214
- static category = "Browser";
2215
- static title = "Browser Extract Text";
2216
- static description = "Extracts text content from a specific element or the full page";
2217
- static cacheable = false;
2218
- static configSchema() {
2219
- return browserExtractTextTaskConfigSchema;
2220
- }
2221
- static inputSchema() {
2222
- return inputSchema13;
2223
- }
2224
- static outputSchema() {
2225
- return outputSchema13;
2226
- }
2227
- async execute(input, _executeContext) {
2228
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2229
- let ref = this.config.ref;
2230
- if (!ref) {
2231
- const bodyRef = await ctx.querySelector("body");
2232
- if (!bodyRef) {
2233
- throw new Error("BrowserExtractTextTask: could not find body element");
2234
- }
2235
- ref = bodyRef;
2236
- }
2237
- const text = await ctx.textContent(ref);
2238
- return { sessionId: input.sessionId, text: text ?? "" };
2239
- }
2240
- }
2241
- // src/task/tasks/BrowserExtractHtmlTask.ts
2242
- import { Task as Task14, TaskConfigSchema as TaskConfigSchema14 } from "@workglow/task-graph";
2243
- var browserExtractHtmlTaskConfigSchema = {
2244
- type: "object",
2245
- properties: {
2246
- ...TaskConfigSchema14["properties"],
2247
- ref: {
2248
- type: "string",
2249
- title: "Element Ref",
2250
- description: "The element reference to extract HTML from"
2251
- },
2252
- selector: {
2253
- type: "string",
2254
- title: "CSS Selector",
2255
- description: "CSS selector to find element when no ref is provided"
2256
- }
2257
- },
2258
- additionalProperties: false
2259
- };
2260
- var inputSchema14 = {
2261
- type: "object",
2262
- properties: {
2263
- sessionId: {
2264
- type: "string",
2265
- title: "Session ID",
2266
- description: "The browser session to use"
2267
- }
2268
- },
2269
- required: ["sessionId"],
2270
- additionalProperties: false
2271
- };
2272
- var outputSchema14 = {
2273
- type: "object",
2274
- properties: {
2275
- sessionId: {
2276
- type: "string",
2277
- title: "Session ID",
2278
- description: "The browser session ID"
2279
- },
2280
- html: {
2281
- type: "string",
2282
- title: "HTML",
2283
- description: "The extracted HTML content"
2284
- }
2285
- },
2286
- required: ["sessionId", "html"],
2287
- additionalProperties: false
2288
- };
2289
-
2290
- class BrowserExtractHtmlTask extends Task14 {
2291
- static type = "BrowserExtractHtmlTask";
2292
- static category = "Browser";
2293
- static title = "Browser Extract HTML";
2294
- static description = "Extracts HTML content from a specific element by ref or CSS selector";
2295
- static cacheable = false;
2296
- static configSchema() {
2297
- return browserExtractHtmlTaskConfigSchema;
2298
- }
2299
- static inputSchema() {
2300
- return inputSchema14;
2301
- }
2302
- static outputSchema() {
2303
- return outputSchema14;
2304
- }
2305
- async execute(input, _executeContext) {
2306
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2307
- let ref = this.config.ref;
2308
- if (!ref) {
2309
- if (!this.config.selector) {
2310
- throw new Error("BrowserExtractHtmlTask requires either config.ref or config.selector");
2311
- }
2312
- const found = await ctx.querySelector(this.config.selector);
2313
- if (!found) {
2314
- throw new Error(`BrowserExtractHtmlTask could not find an element matching selector: ${this.config.selector}`);
2315
- }
2316
- ref = found;
2317
- }
2318
- const html = await ctx.innerHTML(ref);
2319
- return { sessionId: input.sessionId, html };
2320
- }
2321
- }
2322
- // src/task/tasks/BrowserAttributeTask.ts
2323
- import { Task as Task15, TaskConfigSchema as TaskConfigSchema15 } from "@workglow/task-graph";
2324
- var inputSchema15 = {
2325
- type: "object",
2326
- properties: {
2327
- sessionId: {
2328
- type: "string",
2329
- title: "Session ID",
2330
- description: "The browser session to use"
2331
- },
2332
- ref: {
2333
- type: "string",
2334
- title: "Element Ref",
2335
- description: "The element reference to get the attribute from"
2336
- },
2337
- attribute: {
2338
- type: "string",
2339
- title: "Attribute Name",
2340
- description: "The name of the attribute to retrieve"
2341
- }
2342
- },
2343
- required: ["sessionId", "ref", "attribute"],
2344
- additionalProperties: false
2345
- };
2346
- var outputSchema15 = {
2347
- type: "object",
2348
- properties: {
2349
- sessionId: {
2350
- type: "string",
2351
- title: "Session ID",
2352
- description: "The browser session ID"
2353
- },
2354
- value: {
2355
- type: ["string", "null"],
2356
- title: "Value",
2357
- description: "The attribute value, or null if not present"
2358
- }
2359
- },
2360
- required: ["sessionId", "value"],
2361
- additionalProperties: false
2362
- };
2363
-
2364
- class BrowserAttributeTask extends Task15 {
2365
- static type = "BrowserAttributeTask";
2366
- static category = "Browser";
2367
- static title = "Browser Attribute";
2368
- static description = "Retrieves the value of an attribute from a browser element";
2369
- static cacheable = false;
2370
- static configSchema() {
2371
- return TaskConfigSchema15;
2372
- }
2373
- static inputSchema() {
2374
- return inputSchema15;
2375
- }
2376
- static outputSchema() {
2377
- return outputSchema15;
2378
- }
2379
- async execute(input, _executeContext) {
2380
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2381
- const value = await ctx.attribute(input.ref, input.attribute);
2382
- return { sessionId: input.sessionId, value };
2383
- }
2384
- }
2385
- // src/task/tasks/BrowserQuerySelectorTask.ts
2386
- import { Task as Task16, TaskConfigSchema as TaskConfigSchema16 } from "@workglow/task-graph";
2387
- var inputSchema16 = {
2388
- type: "object",
2389
- properties: {
2390
- sessionId: {
2391
- type: "string",
2392
- title: "Session ID",
2393
- description: "The browser session to use"
2394
- },
2395
- selector: {
2396
- type: "string",
2397
- title: "CSS Selector",
2398
- description: "The CSS selector to query for"
2399
- }
2400
- },
2401
- required: ["sessionId", "selector"],
2402
- additionalProperties: false
2403
- };
2404
- var outputSchema16 = {
2405
- type: "object",
2406
- properties: {
2407
- sessionId: {
2408
- type: "string",
2409
- title: "Session ID",
2410
- description: "The browser session ID"
2411
- },
2412
- refs: {
2413
- type: "array",
2414
- items: { type: "string" },
2415
- title: "Element Refs",
2416
- description: "The element references matching the selector"
2417
- }
2418
- },
2419
- required: ["sessionId", "refs"],
2420
- additionalProperties: false
2421
- };
2422
-
2423
- class BrowserQuerySelectorTask extends Task16 {
2424
- static type = "BrowserQuerySelectorTask";
2425
- static category = "Browser";
2426
- static title = "Browser Query Selector";
2427
- static description = "Queries all elements matching a CSS selector and returns their refs";
2428
- static cacheable = false;
2429
- static configSchema() {
2430
- return TaskConfigSchema16;
2431
- }
2432
- static inputSchema() {
2433
- return inputSchema16;
2434
- }
2435
- static outputSchema() {
2436
- return outputSchema16;
2437
- }
2438
- async execute(input, _executeContext) {
2439
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2440
- const refs = await ctx.querySelectorAll(input.selector);
2441
- return { sessionId: input.sessionId, refs };
2442
- }
2443
- }
2444
- // src/task/tasks/BrowserEvaluateTask.ts
2445
- import {
2446
- Entitlements as Entitlements3,
2447
- Task as Task17,
2448
- TaskConfigSchema as TaskConfigSchema17
2449
- } from "@workglow/task-graph";
2450
- var inputSchema17 = {
2451
- type: "object",
2452
- properties: {
2453
- sessionId: {
2454
- type: "string",
2455
- title: "Session ID",
2456
- description: "The browser session to use"
2457
- },
2458
- expression: {
2459
- type: "string",
2460
- title: "Expression",
2461
- description: "The JavaScript expression to evaluate in the page context"
2462
- }
2463
- },
2464
- required: ["sessionId", "expression"],
2465
- additionalProperties: false
2466
- };
2467
- var outputSchema17 = {
2468
- type: "object",
2469
- properties: {
2470
- sessionId: {
2471
- type: "string",
2472
- title: "Session ID",
2473
- description: "The browser session ID"
2474
- },
2475
- result: {
2476
- title: "Result",
2477
- description: "The result of the evaluated expression"
2478
- }
2479
- },
2480
- required: ["sessionId"],
2481
- additionalProperties: false
2482
- };
2483
-
2484
- class BrowserEvaluateTask extends Task17 {
2485
- static type = "BrowserEvaluateTask";
2486
- static category = "Browser";
2487
- static title = "Browser Evaluate";
2488
- static description = "Evaluates a JavaScript expression in the browser page context";
2489
- static cacheable = false;
2490
- static configSchema() {
2491
- return TaskConfigSchema17;
2492
- }
2493
- static inputSchema() {
2494
- return inputSchema17;
2495
- }
2496
- static outputSchema() {
2497
- return outputSchema17;
2498
- }
2499
- static entitlements() {
2500
- return {
2501
- entitlements: [
2502
- {
2503
- id: Entitlements3.BROWSER_CONTROL_EVALUATE,
2504
- reason: "Evaluates arbitrary JavaScript in the browser context"
2505
- }
2506
- ]
2507
- };
2508
- }
2509
- async execute(input, _executeContext) {
2510
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2511
- const result = await ctx.evaluate(input.expression);
2512
- return { sessionId: input.sessionId, result };
2513
- }
2514
- }
2515
- // src/task/tasks/BrowserPressKeyTask.ts
2516
- import { Task as Task18, TaskConfigSchema as TaskConfigSchema18 } from "@workglow/task-graph";
2517
- var browserPressKeyTaskConfigSchema = {
2518
- type: "object",
2519
- properties: {
2520
- ...TaskConfigSchema18["properties"],
2521
- modifiers: {
2522
- type: "array",
2523
- items: {
2524
- type: "string",
2525
- enum: ["Alt", "Control", "Meta", "Shift"]
2526
- },
2527
- title: "Modifiers",
2528
- description: "Keyboard modifiers to hold during the key press"
2529
- }
2530
- },
2531
- additionalProperties: false
2532
- };
2533
- var inputSchema18 = {
2534
- type: "object",
2535
- properties: {
2536
- sessionId: {
2537
- type: "string",
2538
- title: "Session ID",
2539
- description: "The browser session to use"
2540
- },
2541
- key: {
2542
- type: "string",
2543
- title: "Key",
2544
- description: "The key to press (e.g. Enter, Tab, ArrowDown)"
2545
- }
2546
- },
2547
- required: ["sessionId", "key"],
2548
- additionalProperties: false
2549
- };
2550
- var outputSchema18 = {
2551
- type: "object",
2552
- properties: {
2553
- sessionId: {
2554
- type: "string",
2555
- title: "Session ID",
2556
- description: "The browser session ID"
2557
- }
2558
- },
2559
- required: ["sessionId"],
2560
- additionalProperties: false
2561
- };
2562
-
2563
- class BrowserPressKeyTask extends Task18 {
2564
- static type = "BrowserPressKeyTask";
2565
- static category = "Browser";
2566
- static title = "Browser Press Key";
2567
- static description = "Presses a keyboard key in the browser, optionally with modifiers";
2568
- static cacheable = false;
2569
- static configSchema() {
2570
- return browserPressKeyTaskConfigSchema;
2571
- }
2572
- static inputSchema() {
2573
- return inputSchema18;
2574
- }
2575
- static outputSchema() {
2576
- return outputSchema18;
2577
- }
2578
- buildKeyChord(key) {
2579
- const modifiers = this.config.modifiers?.filter(Boolean) ?? [];
2580
- return modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
2581
- }
2582
- async execute(input, _executeContext) {
2583
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2584
- await ctx.pressKey(this.buildKeyChord(input.key));
2585
- return { sessionId: input.sessionId };
2586
- }
2587
- }
2588
- // src/task/tasks/BrowserTypeTask.ts
2589
- import { Task as Task19, TaskConfigSchema as TaskConfigSchema19 } from "@workglow/task-graph";
2590
- var browserTypeTaskConfigSchema = {
2591
- type: "object",
2592
- properties: {
2593
- ...TaskConfigSchema19["properties"]
2594
- },
2595
- additionalProperties: false
2596
- };
2597
- var inputSchema19 = {
2598
- type: "object",
2599
- properties: {
2600
- sessionId: {
2601
- type: "string",
2602
- title: "Session ID",
2603
- description: "The browser session to use"
2604
- },
2605
- text: {
2606
- type: "string",
2607
- title: "Text",
2608
- description: "The text to type into the currently focused element"
2609
- }
2610
- },
2611
- required: ["sessionId", "text"],
2612
- additionalProperties: false
2613
- };
2614
- var outputSchema19 = {
2615
- type: "object",
2616
- properties: {
2617
- sessionId: {
2618
- type: "string",
2619
- title: "Session ID",
2620
- description: "The browser session ID"
2621
- }
2622
- },
2623
- required: ["sessionId"],
2624
- additionalProperties: false
2625
- };
2626
-
2627
- class BrowserTypeTask extends Task19 {
2628
- static type = "BrowserTypeTask";
2629
- static category = "Browser";
2630
- static title = "Browser Type";
2631
- static description = "Types text into the currently focused element in the browser";
2632
- static cacheable = false;
2633
- static configSchema() {
2634
- return browserTypeTaskConfigSchema;
2635
- }
2636
- static inputSchema() {
2637
- return inputSchema19;
2638
- }
2639
- static outputSchema() {
2640
- return outputSchema19;
2641
- }
2642
- async execute(input, _executeContext) {
2643
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2644
- await ctx.type(input.text);
2645
- return { sessionId: input.sessionId };
2646
- }
2647
- }
2648
- // src/task/tasks/BrowserScrollTask.ts
2649
- import { Task as Task20, TaskConfigSchema as TaskConfigSchema20 } from "@workglow/task-graph";
2650
- var browserScrollTaskConfigSchema = {
2651
- type: "object",
2652
- properties: {
2653
- ...TaskConfigSchema20["properties"],
2654
- x: {
2655
- type: "number",
2656
- title: "X",
2657
- description: "Horizontal scroll amount in pixels",
2658
- default: 0
2659
- },
2660
- y: {
2661
- type: "number",
2662
- title: "Y",
2663
- description: "Vertical scroll amount in pixels",
2664
- default: 0
2665
- },
2666
- ref: {
2667
- type: "string",
2668
- title: "Element Ref",
2669
- description: "The element reference to scroll within (scrolls page if not provided)"
2670
- }
2671
- },
2672
- additionalProperties: false
2673
- };
2674
- var inputSchema20 = {
2675
- type: "object",
2676
- properties: {
2677
- sessionId: {
2678
- type: "string",
2679
- title: "Session ID",
2680
- description: "The browser session to use"
2681
- }
2682
- },
2683
- required: ["sessionId"],
2684
- additionalProperties: false
2685
- };
2686
- var outputSchema20 = {
2687
- type: "object",
2688
- properties: {
2689
- sessionId: {
2690
- type: "string",
2691
- title: "Session ID",
2692
- description: "The browser session ID"
2693
- }
2694
- },
2695
- required: ["sessionId"],
2696
- additionalProperties: false
2697
- };
2698
-
2699
- class BrowserScrollTask extends Task20 {
2700
- static type = "BrowserScrollTask";
2701
- static category = "Browser";
2702
- static title = "Browser Scroll";
2703
- static description = "Scrolls the page or a specific element by the given pixel deltas";
2704
- static cacheable = false;
2705
- static configSchema() {
2706
- return browserScrollTaskConfigSchema;
2707
- }
2708
- static inputSchema() {
2709
- return inputSchema20;
2710
- }
2711
- static outputSchema() {
2712
- return outputSchema20;
2713
- }
2714
- async execute(input, _executeContext) {
2715
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2716
- await ctx.scroll(this.config.x ?? 0, this.config.y ?? 0, this.config.ref);
2717
- return { sessionId: input.sessionId };
2718
- }
2719
- }
2720
- // src/task/tasks/BrowserUploadTask.ts
2721
- import {
2722
- Entitlements as Entitlements4,
2723
- Task as Task21,
2724
- TaskConfigSchema as TaskConfigSchema21
2725
- } from "@workglow/task-graph";
2726
- var browserUploadTaskConfigSchema = {
2727
- type: "object",
2728
- properties: {
2729
- ...TaskConfigSchema21["properties"]
2730
- },
2731
- additionalProperties: false
2732
- };
2733
- var inputSchema21 = {
2734
- type: "object",
2735
- properties: {
2736
- sessionId: {
2737
- type: "string",
2738
- title: "Session ID",
2739
- description: "The browser session to use"
2740
- },
2741
- ref: {
2742
- type: "string",
2743
- title: "Element Ref",
2744
- description: "The file input element reference to upload to"
2745
- },
2746
- filePaths: {
2747
- type: "array",
2748
- items: {
2749
- type: "string"
2750
- },
2751
- title: "File Paths",
2752
- description: "The local file paths to upload"
2753
- }
2754
- },
2755
- required: ["sessionId", "ref", "filePaths"],
2756
- additionalProperties: false
2757
- };
2758
- var outputSchema21 = {
2759
- type: "object",
2760
- properties: {
2761
- sessionId: {
2762
- type: "string",
2763
- title: "Session ID",
2764
- description: "The browser session ID"
2765
- }
2766
- },
2767
- required: ["sessionId"],
2768
- additionalProperties: false
2769
- };
2770
-
2771
- class BrowserUploadTask extends Task21 {
2772
- static type = "BrowserUploadTask";
2773
- static category = "Browser";
2774
- static title = "Browser Upload";
2775
- static description = "Uploads one or more files to a file input element in the browser";
2776
- static cacheable = false;
2777
- static entitlements() {
2778
- return {
2779
- entitlements: [
2780
- { id: Entitlements4.FILESYSTEM_READ, reason: "Reads local files for upload to browser" }
2781
- ]
2782
- };
2783
- }
2784
- static configSchema() {
2785
- return browserUploadTaskConfigSchema;
2786
- }
2787
- static inputSchema() {
2788
- return inputSchema21;
2789
- }
2790
- static outputSchema() {
2791
- return outputSchema21;
2792
- }
2793
- async execute(input, _executeContext) {
2794
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2795
- await ctx.uploadFile(input.ref, input.filePaths);
2796
- return { sessionId: input.sessionId };
2797
- }
2798
- }
2799
- // src/task/tasks/BrowserWaitTask.ts
2800
- import { Task as Task22, TaskConfigSchema as TaskConfigSchema22 } from "@workglow/task-graph";
2801
- var browserWaitTaskConfigSchema = {
2802
- type: "object",
2803
- properties: {
2804
- ...TaskConfigSchema22["properties"],
2805
- waitFor: {
2806
- type: "string",
2807
- enum: ["navigation", "selector", "idle"],
2808
- title: "Wait For",
2809
- description: "The condition to wait for",
2810
- default: "idle"
2811
- },
2812
- selector: {
2813
- type: "string",
2814
- title: "Selector",
2815
- description: "CSS selector to wait for (required when waitFor is 'selector')"
2816
- },
2817
- timeout: {
2818
- type: "number",
2819
- title: "Timeout",
2820
- description: "Maximum time to wait in milliseconds",
2821
- default: 30000
2822
- }
2823
- },
2824
- additionalProperties: false
2825
- };
2826
- var inputSchema22 = {
2827
- type: "object",
2828
- properties: {
2829
- sessionId: {
2830
- type: "string",
2831
- title: "Session ID",
2832
- description: "The browser session to use"
2833
- }
2834
- },
2835
- required: ["sessionId"],
2836
- additionalProperties: false
2837
- };
2838
- var outputSchema22 = {
2839
- type: "object",
2840
- properties: {
2841
- sessionId: {
2842
- type: "string",
2843
- title: "Session ID",
2844
- description: "The browser session ID"
2845
- }
2846
- },
2847
- required: ["sessionId"],
2848
- additionalProperties: false
2849
- };
2850
-
2851
- class BrowserWaitTask extends Task22 {
2852
- static type = "BrowserWaitTask";
2853
- static category = "Browser";
2854
- static title = "Browser Wait";
2855
- static description = "Waits for a navigation, selector, or network idle state in the browser";
2856
- static cacheable = false;
2857
- static configSchema() {
2858
- return browserWaitTaskConfigSchema;
2859
- }
2860
- static inputSchema() {
2861
- return inputSchema22;
2862
- }
2863
- static outputSchema() {
2864
- return outputSchema22;
2865
- }
2866
- async execute(input, _executeContext) {
2867
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2868
- const waitFor = this.config.waitFor ?? "idle";
2869
- const timeout = this.config.timeout ?? 30000;
2870
- switch (waitFor) {
2871
- case "navigation":
2872
- await ctx.waitForNavigation({ timeout });
2873
- break;
2874
- case "selector": {
2875
- const selector = this.config.selector;
2876
- if (!selector) {
2877
- throw new Error("BrowserWaitTask: selector is required when waitFor is 'selector'");
2878
- }
2879
- await ctx.waitForSelector(selector, { timeout });
2880
- break;
2881
- }
2882
- case "idle":
2883
- await ctx.waitForIdle({ timeout });
2884
- break;
2885
- }
2886
- return { sessionId: input.sessionId };
2887
- }
2888
- }
2889
- // src/task/tasks/BrowserNewTabTask.ts
2890
- import { Task as Task23, TaskConfigSchema as TaskConfigSchema23 } from "@workglow/task-graph";
2891
- var browserNewTabTaskConfigSchema = {
2892
- type: "object",
2893
- properties: {
2894
- ...TaskConfigSchema23["properties"]
2895
- },
2896
- additionalProperties: false
2897
- };
2898
- var inputSchema23 = {
2899
- type: "object",
2900
- properties: {
2901
- sessionId: {
2902
- type: "string",
2903
- title: "Session ID",
2904
- description: "The browser session to use"
2905
- },
2906
- url: {
2907
- type: "string",
2908
- title: "URL",
2909
- description: "Optional URL to open in the new tab"
2910
- }
2911
- },
2912
- required: ["sessionId"],
2913
- additionalProperties: false
2914
- };
2915
- var outputSchema23 = {
2916
- type: "object",
2917
- properties: {
2918
- sessionId: {
2919
- type: "string",
2920
- title: "Session ID",
2921
- description: "The browser session ID"
2922
- },
2923
- tabId: {
2924
- type: "string",
2925
- title: "Tab ID",
2926
- description: "The unique identifier for the new tab"
2927
- }
2928
- },
2929
- required: ["sessionId", "tabId"],
2930
- additionalProperties: false
2931
- };
2932
-
2933
- class BrowserNewTabTask extends Task23 {
2934
- static type = "BrowserNewTabTask";
2935
- static category = "Browser";
2936
- static title = "Browser New Tab";
2937
- static description = "Opens a new browser tab and returns its tab ID";
2938
- static cacheable = false;
2939
- static configSchema() {
2940
- return browserNewTabTaskConfigSchema;
2941
- }
2942
- static inputSchema() {
2943
- return inputSchema23;
2944
- }
2945
- static outputSchema() {
2946
- return outputSchema23;
2947
- }
2948
- async execute(input, _executeContext) {
2949
- const ctx = BrowserSessionRegistry.get(input.sessionId);
2950
- const tabInfo = await ctx.newTab(input.url);
2951
- return { sessionId: input.sessionId, tabId: tabInfo.tabId };
2952
- }
2953
- }
2954
- // src/task/tasks/BrowserSwitchTabTask.ts
2955
- import { Task as Task24, TaskConfigSchema as TaskConfigSchema24 } from "@workglow/task-graph";
2956
- var browserSwitchTabTaskConfigSchema = {
2957
- type: "object",
2958
- properties: {
2959
- ...TaskConfigSchema24["properties"]
2960
- },
2961
- additionalProperties: false
2962
- };
2963
- var inputSchema24 = {
2964
- type: "object",
2965
- properties: {
2966
- sessionId: {
2967
- type: "string",
2968
- title: "Session ID",
2969
- description: "The browser session to use"
2970
- },
2971
- tabId: {
2972
- type: "string",
2973
- title: "Tab ID",
2974
- description: "The tab ID to switch to"
2975
- }
2976
- },
2977
- required: ["sessionId", "tabId"],
2978
- additionalProperties: false
2979
- };
2980
- var outputSchema24 = {
2981
- type: "object",
2982
- properties: {
2983
- sessionId: {
2984
- type: "string",
2985
- title: "Session ID",
2986
- description: "The browser session ID"
2987
- }
2988
- },
2989
- required: ["sessionId"],
2990
- additionalProperties: false
2991
- };
2992
-
2993
- class BrowserSwitchTabTask extends Task24 {
2994
- static type = "BrowserSwitchTabTask";
2995
- static category = "Browser";
2996
- static title = "Browser Switch Tab";
2997
- static description = "Switches the active browser tab to the specified tab ID";
2998
- static cacheable = false;
2999
- static configSchema() {
3000
- return browserSwitchTabTaskConfigSchema;
3001
- }
3002
- static inputSchema() {
3003
- return inputSchema24;
3004
- }
3005
- static outputSchema() {
3006
- return outputSchema24;
3007
- }
3008
- async execute(input, _executeContext) {
3009
- const ctx = BrowserSessionRegistry.get(input.sessionId);
3010
- await ctx.switchTab(input.tabId);
3011
- return { sessionId: input.sessionId };
3012
- }
3013
- }
3014
- // src/task/tasks/BrowserCloseTabTask.ts
3015
- import { Task as Task25, TaskConfigSchema as TaskConfigSchema25 } from "@workglow/task-graph";
3016
- var browserCloseTabTaskConfigSchema = {
3017
- type: "object",
3018
- properties: {
3019
- ...TaskConfigSchema25["properties"],
3020
- tabId: {
3021
- type: "string",
3022
- title: "Tab ID",
3023
- description: "The tab ID to close (closes current tab if not provided)"
3024
- }
3025
- },
3026
- additionalProperties: false
3027
- };
3028
- var inputSchema25 = {
3029
- type: "object",
3030
- properties: {
3031
- sessionId: {
3032
- type: "string",
3033
- title: "Session ID",
3034
- description: "The browser session to use"
3035
- }
3036
- },
3037
- required: ["sessionId"],
3038
- additionalProperties: false
3039
- };
3040
- var outputSchema25 = {
3041
- type: "object",
3042
- properties: {
3043
- sessionId: {
3044
- type: "string",
3045
- title: "Session ID",
3046
- description: "The browser session ID"
3047
- }
3048
- },
3049
- required: ["sessionId"],
3050
- additionalProperties: false
3051
- };
3052
-
3053
- class BrowserCloseTabTask extends Task25 {
3054
- static type = "BrowserCloseTabTask";
3055
- static category = "Browser";
3056
- static title = "Browser Close Tab";
3057
- static description = "Closes a browser tab by tab ID";
3058
- static cacheable = false;
3059
- static configSchema() {
3060
- return browserCloseTabTaskConfigSchema;
3061
- }
3062
- static inputSchema() {
3063
- return inputSchema25;
3064
- }
3065
- static outputSchema() {
3066
- return outputSchema25;
3067
- }
3068
- async execute(input, _executeContext) {
3069
- if (!this.config.tabId) {
3070
- throw new Error("BrowserCloseTabTask requires config.tabId");
3071
- }
3072
- const ctx = BrowserSessionRegistry.get(input.sessionId);
3073
- await ctx.closeTab(this.config.tabId);
3074
- return { sessionId: input.sessionId };
3075
- }
3076
- }
3077
- // src/task/tasks/BrowserLoginTask.ts
3078
- import {
3079
- Entitlements as Entitlements5,
3080
- mergeEntitlements as mergeEntitlements2,
3081
- Task as Task26,
3082
- TaskConfigSchema as TaskConfigSchema26
3083
- } from "@workglow/task-graph";
3084
- var browserLoginTaskConfigSchema = {
3085
- type: "object",
3086
- properties: {
3087
- ...TaskConfigSchema26["properties"],
3088
- mode: {
3089
- type: "string",
3090
- enum: ["manual", "credential", "ai"],
3091
- title: "Login Mode",
3092
- description: "The login strategy to use",
3093
- default: "manual"
3094
- },
3095
- credentialName: {
3096
- type: "string",
3097
- title: "Credential Name",
3098
- description: "Name of the stored credential to use (required when mode is 'credential')"
3099
- }
3100
- },
3101
- additionalProperties: false
3102
- };
3103
- var inputSchema26 = {
3104
- type: "object",
3105
- properties: {
3106
- sessionId: {
3107
- type: "string",
3108
- title: "Session ID",
3109
- description: "The browser session to use"
3110
- },
3111
- url: {
3112
- type: "string",
3113
- format: "uri",
3114
- title: "URL",
3115
- description: "The login page URL to navigate to"
3116
- }
3117
- },
3118
- required: ["sessionId", "url"],
3119
- additionalProperties: false
3120
- };
3121
- var outputSchema26 = {
3122
- type: "object",
3123
- properties: {
3124
- sessionId: {
3125
- type: "string",
3126
- title: "Session ID",
3127
- description: "The browser session ID"
3128
- }
3129
- },
3130
- required: ["sessionId"],
3131
- additionalProperties: false
3132
- };
3133
-
3134
- class BrowserLoginTask extends Task26 {
3135
- static type = "BrowserLoginTask";
3136
- static category = "Browser";
3137
- static title = "Browser Login";
3138
- static description = "Logs into a website using manual, credential, or AI-driven login strategies";
3139
- static cacheable = false;
3140
- static hasDynamicEntitlements = true;
3141
- static configSchema() {
3142
- return browserLoginTaskConfigSchema;
3143
- }
3144
- static inputSchema() {
3145
- return inputSchema26;
3146
- }
3147
- static outputSchema() {
3148
- return outputSchema26;
3149
- }
3150
- static entitlements() {
3151
- return {
3152
- entitlements: [
3153
- { id: Entitlements5.BROWSER_CONTROL_NAVIGATE, reason: "Navigates to the login page URL" }
3154
- ]
3155
- };
3156
- }
3157
- entitlements() {
3158
- const base = BrowserLoginTask.entitlements();
3159
- if (this.config.mode === "credential") {
3160
- return mergeEntitlements2(base, {
3161
- entitlements: [
3162
- {
3163
- id: Entitlements5.BROWSER_CONTROL_CREDENTIAL,
3164
- reason: "Accesses stored credentials for login"
3165
- }
3166
- ]
3167
- });
3168
- }
3169
- return base;
3170
- }
3171
- async execute(input, executeContext) {
3172
- const parsed = new URL(input.url, "https://placeholder");
3173
- if (parsed.protocol === "javascript:") {
3174
- throw new Error("BrowserLoginTask: javascript: URLs are not allowed");
3175
- }
3176
- const ctx = BrowserSessionRegistry.get(input.sessionId);
3177
- const mode = this.config.mode ?? "manual";
3178
- await ctx.navigate(input.url);
3179
- await executeContext.updateProgress(20, "Navigated to login page");
3180
- switch (mode) {
3181
- case "manual":
3182
- await executeContext.updateProgress(50, "Waiting for manual login...");
3183
- break;
3184
- case "credential":
3185
- throw new Error("Credential-based login mode is not yet implemented");
3186
- case "ai":
3187
- throw new Error("AI-driven login mode is not yet implemented");
3188
- }
3189
- return { sessionId: input.sessionId };
3190
- }
3191
- }
3192
- // src/task/ElectronBackend.ts
3193
- import { sleep as sleep2 } from "@workglow/util";
3194
- var electronModule = null;
3195
- async function getElectron() {
3196
- if (!electronModule) {
3197
- electronModule = await new Function("m", "return import(m)")("electron");
3198
- }
3199
- return electronModule;
3200
- }
3201
-
3202
- class ElectronBackend extends CDPBrowserBackend {
3203
- _window = null;
3204
- _webContents = null;
3205
- _connected = false;
3206
- _dialogHandler = null;
3207
- backendName = "ElectronBackend";
3208
- async cdp(method, params = {}) {
3209
- if (!this._webContents) {
3210
- throw new Error("ElectronBackend: not connected — call connect() first");
3211
- }
3212
- return this._webContents.debugger.sendCommand(method, params);
3213
- }
3214
- async evaluateInPage(script) {
3215
- return this.wc.executeJavaScript(script);
3216
- }
3217
- async connect(options = {}) {
3218
- const electron = await getElectron();
3219
- const { BrowserWindow, session: electronSession } = electron;
3220
- const { projectId = "default", profileName = "default", headless = false } = options;
3221
- const partitionString = `persist:${projectId}:${profileName}`;
3222
- const sess = electronSession.fromPartition(partitionString);
3223
- this._window = new BrowserWindow({
3224
- width: 1280,
3225
- height: 800,
3226
- show: !headless,
3227
- webPreferences: {
3228
- session: sess,
3229
- nodeIntegration: false,
3230
- contextIsolation: true
3231
- }
3232
- });
3233
- this._webContents = this._window.webContents;
3234
- try {
3235
- this._webContents.debugger.attach("1.3");
3236
- } catch {}
3237
- await this.cdp("Accessibility.enable");
3238
- this._webContents.on("select-client-certificate", (_event, _url, _list, callback) => {
3239
- callback(undefined);
3240
- });
3241
- this._webContents.on("will-prevent-unload", (event) => {
3242
- event.preventDefault();
3243
- });
3244
- this._connected = true;
3245
- }
3246
- async disconnect() {
3247
- this._connected = false;
3248
- try {
3249
- if (this._webContents) {
3250
- try {
3251
- this._webContents.debugger.detach();
3252
- } catch {}
3253
- }
3254
- if (this._window && !this._window.isDestroyed()) {
3255
- this._window.close();
3256
- }
3257
- } finally {
3258
- this._window = null;
3259
- this._webContents = null;
3260
- this._refMap.clear();
3261
- this._refCounter.count = 0;
3262
- }
3263
- }
3264
- isConnected() {
3265
- return this._connected && this._window !== null && !this._window.isDestroyed();
3266
- }
3267
- get wc() {
3268
- if (!this._webContents || !this._connected) {
3269
- throw new Error("ElectronBackend: not connected — call connect() first");
3270
- }
3271
- return this._webContents;
3272
- }
3273
- async navigate(url, _options = {}) {
3274
- await this.wc.loadURL(url);
3275
- }
3276
- async goBack(_options = {}) {
3277
- this.wc.navigationHistory.goBack();
3278
- await this.waitForNavigation();
3279
- }
3280
- async goForward(_options = {}) {
3281
- this.wc.navigationHistory.goForward();
3282
- await this.waitForNavigation();
3283
- }
3284
- async reload(_options = {}) {
3285
- this.wc.reload();
3286
- await this.waitForNavigation();
3287
- }
3288
- async currentUrl() {
3289
- return this.wc.getURL();
3290
- }
3291
- async title() {
3292
- return this.wc.getTitle();
3293
- }
3294
- async content() {
3295
- return this.wc.executeJavaScript("document.documentElement.outerHTML");
3296
- }
3297
- async evaluate(expression) {
3298
- return this.wc.executeJavaScript(expression);
3299
- }
3300
- async screenshot(options = {}) {
3301
- const { format = "png", quality } = options;
3302
- const image = await this.wc.capturePage();
3303
- if (format === "jpeg") {
3304
- return image.toJPEG(quality ?? 90);
3305
- }
3306
- return image.toPNG();
3307
- }
3308
- async download(trigger, options = {}) {
3309
- const os = await import("node:os");
3310
- const downloadDir = os.tmpdir();
3311
- const timeout = options.timeout ?? 30000;
3312
- await this.cdp("Browser.setDownloadBehavior", {
3313
- behavior: "allow",
3314
- downloadPath: downloadDir
3315
- });
3316
- let downloadPath = "";
3317
- let suggestedFilename = "";
3318
- const downloadPromise = new Promise((resolve, reject) => {
3319
- const timer = setTimeout(() => {
3320
- reject(new Error("ElectronBackend: download timed out"));
3321
- }, timeout);
3322
- const handler = (_event, item, _webContents) => {
3323
- suggestedFilename = item.getFilename ? item.getFilename() : "download";
3324
- item.once?.("done", (_e, state) => {
3325
- clearTimeout(timer);
3326
- if (state === "completed") {
3327
- downloadPath = item.getSavePath ? item.getSavePath() : downloadDir + "/" + suggestedFilename;
3328
- }
3329
- resolve();
3330
- });
3331
- };
3332
- this.wc.session.once("will-download", handler);
3333
- });
3334
- await trigger();
3335
- await downloadPromise;
3336
- if (!downloadPath) {
3337
- throw new Error("ElectronBackend: download failed — no path received");
3338
- }
3339
- return { path: downloadPath, suggestedFilename };
3340
- }
3341
- onDialog(handler) {
3342
- this._dialogHandler = handler;
3343
- this.cdp("Page.enable").then(() => {
3344
- this.wc.debugger.on("message", async (_event, method, params) => {
3345
- if (method !== "Page.javascriptDialogOpening")
3346
- return;
3347
- const info = {
3348
- type: params.type,
3349
- message: params.message,
3350
- defaultValue: params.defaultPrompt || undefined
3351
- };
3352
- if (this._dialogHandler) {
3353
- const action = await this._dialogHandler(info);
3354
- const accept = action.accept;
3355
- const promptText = accept && "promptText" in action ? action.promptText : undefined;
3356
- await this.cdp("Page.handleJavaScriptDialog", {
3357
- accept,
3358
- ...promptText !== undefined && { promptText }
3359
- });
3360
- } else {
3361
- await this.cdp("Page.handleJavaScriptDialog", { accept: false });
3362
- }
3363
- });
3364
- });
3365
- }
3366
- async tabs() {
3367
- const url = this.wc.getURL();
3368
- const title = this.wc.getTitle();
3369
- return [{ tabId: "0", url, title }];
3370
- }
3371
- async switchTab(_tabId) {}
3372
- async newTab(url) {
3373
- if (url) {
3374
- await this.navigate(url);
3375
- }
3376
- return {
3377
- tabId: "0",
3378
- url: this.wc.getURL(),
3379
- title: this.wc.getTitle()
3380
- };
3381
- }
3382
- async closeTab(_tabId) {
3383
- await this.disconnect();
3384
- }
3385
- async waitForNavigation(options = {}) {
3386
- const timeout = options.timeout ?? 30000;
3387
- return new Promise((resolve, reject) => {
3388
- const timer = setTimeout(() => {
3389
- reject(new Error("ElectronBackend: waitForNavigation timed out"));
3390
- }, timeout);
3391
- this.wc.once("did-finish-load", () => {
3392
- clearTimeout(timer);
3393
- resolve();
3394
- });
3395
- });
3396
- }
3397
- async waitForSelector(selector, options = {}) {
3398
- const timeout = options.timeout ?? 30000;
3399
- const interval = 100;
3400
- const deadline = Date.now() + timeout;
3401
- while (Date.now() < deadline) {
3402
- const found = await this.wc.executeJavaScript(`!!document.querySelector(${JSON.stringify(selector)})`);
3403
- if (found) {
3404
- const ref = await this.querySelector(selector);
3405
- if (ref)
3406
- return ref;
3407
- }
3408
- await sleep2(interval);
3409
- }
3410
- throw new Error(`ElectronBackend: waitForSelector timed out for "${selector}"`);
3411
- }
3412
- async waitForIdle(options = {}) {
3413
- const timeout = options.timeout ?? 30000;
3414
- const interval = 100;
3415
- const deadline = Date.now() + timeout;
3416
- while (Date.now() < deadline) {
3417
- const ready = await this.wc.executeJavaScript(`document.readyState === "complete"`);
3418
- if (ready)
3419
- return;
3420
- await sleep2(interval);
3421
- }
3422
- throw new Error("ElectronBackend: waitForIdle timed out");
3423
- }
3424
- networkRequests = (_filter) => {
3425
- return Promise.resolve([]);
3426
- };
3427
- consoleMessages = () => {
3428
- return Promise.resolve([]);
3429
- };
3430
- }
3431
- export {
3432
- registerBrowserDeps,
3433
- getBrowserDeps,
3434
- PlaywrightBackend,
3435
- ElectronBackend,
3436
- CDPBrowserBackend,
3437
- BunWebViewBackend,
3438
- BrowserWaitTask,
3439
- BrowserUploadTask,
3440
- BrowserTypeTask,
3441
- BrowserSwitchTabTask,
3442
- BrowserSnapshotTask,
3443
- BrowserSessionTask,
3444
- BrowserSessionRegistry,
3445
- BrowserSelectTask,
3446
- BrowserScrollTask,
3447
- BrowserScreenshotTask,
3448
- BrowserReloadTask,
3449
- BrowserQuerySelectorTask,
3450
- BrowserPressKeyTask,
3451
- BrowserNewTabTask,
3452
- BrowserNavigateTask,
3453
- BrowserLoginTask,
3454
- BrowserHoverTask,
3455
- BrowserForwardTask,
3456
- BrowserFillTask,
3457
- BrowserExtractTextTask,
3458
- BrowserExtractHtmlTask,
3459
- BrowserEvaluateTask,
3460
- BrowserCloseTask,
3461
- BrowserCloseTabTask,
3462
- BrowserClickTask,
3463
- BrowserBackTask,
3464
- BrowserAttributeTask,
3465
- BROWSER_CONTROL_TASK_DEPS
3466
- };
3467
-
3468
- //# debugId=F0E5F00740A84F2364756E2164756E21