@workglow/browser-control 0.2.28

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