@usecrow/client 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,28 @@
1
+ declare interface ActionResult {
2
+ success: boolean;
3
+ message: string;
4
+ }
5
+
1
6
  export declare interface ActiveWorkflow {
2
7
  name: string;
3
8
  todos: WorkflowTodo[];
4
9
  isComplete?: boolean;
5
10
  }
6
11
 
12
+ /**
13
+ * Structured browser state for LLM consumption
14
+ */
15
+ declare interface BrowserState {
16
+ url: string;
17
+ title: string;
18
+ /** Page info + scroll position hint (e.g. "Page info: 1920x1080px...\n[Start of page]") */
19
+ header: string;
20
+ /** Simplified HTML of interactive elements */
21
+ content: string;
22
+ /** Page footer hint (e.g. "... 300 pixels below ..." or "[End of page]") */
23
+ footer: string;
24
+ }
25
+
7
26
  declare interface BrowserUseConfig {
8
27
  productId: string;
9
28
  apiUrl: string;
@@ -20,6 +39,8 @@ export declare interface Citation {
20
39
  page?: number;
21
40
  }
22
41
 
42
+ declare function cleanUpHighlights(): void;
43
+
23
44
  export declare interface ContextData {
24
45
  /** Current page URL or path */
25
46
  page?: string;
@@ -317,6 +338,87 @@ export declare const DEFAULT_TOOLS: ToolHandlers;
317
338
 
318
339
  export declare type DefaultToolName = keyof typeof DEFAULT_TOOLS;
319
340
 
341
+ declare namespace dom {
342
+ export {
343
+ getFlatTree,
344
+ flatTreeToString,
345
+ getSelectorMap,
346
+ getElementTextMap,
347
+ cleanUpHighlights,
348
+ DomConfig,
349
+ getAllTextTillNextClickableElement
350
+ }
351
+ }
352
+
353
+ declare interface DomConfig {
354
+ interactiveBlacklist?: (Element | (() => Element))[];
355
+ interactiveWhitelist?: (Element | (() => Element))[];
356
+ include_attributes?: string[];
357
+ highlightOpacity?: number;
358
+ highlightLabelOpacity?: number;
359
+ }
360
+
361
+ declare type DomNode = TextDomNode | ElementDomNode | InteractiveElementDomNode;
362
+
363
+ declare interface ElementDomNode {
364
+ tagName: string;
365
+ attributes?: Record<string, string>;
366
+ xpath?: string;
367
+ children?: string[];
368
+ isVisible?: boolean;
369
+ isTopElement?: boolean;
370
+ isInViewport?: boolean;
371
+ isNew?: boolean;
372
+ isInteractive?: false;
373
+ highlightIndex?: number;
374
+ extra?: Record<string, any>;
375
+ [key: string]: unknown;
376
+ }
377
+
378
+ /**
379
+ * Derived from @page-agent/page-controller
380
+ * Original: https://github.com/alibaba/page-agent
381
+ * Copyright (c) 2025 Alibaba Group Holding Limited
382
+ * Licensed under MIT License
383
+ */
384
+ declare interface FlatDomTree {
385
+ rootId: string;
386
+ map: Record<string, DomNode>;
387
+ }
388
+
389
+ /**
390
+ * 对应 python 中的 views::clickable_elements_to_string,
391
+ * 将 dom 信息处理成适合 llm 阅读的文本格式
392
+ * @形如
393
+ * ``` text
394
+ * [0]<a aria-label=page-agent.js 首页 />
395
+ * [1]<div >P />
396
+ * [2]<div >page-agent.js
397
+ * UI Agent in your webpage />
398
+ * [3]<a >文档 />
399
+ * [4]<a aria-label=查看源码(在新窗口打开)>源码 />
400
+ * UI Agent in your webpage
401
+ * 用户输入需求,AI 理解页面并自动操作。
402
+ * [5]<a role=button>快速开始 />
403
+ * [6]<a role=button>查看文档 />
404
+ * 无需后端
405
+ * ```
406
+ * 其中可交互元素用序号标出,提示llm可以用序号操作。
407
+ * 缩进代表父子关系。
408
+ * 普通文本则直接列出来。
409
+ *
410
+ * @todo 数据脱敏过滤器
411
+ */
412
+ declare function flatTreeToString(flatTree: FlatDomTree, include_attributes?: string[]): string;
413
+
414
+ declare const getAllTextTillNextClickableElement: (node: TreeNode, maxDepth?: number) => string;
415
+
416
+ declare function getElementTextMap(simplifiedHTML: string): Map<number, string>;
417
+
418
+ declare function getFlatTree(config: DomConfig): FlatDomTree;
419
+
420
+ declare function getSelectorMap(flatTree: FlatDomTree): Map<number, InteractiveElementDomNode>;
421
+
320
422
  export declare interface IdentifyOptions {
321
423
  /** JWT token from your backend for user verification */
322
424
  token: string;
@@ -372,6 +474,30 @@ export declare interface IdentityState {
372
474
  isVerified: boolean;
373
475
  }
374
476
 
477
+ export declare interface InputSchemaFieldDef {
478
+ name: string;
479
+ type: 'string' | 'number' | 'bool';
480
+ required: boolean;
481
+ default_value?: string;
482
+ }
483
+
484
+ declare interface InteractiveElementDomNode {
485
+ tagName: string;
486
+ attributes?: Record<string, string>;
487
+ xpath?: string;
488
+ children?: string[];
489
+ isVisible?: boolean;
490
+ isTopElement?: boolean;
491
+ isInViewport?: boolean;
492
+ isInteractive: true;
493
+ highlightIndex: number;
494
+ /**
495
+ * 可交互元素的 dom 引用
496
+ */
497
+ ref: HTMLElement;
498
+ [key: string]: unknown;
499
+ }
500
+
375
501
  export declare interface Message {
376
502
  id: string;
377
503
  content: string;
@@ -392,6 +518,193 @@ export declare interface NavigationRoute {
392
518
  params?: Record<string, string>;
393
519
  }
394
520
 
521
+ /**
522
+ * PageController manages DOM state and element interactions.
523
+ * It provides async methods for all DOM operations, keeping state isolated.
524
+ *
525
+ * @lifecycle
526
+ * - beforeUpdate: Emitted before the DOM tree is updated.
527
+ * - afterUpdate: Emitted after the DOM tree is updated.
528
+ */
529
+ declare class PageController extends EventTarget {
530
+ private config;
531
+ /** Corresponds to eval_page in browser-use */
532
+ private flatTree;
533
+ /**
534
+ * All highlighted index-mapped interactive elements
535
+ * Corresponds to DOMState.selector_map in browser-use
536
+ */
537
+ private selectorMap;
538
+ /** Index -> element text description mapping */
539
+ private elementTextMap;
540
+ /**
541
+ * Simplified HTML for LLM consumption.
542
+ * Corresponds to clickable_elements_to_string in browser-use
543
+ */
544
+ private simplifiedHTML;
545
+ /** last time the tree was updated */
546
+ private lastTimeUpdate;
547
+ /** Whether the tree has been indexed at least once */
548
+ private isIndexed;
549
+ /** Visual mask overlay for blocking user interaction during automation */
550
+ private mask;
551
+ private maskReady;
552
+ constructor(config?: PageControllerConfig);
553
+ /**
554
+ * Initialize mask asynchronously (dynamic import to avoid CSS loading in Node)
555
+ */
556
+ initMask(): void;
557
+ /**
558
+ * Get current page URL
559
+ */
560
+ getCurrentUrl(): Promise<string>;
561
+ /**
562
+ * Get last tree update timestamp
563
+ */
564
+ getLastUpdateTime(): Promise<number>;
565
+ /**
566
+ * Get structured browser state for LLM consumption.
567
+ * Automatically calls updateTree() to refresh the DOM state.
568
+ */
569
+ getBrowserState(): Promise<BrowserState>;
570
+ /**
571
+ * Update DOM tree, returns simplified HTML for LLM.
572
+ * This is the main method to refresh the page state.
573
+ * Automatically bypasses mask during DOM extraction if enabled.
574
+ */
575
+ updateTree(): Promise<string>;
576
+ /**
577
+ * Clean up all element highlights
578
+ */
579
+ cleanUpHighlights(): Promise<void>;
580
+ /**
581
+ * Ensure the tree has been indexed before any index-based operation.
582
+ * Throws if updateTree() hasn't been called yet.
583
+ */
584
+ private assertIndexed;
585
+ /**
586
+ * Click element by index
587
+ */
588
+ clickElement(index: number): Promise<ActionResult>;
589
+ /**
590
+ * Input text into element by index
591
+ */
592
+ inputText(index: number, text: string): Promise<ActionResult>;
593
+ /**
594
+ * Select dropdown option by index and option text
595
+ */
596
+ selectOption(index: number, optionText: string): Promise<ActionResult>;
597
+ /**
598
+ * Scroll vertically
599
+ */
600
+ scroll(options: {
601
+ down: boolean;
602
+ numPages: number;
603
+ pixels?: number;
604
+ index?: number;
605
+ }): Promise<ActionResult>;
606
+ /**
607
+ * Scroll horizontally
608
+ */
609
+ scrollHorizontally(options: {
610
+ right: boolean;
611
+ pixels: number;
612
+ index?: number;
613
+ }): Promise<ActionResult>;
614
+ /**
615
+ * Execute arbitrary JavaScript on the page
616
+ */
617
+ executeJavascript(script: string): Promise<ActionResult>;
618
+ /**
619
+ * Find an interactive element index by visible text (exact match).
620
+ * Scans the elementTextMap for a match.
621
+ */
622
+ findElementByText(text: string, exact?: boolean): number | null;
623
+ /**
624
+ * Find an interactive element index by CSS selector.
625
+ * Queries the DOM, then reverse-looks up in selectorMap.
626
+ */
627
+ findElementBySelector(cssSelector: string): number | null;
628
+ /**
629
+ * Find an interactive element index by XPath.
630
+ * Evaluates the XPath, then reverse-looks up in selectorMap.
631
+ */
632
+ findElementByXPath(xpath: string): number | null;
633
+ /**
634
+ * Find an interactive element index by aria-label attribute.
635
+ */
636
+ findElementByAriaLabel(label: string): number | null;
637
+ /**
638
+ * Find an interactive element index by placeholder text.
639
+ */
640
+ findElementByPlaceholder(placeholder: string): number | null;
641
+ /**
642
+ * Multi-strategy element finder. Tries strategies in priority order.
643
+ * Returns the index of the first match, or null if nothing found.
644
+ */
645
+ findElementByStrategies(strategies: Array<{
646
+ type: 'text_exact' | 'text_contains' | 'aria_label' | 'placeholder' | 'css' | 'xpath';
647
+ value: string;
648
+ priority: number;
649
+ }>): number | null;
650
+ /**
651
+ * Press a key on an element (or the active element if no index provided).
652
+ */
653
+ pressKey(key: string, index?: number): Promise<ActionResult>;
654
+ /**
655
+ * Navigate to a URL. Restricts to same-origin http(s) URLs for security.
656
+ */
657
+ navigateToUrl(url: string): Promise<ActionResult>;
658
+ /**
659
+ * Go back in browser history.
660
+ */
661
+ goBack(): Promise<ActionResult>;
662
+ /**
663
+ * Go forward in browser history.
664
+ */
665
+ goForward(): Promise<ActionResult>;
666
+ /**
667
+ * Wait until an element matching the given strategies appears in the DOM,
668
+ * or until the timeout is reached. Refreshes the DOM tree on each poll.
669
+ */
670
+ waitForElement(strategies: Array<{
671
+ type: 'text_exact' | 'text_contains' | 'aria_label' | 'placeholder' | 'css' | 'xpath';
672
+ value: string;
673
+ priority: number;
674
+ }>, timeoutMs?: number, pollIntervalMs?: number): Promise<number | null>;
675
+ /**
676
+ * Get the current selectorMap (for external use like WorkflowExecutor).
677
+ */
678
+ getSelectorMap(): Map<number, InteractiveElementDomNode>;
679
+ /**
680
+ * Get the current elementTextMap (for external use like WorkflowExecutor).
681
+ */
682
+ getElementTextMap(): Map<number, string>;
683
+ /**
684
+ * Show the visual mask overlay.
685
+ * Only works after mask is setup.
686
+ */
687
+ showMask(): Promise<void>;
688
+ /**
689
+ * Hide the visual mask overlay.
690
+ * Only works after mask is setup.
691
+ */
692
+ hideMask(): Promise<void>;
693
+ /**
694
+ * Dispose and clean up resources
695
+ */
696
+ dispose(): void;
697
+ }
698
+
699
+ /**
700
+ * Configuration for PageController
701
+ */
702
+ declare interface PageControllerConfig extends dom.DomConfig {
703
+ viewportExpansion?: number;
704
+ /** Enable visual mask overlay during operations (default: false) */
705
+ enableMask?: boolean;
706
+ }
707
+
395
708
  /**
396
709
  * Parse SSE chunk into lines and extract data
397
710
  */
@@ -402,6 +715,26 @@ export declare function parseSSEChunk(chunk: string): Generator<string>;
402
715
  */
403
716
  export declare function parseSSEData(data: string): StreamEvent | null;
404
717
 
718
+ export declare interface RecordedStepDef {
719
+ type: 'click' | 'input' | 'select' | 'keypress' | 'navigation';
720
+ description: string;
721
+ target_text: string;
722
+ selector_strategies: SelectorStrategy[];
723
+ value?: string;
724
+ url?: string;
725
+ element_tag: string;
726
+ container_hint?: string;
727
+ }
728
+
729
+ export declare interface RecordedWorkflowDef {
730
+ id: string;
731
+ name: string;
732
+ description?: string;
733
+ steps: RecordedStepDef[];
734
+ input_schema: InputSchemaFieldDef[];
735
+ enabled: boolean;
736
+ }
737
+
405
738
  /**
406
739
  * Resolve a route template by interpolating :param placeholders with actual values.
407
740
  *
@@ -411,6 +744,21 @@ export declare function parseSSEData(data: string): StreamEvent | null;
411
744
  */
412
745
  export declare function resolveRoute(routes: NavigationRoute[], pageName: string, params?: Record<string, unknown>): string | null;
413
746
 
747
+ export declare interface SelectorStrategy {
748
+ type: 'text_exact' | 'text_contains' | 'aria_label' | 'placeholder' | 'css' | 'xpath';
749
+ value: string;
750
+ priority: number;
751
+ }
752
+
753
+ export declare interface StepResult {
754
+ step_index: number;
755
+ step_type: string;
756
+ description: string;
757
+ success: boolean;
758
+ message: string;
759
+ element_index?: number;
760
+ }
761
+
414
762
  export declare type StreamEvent = {
415
763
  type: 'content';
416
764
  text: string;
@@ -481,6 +829,13 @@ export declare type StreamEvent = {
481
829
  */
482
830
  export declare function streamResponse(response: Response, signal?: AbortSignal): AsyncGenerator<StreamEvent>;
483
831
 
832
+ declare interface TextDomNode {
833
+ type: 'TEXT_NODE';
834
+ text: string;
835
+ isVisible: boolean;
836
+ [key: string]: unknown;
837
+ }
838
+
484
839
  export declare type ToolHandler = (args: Record<string, unknown>) => Promise<ToolResult> | ToolResult;
485
840
 
486
841
  export declare type ToolHandlers = Record<string, ToolHandler>;
@@ -515,6 +870,66 @@ export declare interface ToolResult {
515
870
  error?: string;
516
871
  }
517
872
 
873
+ /**
874
+ * elementsToString 内部使用的类型
875
+ */
876
+ declare interface TreeNode {
877
+ type: 'text' | 'element';
878
+ parent: TreeNode | null;
879
+ children: TreeNode[];
880
+ isVisible: boolean;
881
+ text?: string;
882
+ tagName?: string;
883
+ attributes?: Record<string, string>;
884
+ isInteractive?: boolean;
885
+ isTopElement?: boolean;
886
+ isNew?: boolean;
887
+ highlightIndex?: number;
888
+ extra?: Record<string, any>;
889
+ }
890
+
891
+ export declare class WorkflowExecutor {
892
+ private controller;
893
+ private config;
894
+ constructor(controller: PageController, config?: WorkflowExecutorConfig);
895
+ /**
896
+ * Execute a recorded workflow with variable substitution.
897
+ */
898
+ execute(workflow: RecordedWorkflowDef, inputs?: Record<string, string | number | boolean>): Promise<WorkflowResult>;
899
+ /**
900
+ * Execute a single step.
901
+ */
902
+ private executeStep;
903
+ /**
904
+ * Replace {variable_name} placeholders in step fields with actual values.
905
+ */
906
+ private resolveVariables;
907
+ private delay;
908
+ }
909
+
910
+ export declare interface WorkflowExecutorConfig {
911
+ /** Time to wait for elements to appear after actions (ms). Default: 3000 */
912
+ waitTimeout?: number;
913
+ /** Time between DOM polls when waiting for elements (ms). Default: 300 */
914
+ pollInterval?: number;
915
+ /** Default delay between steps (ms). Default: 500 */
916
+ stepDelay?: number;
917
+ /** Stop execution on first failure. Default: false */
918
+ stopOnFailure?: boolean;
919
+ /** Callback for step progress updates */
920
+ onStepProgress?: (result: StepResult) => void;
921
+ }
922
+
923
+ export declare interface WorkflowResult {
924
+ success: boolean;
925
+ workflow_name: string;
926
+ total_steps: number;
927
+ completed_steps: number;
928
+ failed_steps: number;
929
+ step_results: StepResult[];
930
+ error?: string;
931
+ }
932
+
518
933
  export declare interface WorkflowTodo {
519
934
  id: string;
520
935
  text: string;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as R } from "./browserUse-Btg7osSj.js";
1
+ import { C as R, W as D } from "./workflowExecutor-DgghvBIA.js";
2
2
  class _ {
3
3
  constructor() {
4
4
  this.state = {
@@ -419,7 +419,7 @@ const w = {
419
419
  refreshPage: S,
420
420
  whatsOnScreen: T
421
421
  }, $ = Object.keys(w), E = "https://api.usecrow.org", L = "claude-sonnet-4-20250514";
422
- class x {
422
+ class U {
423
423
  constructor(e) {
424
424
  this.context = {}, this.abortController = null, this.callbacks = {}, this._messages = [], this.messageListeners = /* @__PURE__ */ new Set(), this._isLoading = !1, this.loadingListeners = /* @__PURE__ */ new Set(), this.config = {
425
425
  productId: e.productId,
@@ -747,7 +747,7 @@ class x {
747
747
  this.stop(), this.messageListeners.clear(), this.loadingListeners.clear();
748
748
  }
749
749
  }
750
- function U(o, e, t) {
750
+ function x(o, e, t) {
751
751
  const s = o.find(
752
752
  (n) => n.name.toLowerCase() === e.toLowerCase()
753
753
  );
@@ -764,7 +764,7 @@ function O(o, e) {
764
764
  const s = t.page, r = t.params, n = t.url;
765
765
  let i = null;
766
766
  if (s) {
767
- if (i = U(o, s, r), !i)
767
+ if (i = x(o, s, r), !i)
768
768
  return {
769
769
  status: "error",
770
770
  error: `Unknown page: "${s}". Available pages: ${o.map((h) => h.name).join(", ")}`
@@ -806,14 +806,15 @@ function O(o, e) {
806
806
  export {
807
807
  C as ConversationManager,
808
808
  R as CrowBrowserUse,
809
- x as CrowClient,
809
+ U as CrowClient,
810
810
  w as DEFAULT_TOOLS,
811
811
  $ as DEFAULT_TOOL_NAMES,
812
812
  _ as IdentityManager,
813
813
  k as ToolManager,
814
+ D as WorkflowExecutor,
814
815
  O as createNavigateToPageTool,
815
816
  I as parseSSEChunk,
816
817
  b as parseSSEData,
817
- U as resolveRoute,
818
+ x as resolveRoute,
818
819
  M as streamResponse
819
820
  };
@@ -0,0 +1 @@
1
+ "use strict";let m=null,f=null;function P(g){m=g}async function E(){if(m)return m;if(!f)try{f=await Promise.resolve().then(()=>require("./PageController-Cu6KUkcn.cjs"))}catch{throw new Error('PageController not available. Either import from "@usecrow/client/browser" or use the bundled version.')}return f.PageController}class ${constructor(e){this.pageController=null,this.sessionId=null,this.maxSteps=20,this.aborted=!1,this.config=e}async initPageController(){if(this.pageController)return this.pageController;try{const e=await E();this.pageController=new e({enableMask:!0,viewportExpansion:500,highlightLabelOpacity:0,highlightOpacity:0}),await this.pageController.showMask();const o=this.pageController.mask;return o!=null&&o.wrapper&&(o.wrapper.style.pointerEvents="none"),console.log("[CrowBrowserUse] PageController initialized with non-blocking pointer"),this.pageController}catch(e){throw console.error("[CrowBrowserUse] Failed to initialize PageController:",e),new Error("Failed to initialize browser automation. Please import from @usecrow/client/browser.")}}async execute(e){var o,s,t,r,n,h,u,l,x,y,C,_,S,U;if(console.log("[CrowBrowserUse] Starting task:",e),this.config.onConfirmation&&!await this.config.onConfirmation(e))return console.log("[CrowBrowserUse] User declined browser automation"),(s=(o=this.config).onProgress)==null||s.call(o,-1,this.maxSteps),{status:"error",error:"User declined browser automation",data:{declined:!0}};try{const a=await this.initPageController(),b=await this.startSession(e);this.sessionId=b.session_id,this.maxSteps=b.max_steps,console.log("[CrowBrowserUse] Session started:",this.sessionId);let p=0,c;for(;p<this.maxSteps;){if(this.aborted)return console.log("[CrowBrowserUse] Task cancelled by user"),await this.cleanup(),(r=(t=this.config).onProgress)==null||r.call(t,-1,this.maxSteps),{status:"error",error:"Task cancelled by user"};p++;const v=await a.getBrowserState(),d=a.mask;d!=null&&d.wrapper&&(d.wrapper.style.pointerEvents="none");const i=await this.processStep(v,c);if(i.needs_user_input&&i.question){if(console.log("[CrowBrowserUse] Asking user:",i.question),!this.config.onQuestion){c="User input not available - no callback provided",console.warn("[CrowBrowserUse] No onQuestion callback provided");continue}try{const w=await this.config.onQuestion(i.question);c=`User answered: ${w}`,console.log("[CrowBrowserUse] User answered:",w)}catch(w){if(c="User cancelled or failed to respond",console.log("[CrowBrowserUse] User cancelled or error:",w),this.aborted)return console.log("[CrowBrowserUse] Aborted after user cancelled"),await this.cleanup(),(h=(n=this.config).onProgress)==null||h.call(n,-1,this.maxSteps),{status:"error",error:"Task cancelled by user"}}continue}if(i.done)return console.log("[CrowBrowserUse] Task completed:",i.message),await this.cleanup(),(l=(u=this.config).onProgress)==null||l.call(u,p,this.maxSteps),{status:i.success?"success":"error",data:{message:i.message,steps:p},error:i.success?void 0:i.message};if(i.error)return console.error("[CrowBrowserUse] Error:",i.error),await this.cleanup(),(y=(x=this.config).onProgress)==null||y.call(x,-1,this.maxSteps),{status:"error",error:i.error};i.action&&(c=await this.executeAction(a,i.action),console.log(`[CrowBrowserUse] Step ${p}:`,c)),i.reflection&&console.log("[CrowBrowserUse] Reflection:",i.reflection.next_goal)}return await this.cleanup(),(_=(C=this.config).onProgress)==null||_.call(C,-1,this.maxSteps),{status:"error",error:`Task incomplete after ${this.maxSteps} steps`}}catch(a){return console.error("[CrowBrowserUse] Error:",a),await this.cleanup(),(U=(S=this.config).onProgress)==null||U.call(S,-1,this.maxSteps),{status:"error",error:a instanceof Error?a.message:String(a)}}}async startSession(e){const o=await fetch(`${this.config.apiUrl}/api/browser-use/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({product_id:this.config.productId,task:e})});if(!o.ok){const s=await o.json().catch(()=>({detail:"Unknown error"}));throw new Error(s.detail||`Failed to start session: ${o.status}`)}return o.json()}async processStep(e,o){const s=await fetch(`${this.config.apiUrl}/api/browser-use/step`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:this.sessionId,product_id:this.config.productId,browser_state:e,action_result:o})});if(!s.ok){const t=await s.json().catch(()=>({detail:"Unknown error"}));throw new Error(t.detail||`Failed to process step: ${s.status}`)}return s.json()}async executeAction(e,o){const s=Object.keys(o)[0],t=o[s];try{switch(s){case"click_element_by_index":return(await e.clickElement(t.index)).message;case"input_text":return(await e.inputText(t.index,t.text)).message;case"select_dropdown_option":return(await e.selectOption(t.index,t.text)).message;case"scroll":return(await e.scroll({down:t.down,numPages:t.num_pages,pixels:t.pixels,index:t.index})).message;case"scroll_horizontally":return(await e.scrollHorizontally({right:t.right,pixels:t.pixels,index:t.index})).message;case"wait":{const r=t.seconds||1;return await new Promise(n=>setTimeout(n,r*1e3)),`Waited ${r} seconds`}case"done":return"Task completed";default:return`Unknown action: ${s}`}}catch(r){return`Action failed: ${r instanceof Error?r.message:String(r)}`}}async cleanup(){if(this.pageController){try{await this.pageController.hideMask(),await this.pageController.cleanUpHighlights(),this.pageController.dispose()}catch(e){console.warn("[CrowBrowserUse] Cleanup error:",e)}this.pageController=null}if(this.sessionId){try{await fetch(`${this.config.apiUrl}/api/browser-use/end`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({session_id:this.sessionId,product_id:this.config.productId})})}catch{}this.sessionId=null}}async stop(){this.aborted=!0,await this.cleanup()}}class k{constructor(e,o={}){this.controller=e,this.config={waitTimeout:o.waitTimeout??3e3,pollInterval:o.pollInterval??300,stepDelay:o.stepDelay??500,stopOnFailure:o.stopOnFailure??!1,onStepProgress:o.onStepProgress??(()=>{})}}async execute(e,o={}){const s=[];let t=0;console.log(`[WorkflowExecutor] Starting workflow: ${e.name} (${e.steps.length} steps)`);for(let n=0;n<e.steps.length;n++){const h=e.steps[n],u=this.resolveVariables(h,o);console.log(`[WorkflowExecutor] Step ${n+1}/${e.steps.length}: ${u.description}`);const l=await this.executeStep(u,n);if(s.push(l),this.config.onStepProgress(l),!l.success&&(t++,console.warn(`[WorkflowExecutor] Step ${n+1} failed: ${l.message}`),this.config.stopOnFailure))return{success:!1,workflow_name:e.name,total_steps:e.steps.length,completed_steps:n+1,failed_steps:t,step_results:s,error:`Stopped at step ${n+1}: ${l.message}`};n<e.steps.length-1&&await this.delay(this.config.stepDelay)}const r=t===0;return console.log(`[WorkflowExecutor] Workflow "${e.name}" ${r?"completed successfully":`completed with ${t} failures`}`),{success:r,workflow_name:e.name,total_steps:e.steps.length,completed_steps:e.steps.length,failed_steps:t,step_results:s}}async executeStep(e,o){const s={step_index:o,step_type:e.type,description:e.description,success:!1,message:""};try{if(e.type==="navigation"){if(!e.url)return{...s,message:"Navigation step has no URL"};const r=await this.controller.navigateToUrl(e.url);return await this.delay(1e3),{...s,success:r.success,message:r.message}}await this.controller.updateTree();let t=this.controller.findElementByStrategies(e.selector_strategies);if(t===null&&e.selector_strategies.length>0&&(console.log(`[WorkflowExecutor] Element not found, waiting up to ${this.config.waitTimeout}ms...`),t=await this.controller.waitForElement(e.selector_strategies,this.config.waitTimeout,this.config.pollInterval)),t===null&&e.target_text&&(await this.controller.updateTree(),t=this.controller.findElementByText(e.target_text,!0),t===null&&(t=this.controller.findElementByText(e.target_text,!1))),t===null)return{...s,message:`Element not found for step: ${e.description} (target_text: "${e.target_text}")`};switch(s.element_index=t,e.type){case"click":{const r=await this.controller.clickElement(t);return{...s,success:r.success,message:r.message}}case"input":{if(e.value===void 0||e.value===null)return{...s,message:"Input step has no value"};const r=await this.controller.inputText(t,e.value);return{...s,success:r.success,message:r.message}}case"select":{if(!e.value)return{...s,message:"Select step has no value"};const r=await this.controller.selectOption(t,e.value);return{...s,success:r.success,message:r.message}}case"keypress":{if(!e.value)return{...s,message:"Keypress step has no key"};const r=await this.controller.pressKey(e.value,t);return{...s,success:r.success,message:r.message}}default:return{...s,message:`Unknown step type: ${e.type}`}}}catch(t){return{...s,message:`Error executing step: ${t instanceof Error?t.message:String(t)}`}}}resolveVariables(e,o){const s=t=>!t||!t.includes("{")?t:t.replace(/\{(\w+)\}/g,(r,n)=>n in o?String(o[n]):r);return{...e,description:s(e.description)||e.description,target_text:s(e.target_text)||e.target_text,value:s(e.value),url:s(e.url),selector_strategies:e.selector_strategies.map(t=>({...t,value:s(t.value)||t.value}))}}delay(e){return new Promise(o=>setTimeout(o,e))}}exports.CrowBrowserUse=$;exports.WorkflowExecutor=k;exports.setPageController=P;