Skill: luoqianchenguni-max/easybuy-amazon-agent (version 1.0.0)

# EasyBuy Skillpack This SKILL.md describes the skills included in this pack and how to use them. ## Skills - `order_reader` - `evidence_builder` - `message_drafter` - `form_filler` - `amazon_product_detector` - `amazon_orders_scraper` - `amazon_orders_opener` - `amazon_order_details_fetcher` - `amazon_price_checker` - `amazon_review_scraper` - `amazon_contact_flow` - `message_monitor` - `price_alert_manager` - `case_exporter` ## Registry - `dist/skills/registry.json` lists all skills and their JSON files. ## Usage - Load the MV3 extension from `dist/` (Chrome -> Extensions -> Developer Mode -> Load unpacked). - Skills are invoked via the agent runtime using tool name `skill.<skill_name>`. - Each skill defines `allowedTools`, `input_schema`, and `output_schema` in its JSON file. ## Playbooks - Playbooks in `dist/playbooks/` declare intent-only steps and `requires_skills`. ## Requirements - Chrome (MV3-capable) - Amazon login for live flows - Permissions: `tabs`, `scripting`, `storage`, `downloads`, `sidePanel`, `activeTab`

path: manifest.json
content: {
  "manifest_version": 3,
  "name": "EasyBuy Agent Demo",
  "version": "0.1.0",
  "description": "MV3 Agent demo: panel -> SW -> CS -> tool -> result -> panel",
  "permissions": ["tabs", "scripting", "storage", "downloads", "sidePanel", "activeTab"],
  "host_permissions": ["<all_urls>", "https://api.openai.com/*"],
  "background": {
    "service_worker": "background/sw.js"
  },
  "side_panel": {
    "default_path": "panel/index.html"
  },
  "content_scripts": [
    {
      "matches": ["https://*.amazon.com/*"],
      "js": ["content/index.js"]
    }
  ],
  "action": {
    "default_title": "EasyBuy Agent Demo"
  }
}


path: skills/amazon_contact_flow.json
content: {
  "name": "amazon_contact_flow",
  "version": "1.1.0",
  "description": "Opens order detail page, navigates Amazon contact flow, and types a message for after-sales requests.",
  "allowedTools": [
    "browser.click",
    "browser.click_text",
    "browser.exec",
    "browser.type",
    "browser.type_message",
    "browser.wait",
    "browser.query",
    "browser.scroll"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string"
      },
      "details_url": {
        "type": "string"
      },
      "message": {
        "type": "string"
      },
      "auto_send": {
        "type": "boolean"
      },
      "origin": {
        "type": "string"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "detailUrl": {
        "type": "string"
      },
      "contactUrl": {
        "type": "string"
      },
      "messagingUrl": {
        "type": "string"
      },
      "conversationUrl": {
        "type": "string"
      },
      "detailTabId": {
        "type": "number"
      },
      "messagingTabId": {
        "type": "number"
      }
    }
  },
  "notes": "In production this skill is orchestrated by background.js (EB_AI_AGENT_ORDER) using the injected contact flow + messaging tab detection. Prefer details_url when available; order_id is used to build the details URL.",
  "examples": [
    {
      "input": {
        "order_id": "114-1234567-1234567",
        "details_url": "https://www.amazon.com/your-orders/order-details?orderID=114-1234567-1234567",
        "message": "I noticed a price drop after purchase and request a refund for the difference.",
        "auto_send": false
      },
      "output": {
        "detailUrl": "https://www.amazon.com/your-orders/order-details?orderID=114-1234567-1234567",
        "contactUrl": "https://www.amazon.com/gp/help/contact/contact.html",
        "messagingUrl": "https://www.amazon.com/gp/message?threadId=EXAMPLE",
        "conversationUrl": "https://www.amazon.com/gp/message?threadId=EXAMPLE",
        "detailTabId": 123,
        "messagingTabId": 456
      }
    }
  ],
  "id": "amazon_contact_flow"
}


path: skills/amazon_orders_opener.json
content: {
  "name": "amazon_orders_opener",
  "version": "1.1.0",
  "description": "Navigates to Amazon orders page using current Amazon origin, optionally filtered by year.",
  "allowedTools": [
    "browser.navigate"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "kind": {
        "type": "string",
        "enum": [
          "orders_page"
        ]
      },
      "year": {
        "type": "number"
      },
      "timeFilter": {
        "type": "string"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "url": {
        "type": "string"
      }
    }
  },
  "notes": "Resolves base origin from active or any Amazon tab, then navigates to /your-orders/orders. If year is provided, it uses timeFilter=year-YYYY.",
  "examples": [
    {
      "input": {
        "kind": "orders_page",
        "year": 2025
      },
      "output": {
        "url": "https://www.amazon.com/your-orders/orders?timeFilter=year-2025&ref_=ppx_yo2ov_dt_b_filter_all_y2025"
      }
    }
  ],
  "id": "amazon_orders_opener"
}


path: skills/amazon_orders_scraper.json
content: {
  "name": "amazon_orders_scraper",
  "version": "1.1.0",
  "description": "Extracts order cards from Amazon orders page and can optionally open the first order details link.",
  "allowedTools": [
    "browser.extract",
    "browser.query",
    "browser.get_dom"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "rootSelector": {
        "type": "string"
      },
      "list": {
        "type": "boolean"
      },
      "openFirstDetails": {
        "type": "boolean"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "orders": {
        "type": "array",
        "items": {
          "type": "object"
        }
      }
    }
  },
  "notes": "Use list extraction with order card selectors. Set openFirstDetails=true to click the first order details link after extraction.",
  "examples": [
    {
      "input": {
        "rootSelector": ".order-card",
        "list": true,
        "openFirstDetails": true
      },
      "output": {
        "orders": [
          {
            "order_id": "123-123",
            "title": "Item",
            "price": "$10"
          }
        ]
      }
    }
  ],
  "id": "amazon_orders_scraper"
}


path: skills/amazon_order_details_fetcher.json
content: {
  "name": "amazon_order_details_fetcher",
  "version": "1.0.0",
  "description": "Fetches order detail data like ASIN and buy price.",
  "allowedTools": [
    "browser.get_dom",
    "browser.query"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "detailsUrl": {
        "type": "string"
      }
    },
    "required": [
      "detailsUrl"
    ]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "asin": {
        "type": "string"
      },
      "buy_price": {
        "type": "string"
      },
      "items": {
        "type": "array",
        "items": {
          "type": "object"
        }
      }
    }
  },
  "notes": "In production this is implemented by SW fetch against detailsUrl.",
  "id": "amazon_order_details_fetcher"
}


path: skills/amazon_price_checker.json
content: {
  "name": "amazon_price_checker",
  "version": "1.0.0",
  "description": "Checks live price for a given ASIN.",
  "allowedTools": [
    "browser.get_dom",
    "browser.query"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "asin": {
        "type": "string"
      },
      "origin": {
        "type": "string"
      }
    },
    "required": [
      "asin"
    ]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "price": {
        "type": "string"
      }
    }
  },
  "notes": "In production this is implemented by SW fetch to /dp/{asin}.",
  "id": "amazon_price_checker"
}


path: skills/amazon_product_detector.json
content: {
  "name": "amazon_product_detector",
  "version": "1.0.0",
  "description": "Extracts ASIN, title, price, image, rating, review count from Amazon product pages.",
  "allowedTools": [
    "browser.extract",
    "browser.query"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "rootSelector": {
        "type": "string"
      },
      "preferSelectors": {
        "type": "object"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "asin": {
        "type": "string"
      },
      "title": {
        "type": "string"
      },
      "price": {
        "type": "string"
      },
      "image_url": {
        "type": "string"
      },
      "rating": {
        "type": "string"
      },
      "review_count": {
        "type": "string"
      }
    }
  },
  "notes": "Prefer #productTitle for title and #ASIN for asin when available.",
  "examples": [
    {
      "input": {
        "rootSelector": "body"
      },
      "output": {
        "asin": "B000000000",
        "title": "Sample Product",
        "price": "$12.99"
      }
    }
  ],
  "id": "amazon_product_detector"
}


path: skills/amazon_review_scraper.json
content: {
  "name": "amazon_review_scraper",
  "version": "1.0.0",
  "description": "Scrapes reviews from Amazon review pages with pagination.",
  "allowedTools": [
    "browser.query",
    "browser.click",
    "browser.wait",
    "browser.scroll"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "maxPages": {
        "type": "number"
      },
      "reviewSelector": {
        "type": "string"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "reviews": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    }
  },
  "id": "amazon_review_scraper"
}


path: skills/case_exporter.json
content: {
  "name": "case_exporter",
  "version": "1.0.0",
  "description": "Exports evidence package including screenshots and DOM snapshots.",
  "allowedTools": [
    "browser.get_dom",
    "browser.screenshot"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "selectors": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "includeDom": {
        "type": "boolean"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "artifactIds": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    }
  },
  "notes": "Artifacts are stored in IndexedDB; download/export handled by SW.",
  "id": "case_exporter"
}


path: skills/evidence_builder.json
content: {
  "name": "evidence_builder",
  "version": "1.0.0",
  "description": "Collects screenshots and DOM snippets as case evidence.",
  "allowedTools": [
    "browser.screenshot",
    "browser.get_dom",
    "browser.query"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "selectors": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "includeDom": {
        "type": "boolean"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "artifactIds": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "domSnippet": {
        "type": "string"
      }
    }
  },
  "notes": "Screenshot tool is composite; rect is returned with artifactId.",
  "examples": [
    {
      "input": {
        "selectors": [
          "#productTitle"
        ],
        "includeDom": true
      },
      "output": {
        "artifactIds": [
          "artifact_123"
        ],
        "domSnippet": "<h1>Title</h1>"
      }
    }
  ],
  "id": "evidence_builder"
}


path: skills/form_filler.json
content: {
  "name": "form_filler",
  "version": "1.0.0",
  "description": "Fills in a web form using provided selectors and values.",
  "allowedTools": [
    "browser.type",
    "browser.click",
    "browser.scroll"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "fields": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "selector": {
              "type": "string"
            },
            "value": {
              "type": "string"
            },
            "clear": {
              "type": "boolean"
            }
          },
          "required": [
            "selector",
            "value"
          ]
        }
      },
      "submitSelector": {
        "type": "string"
      }
    },
    "required": [
      "fields"
    ]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "filled": {
        "type": "boolean"
      }
    }
  },
  "notes": "Requires user confirmation before final submit.",
  "examples": [
    {
      "input": {
        "fields": [
          {
            "selector": "#message",
            "value": "Hello"
          }
        ],
        "submitSelector": "button[type='submit']"
      },
      "output": {
        "filled": true
      }
    }
  ],
  "id": "form_filler"
}


path: skills/message_drafter.json
content: {
  "name": "message_drafter",
  "version": "1.1.0",
  "description": "Drafts an after-sales message (price adjustment/refund) based on order and price context.",
  "allowedTools": [
    "browser.query"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "issue_summary": {
        "type": "string"
      },
      "desired_resolution": {
        "type": "string"
      },
      "order_id": {
        "type": "string"
      },
      "asin": {
        "type": "string"
      },
      "title": {
        "type": "string"
      },
      "buy_price": {
        "type": "string"
      },
      "current_price": {
        "type": "string"
      },
      "observed_date": {
        "type": "string"
      },
      "tone": {
        "type": "string"
      },
      "language": {
        "type": "string"
      }
    },
    "required": [
      "issue_summary"
    ]
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "draft_message": {
        "type": "string"
      },
      "summary": {
        "type": "string"
      }
    }
  },
  "notes": "No direct sending; used before amazon_contact_flow. Include order id, item title, buy price, current price, and observed date when available.",
  "examples": [
    {
      "input": {
        "issue_summary": "Price dropped after purchase",
        "desired_resolution": "Refund the difference",
        "order_id": "114-1234567-1234567",
        "title": "Wireless Headphones",
        "buy_price": "$59.99",
        "current_price": "$49.99",
        "observed_date": "2026-03-04"
      },
      "output": {
        "summary": "Request price adjustment for a recent order.",
        "draft_message": "Dear support, I noticed the price for Wireless Headphones has dropped since my purchase (Order 114-1234567-1234567). I paid $59.99, the current price is $49.99 (observed 2026-03-04). Please refund the difference. Thank you."
      }
    }
  ],
  "id": "message_drafter"
}


path: skills/message_monitor.json
content: {
  "name": "message_monitor",
  "version": "1.1.0",
  "description": "Monitors Amazon messaging threads for new replies and emits updates.",
  "allowedTools": [
    "browser.query",
    "browser.wait"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "threadUrl": {
        "type": "string"
      },
      "threadId": {
        "type": "string"
      },
      "pollIntervalMs": {
        "type": "number"
      },
      "enabled": {
        "type": "boolean"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "threadId": {
        "type": "string"
      },
      "messageId": {
        "type": "string"
      },
      "text": {
        "type": "string"
      },
      "author": {
        "type": "string"
      },
      "direction": {
        "type": "string"
      },
      "url": {
        "type": "string"
      }
    }
  },
  "notes": "In EasyBuy this is implemented in content.js with a MutationObserver; the background SW handles notifications and auto-reply gating. Only run on /messaging/ pages.",
  "examples": [
    {
      "input": {
        "threadUrl": "https://www.amazon.com/gp/message?threadId=ABC123",
        "pollIntervalMs": 1000
      },
      "output": {
        "threadId": "https://www.amazon.com/gp/message?threadId=ABC123",
        "messageId": "seller|10:41 AM|Thanks for reaching out",
        "text": "Thanks for reaching out. We will review your request.",
        "author": "Seller",
        "direction": "inbound",
        "url": "https://www.amazon.com/gp/message?threadId=ABC123"
      }
    }
  ],
  "id": "message_monitor"
}


path: skills/order_reader.json
content: {
  "name": "order_reader",
  "version": "1.0.0",
  "description": "Extracts order summary details from the current page using schema-driven extraction.",
  "allowedTools": [
    "browser.extract",
    "browser.query",
    "browser.get_dom"
  ],
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string"
      },
      "rootSelector": {
        "type": "string"
      },
      "fields": {
        "type": "object",
        "additionalProperties": {
          "type": "string"
        }
      }
    },
    "required": []
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "order_summary": {
        "type": "object"
      }
    }
  },
  "notes": "Resolver should map rootSelector/fields based on current marketplace layout.",
  "examples": [
    {
      "input": {
        "rootSelector": ".order-card",
        "fields": {
          "title": ".item-title",
          "price": ".a-price .a-offscreen"
        }
      },
      "output": {
        "order_summary": {
          "title": "Sample",
          "price": "$12.99"
        }
      }
    }
  ],
  "id": "order_reader"
}


path: skills/price_alert_manager.json
content: {
  "name": "price_alert_manager",
  "version": "1.0.0",
  "description": "Creates monitors and polls price alert events.",
  "allowedTools": [],
  "input_schema": {
    "type": "object",
    "properties": {
      "asin": {
        "type": "string"
      },
      "buy_price": {
        "type": "string"
      },
      "current_price": {
        "type": "string"
      }
    }
  },
  "output_schema": {
    "type": "object",
    "properties": {
      "alert_id": {
        "type": "string"
      }
    }
  },
  "notes": "Uses background polling + notifications; not directly callable by DOM tools.",
  "id": "price_alert_manager"
}


path: skills/registry.json
content: {
  "version": "1.0.0",
  "skills": [
    { "name": "order_reader", "file": "order_reader.json" },
    { "name": "evidence_builder", "file": "evidence_builder.json" },
    { "name": "message_drafter", "file": "message_drafter.json" },
    { "name": "form_filler", "file": "form_filler.json" },
    { "name": "amazon_product_detector", "file": "amazon_product_detector.json" },
    { "name": "amazon_orders_scraper", "file": "amazon_orders_scraper.json" },
    { "name": "amazon_orders_opener", "file": "amazon_orders_opener.json" },
    { "name": "amazon_order_details_fetcher", "file": "amazon_order_details_fetcher.json" },
    { "name": "amazon_price_checker", "file": "amazon_price_checker.json" },
    { "name": "amazon_review_scraper", "file": "amazon_review_scraper.json" },
    { "name": "amazon_contact_flow", "file": "amazon_contact_flow.json" },
    { "name": "message_monitor", "file": "message_monitor.json" },
    { "name": "price_alert_manager", "file": "price_alert_manager.json" },
    { "name": "case_exporter", "file": "case_exporter.json" }
  ]
}


path: playbooks/refund.json
content: {
  "name": "refund_request",
  "description": "Intent-only playbook to request a refund.",
  "steps": [
    "open_order_detail",
    "extract_order_summary",
    "open_message_center",
    "draft_refund_message",
    "fill_message_form",
    "confirm_send"
  ],
  "notes": "Resolver must derive URLs/selectors from current origin.",
  "version": "1.0.0",
  "requires_skills": [
    "amazon_orders_opener",
    "amazon_order_details_fetcher",
    "order_reader",
    "message_drafter",
    "form_filler",
    "amazon_contact_flow"
  ],
  "supported_sites": [
    "amazon.com",
    "amazon.com.au"
  ]
}


path: playbooks/replacement.json
content: {
  "name": "replacement_request",
  "description": "Intent-only playbook to request a replacement.",
  "steps": [
    "open_order_detail",
    "extract_order_summary",
    "open_message_center",
    "draft_replacement_message",
    "fill_message_form",
    "confirm_send"
  ],
  "notes": "Resolver must map steps to current domain and DOM structure.",
  "version": "1.0.0",
  "requires_skills": [
    "amazon_orders_opener",
    "amazon_order_details_fetcher",
    "order_reader",
    "message_drafter",
    "form_filler",
    "amazon_contact_flow"
  ],
  "supported_sites": [
    "amazon.com",
    "amazon.com.au"
  ]
}


path: playbooks/return.json
content: {
  "name": "return_request",
  "description": "Intent-only playbook to initiate a return.",
  "steps": [
    "open_order_detail",
    "extract_order_summary",
    "open_return_flow",
    "select_return_reason",
    "confirm_return"
  ],
  "notes": "Resolver must generate URLs from current marketplace.",
  "version": "1.0.0",
  "requires_skills": [
    "amazon_orders_opener",
    "amazon_order_details_fetcher",
    "order_reader",
    "form_filler"
  ],
  "supported_sites": [
    "amazon.com",
    "amazon.com.au"
  ]
}


path: panel/index.html
content: <!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>EasyBuy Agent Demo</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        margin: 0;
        padding: 16px;
        background: #f6f7fb;
        color: #1b1b1f;
      }
      #actions {
        display: flex;
        gap: 8px;
        margin-bottom: 12px;
      }
      button {
        padding: 8px 12px;
        border: none;
        background: #1d4ed8;
        color: #fff;
        border-radius: 6px;
        cursor: pointer;
      }
      button.secondary {
        background: #6b7280;
      }
      #confirmBar {
        display: none;
        background: #fff3cd;
        border: 1px solid #ffeeba;
        padding: 12px;
        border-radius: 8px;
        margin-bottom: 12px;
      }
      #trace {
        background: #0f172a;
        color: #e2e8f0;
        padding: 12px;
        border-radius: 8px;
        height: 60vh;
        overflow: auto;
        font-size: 12px;
        white-space: pre-wrap;
      }
    </style>
  </head>
  <body>
    <div id="actions">
      <button id="runDemo">Run Demo</button>
    </div>
    <div id="configSection" style="background: #ffffff; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
      <div style="margin-bottom: 6px;"><strong>AI Config</strong></div>
      <div style="display: grid; gap: 6px; font-size: 12px;">
        <label>Endpoint</label>
        <input id="cfgEndpoint" type="text" placeholder="https://api.openai.com/v1/chat/completions" style="padding: 6px;" />
        <label>Model</label>
        <input id="cfgModel" type="text" placeholder="gpt-4o-mini" style="padding: 6px;" />
        <label>API Key</label>
        <input id="cfgApiKey" type="password" placeholder="sk-..." style="padding: 6px;" />
        <button id="saveConfig" class="secondary">Save Config</button>
      </div>
    </div>
    <div id="promptSection" style="background: #ffffff; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
      <div style="margin-bottom: 6px;"><strong>Prompt</strong></div>
      <textarea id="promptInput" rows="3" style="width: 100%; padding: 6px;" placeholder="e.g. Find the main product title and take a screenshot"></textarea>
      <div style="margin-top: 6px;">
        <button id="runAi">Run AI</button>
      </div>
    </div>
    <div id="confirmBar">
      <div><strong>Confirm Tool Call</strong></div>
      <pre id="confirmText"></pre>
      <div id="confirmActions">
        <button id="approve">Approve</button>
        <button id="reject" class="secondary">Reject</button>
      </div>
    </div>
    <div id="previewSection" style="display: none; margin-bottom: 12px; background: #ffffff; border-radius: 8px; padding: 12px;">
      <div style="margin-bottom: 6px;"><strong>Screenshot Preview</strong></div>
      <div id="previewMeta" style="font-size: 12px; color: #6b7280;"></div>
      <img id="previewImg" style="max-width: 100%; border-radius: 6px; margin-top: 8px;" />
    </div>
    <div id="extractSection" style="display: none; margin-bottom: 12px; background: #ffffff; border-radius: 8px; padding: 12px;">
      <div style="margin-bottom: 6px;"><strong>Extract Result</strong></div>
      <pre id="extractText" style="white-space: pre-wrap; font-size: 12px; color: #111827;"></pre>
    </div>
    <div id="trace"></div>
    <script src="panel.js"></script>
  </body>
</html>


path: panel/panel.js
content: "use strict";
(() => {
  // agent_demo/storage/artifacts.ts
  var DB_NAME = "agent_artifacts";
  var DB_VERSION = 1;
  var STORE_NAME = "artifacts";
  function openDb() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, DB_VERSION);
      request.onupgradeneeded = () => {
        const db = request.result;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          db.createObjectStore(STORE_NAME, { keyPath: "id" });
        }
      };
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
  function txDone(tx) {
    return new Promise((resolve, reject) => {
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
      tx.onabort = () => reject(tx.error);
    });
  }
  function requestToPromise(request) {
    return new Promise((resolve, reject) => {
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
  async function getArtifact(id) {
    const db = await openDb();
    const tx = db.transaction(STORE_NAME, "readonly");
    const store = tx.objectStore(STORE_NAME);
    const req = store.get(id);
    const result = await Promise.all([requestToPromise(req), txDone(tx)]);
    return result[0] || null;
  }

  // agent_demo/panel/panel.ts
  var runButton = document.getElementById("runDemo");
  var runAiButton = document.getElementById("runAi");
  var confirmBar = document.getElementById("confirmBar");
  var confirmText = document.getElementById("confirmText");
  var approveButton = document.getElementById("approve");
  var rejectButton = document.getElementById("reject");
  var traceEl = document.getElementById("trace");
  var previewSection = document.getElementById("previewSection");
  var previewImg = document.getElementById("previewImg");
  var previewMeta = document.getElementById("previewMeta");
  var extractSection = document.getElementById("extractSection");
  var extractText = document.getElementById("extractText");
  var cfgEndpoint = document.getElementById("cfgEndpoint");
  var cfgModel = document.getElementById("cfgModel");
  var cfgApiKey = document.getElementById("cfgApiKey");
  var saveConfigButton = document.getElementById("saveConfig");
  var promptInput = document.getElementById("promptInput");
  var pendingConfirm = null;
  var previewUrl = null;
  function appendTrace(entry) {
    const line = JSON.stringify(entry, null, 2);
    traceEl.textContent += `${line}

`;
    traceEl.scrollTop = traceEl.scrollHeight;
  }
  function showConfirm(message) {
    pendingConfirm = { runId: message.runId, callId: message.callId };
    confirmText.textContent = JSON.stringify(message.toolCall, null, 2);
    confirmBar.style.display = "block";
  }
  function hideConfirm() {
    pendingConfirm = null;
    confirmBar.style.display = "none";
  }
  function showExtractResult(data) {
    extractText.textContent = JSON.stringify(data, null, 2);
    extractSection.style.display = "block";
  }
  async function showArtifactPreview(artifactId) {
    const record = await getArtifact(artifactId);
    if (!record) {
      appendTrace({ error: "artifact_not_found", artifactId });
      return;
    }
    if (previewUrl) {
      URL.revokeObjectURL(previewUrl);
    }
    previewUrl = URL.createObjectURL(record.blob);
    previewImg.src = previewUrl;
    previewMeta.textContent = `artifactId: ${artifactId}`;
    previewSection.style.display = "block";
  }
  async function syncPendingConfirm() {
    const all = await chrome.storage.local.get(null);
    const pendingKeys = Object.keys(all).filter((key) => key.startsWith("agent_run:"));
    for (const key of pendingKeys) {
      const state = all[key];
      if (state?.status === "waiting_confirm" && state?.pendingCallId && state?.pendingToolCall) {
        const pendingMessage = {
          type: "agent.request_confirm",
          runId: state.runId,
          callId: state.pendingCallId,
          toolCall: state.pendingToolCall
        };
        appendTrace({ ...pendingMessage, recovered: true });
        showConfirm(pendingMessage);
        return;
      }
    }
  }
  async function loadConfig() {
    const result = await chrome.storage.local.get("agent_config");
    const config = result.agent_config || {};
    cfgEndpoint.value = config.endpoint || "https://api.openai.com/v1/chat/completions";
    cfgModel.value = config.model || "gpt-4o-mini";
    cfgApiKey.value = config.apiKey || "";
  }
  async function saveConfig() {
    const config = {
      endpoint: cfgEndpoint.value.trim(),
      model: cfgModel.value.trim(),
      [REDACTED_SECRET])
    };
    await chrome.storage.local.set({ agent_config: config });
    appendTrace({ type: "config.saved", config: { ...config, [REDACTED_SECRET] ? "***" : "" } });
  }
  runButton.addEventListener("click", async () => {
    const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
    if (!tab?.id) {
      appendTrace({ error: "no_active_tab" });
      return;
    }
    const runId = `run_${Date.now()}`;
    const request = {
      type: "agent.run",
      runId,
      input: "demo",
      target: { tabId: tab.id }
    };
    appendTrace({ type: "agent.run", runId, tabId: tab.id });
    await chrome.runtime.sendMessage(request);
  });
  runAiButton.addEventListener("click", async () => {
    const prompt = promptInput.value.trim();
    if (!prompt) {
      appendTrace({ error: "empty_prompt" });
      return;
    }
    const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
    if (!tab?.id) {
      appendTrace({ error: "no_active_tab" });
      return;
    }
    const runId = `run_${Date.now()}`;
    const request = {
      type: "agent.run",
      runId,
      input: prompt,
      target: { tabId: tab.id }
    };
    appendTrace({ type: "agent.run", runId, tabId: tab.id, prompt });
    await chrome.runtime.sendMessage(request);
  });
  saveConfigButton.addEventListener("click", async () => {
    await saveConfig();
  });
  approveButton.addEventListener("click", async () => {
    if (!pendingConfirm) return;
    const payload = {
      type: "agent.confirm",
      runId: pendingConfirm.runId,
      callId: pendingConfirm.callId,
      approved: true
    };
    await chrome.runtime.sendMessage(payload);
    hideConfirm();
  });
  rejectButton.addEventListener("click", async () => {
    if (!pendingConfirm) return;
    const payload = {
      type: "agent.confirm",
      runId: pendingConfirm.runId,
      callId: pendingConfirm.callId,
      approved: false
    };
    await chrome.runtime.sendMessage(payload);
    hideConfirm();
  });
  chrome.runtime.onMessage.addListener((message) => {
    if (message?.type === "agent.request_confirm") {
      appendTrace(message);
      showConfirm(message);
      return;
    }
    if (message?.type === "agent.tool_result") {
      appendTrace(message);
      if (message?.result?.tool === "browser.extract") {
        showExtractResult(message.result.data);
      }
      const artifactId = message?.result?.artifacts?.artifactId;
      if (artifactId) {
        showArtifactPreview(artifactId).catch(() => {
          appendTrace({ error: "preview_failed", artifactId });
        });
      }
      return;
    }
    if (message?.type === "agent.done") {
      appendTrace(message);
      return;
    }
    if (message?.type === "agent.error") {
      appendTrace(message);
      return;
    }
  });
  syncPendingConfirm().catch(() => {
  });
  chrome.storage.onChanged.addListener(() => {
    syncPendingConfirm().catch(() => {
    });
  });
  loadConfig().catch(() => {
  });
})();


path: content/index.js
content: "use strict";
(() => {
  // agent_demo/content/index.ts
  function okResult(tool, tabId, data) {
    return { ok: true, tool, tabId, data };
  }
  function errResult(tool, tabId, error) {
    return { ok: false, tool, tabId, error };
  }
  function textOf(node) {
    if (!node) return null;
    const text = node.textContent || "";
    return text.trim();
  }
  function attrOf(node, attr) {
    if (!node) return null;
    if (!attr) return textOf(node);
    return node.getAttribute(attr);
  }
  function isVisible(el) {
    if (!el) return false;
    const win = el.ownerDocument?.defaultView || window;
    const style = win.getComputedStyle(el);
    if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
      return false;
    }
    const rect = el.getBoundingClientRect();
    if (rect.width > 0 && rect.height > 0) return true;
    return el.getClientRects().length > 0;
  }
  function collectNodes(selector) {
    const nodes = Array.from(document.querySelectorAll(selector));
    const frames = Array.from(document.querySelectorAll("iframe"));
    frames.forEach((frame) => {
      try {
        const doc = frame.contentDocument;
        if (!doc) return;
        nodes.push(...Array.from(doc.querySelectorAll(selector)));
      } catch (_) {
      }
    });
    return nodes;
  }
  async function runContactFlow() {
    const logs = [];
    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
    function log(step, extra = {}) {
      const row = { step, t: Date.now(), url: location.href, ...extra };
      logs.push(row);
      console.log("[agent][contactFlow]", row);
    }
    function isVisibleLocal(el) {
      if (!el) return false;
      const style = getComputedStyle(el);
      return style.display !== "none" && style.visibility !== "hidden" && el.offsetParent !== null;
    }
    async function waitUntil(fn, timeout = 12e3, interval = 350, label = "waitUntil") {
      const start = Date.now();
      while (Date.now() - start < timeout) {
        try {
          const res = fn();
          if (res) return res;
        } catch (_) {
        }
        await sleep(interval);
      }
      log(label, { ok: false, error: "timeout" });
      return null;
    }
    async function clickEl(el, label) {
      if (!el) return false;
      try {
        el.scrollIntoView({ behavior: "smooth", block: "center" });
        await sleep(200);
        el.dispatchEvent(
          new MouseEvent("click", { bubbles: true, cancelable: true, view: window })
        );
        log(label, { ok: true, html: el.outerHTML?.slice(0, 200) || "" });
        return true;
      } catch (e) {
        log(label, { ok: false, error: e?.message || "click_failed" });
        return false;
      }
    }
    async function clickSelectCard() {
      log("Step Select: searching...");
      const el = await waitUntil(() => {
        const footers = Array.from(
          document.querySelectorAll(".smartcs-card-carousel a .card-footer")
        ).filter(isVisibleLocal);
        const footer = footers.find(
          (x) => (x.textContent || "").trim().toLowerCase().includes("select")
        );
        if (footer) return footer;
        const candidates = Array.from(document.querySelectorAll("a,button,span,div")).filter(
          isVisibleLocal
        );
        return candidates.find((x) => (x.textContent || "").trim().toLowerCase() === "select") || null;
      }, 2e4, 350, "Wait Select");
      if (!el) return false;
      return clickEl(el, "Step Select: click");
    }
    async function clickSmartcsButtonExact(text) {
      const target = text.trim().toLowerCase();
      log(`Step ${text}: searching...`);
      const el = await waitUntil(() => {
        const all = Array.from(document.querySelectorAll("li.smartcs-buttons-button")).filter(
          isVisibleLocal
        );
        return all.find((li) => (li.textContent || "").trim().toLowerCase() === target) || null;
      }, 2e4, 350, `Wait ${text}`);
      if (!el) return false;
      return clickEl(el, `Step ${text}: click`);
    }
    async function clickTakeMeToConversationIfExists() {
      log("Step Take me to conversation: searching...");
      const el = await waitUntil(() => {
        const candidates = Array.from(document.querySelectorAll("a,button,span,div")).filter(
          isVisibleLocal
        );
        return candidates.find((x) => {
          const t = (x.textContent || "").trim().toLowerCase();
          return t.includes("take me to the conversation");
        }) || null;
      }, 2e4, 350, "Wait take me to conversation");
      if (!el) return false;
      return clickEl(el, "Step take me to conversation: click");
    }
    async function run() {
      log("Start contact flow");
      try {
        await clickSelectCard();
        await sleep(1500);
        await clickSmartcsButtonExact("Other");
        await sleep(1500);
        await clickSmartcsButtonExact("Return policy");
        await sleep(1500);
        await clickTakeMeToConversationIfExists();
        await sleep(800);
        log("Contact flow done (click-only)", { ok: true });
        return { ok: true, steps: logs };
      } catch (e) {
        log("Contact flow crashed", { ok: false, error: e?.message || "crash" });
        return { ok: false, steps: logs, error: e?.message || "crash" };
      }
    }
    return run();
  }
  async function handleExtract(schema) {
    const roots = schema.list ? Array.from(document.querySelectorAll(schema.rootSelector)) : [document.querySelector(schema.rootSelector)].filter(Boolean);
    const mapFields = (root) => {
      const item = {};
      for (const [key, field] of Object.entries(schema.fields)) {
        const node = root?.querySelector(field.selector) || null;
        item[key] = attrOf(node, field.attr);
      }
      return item;
    };
    if (schema.list) {
      return { items: roots.map((root) => mapFields(root)) };
    }
    return { item: mapFields(roots[0]) };
  }
  async function handleToolCall(toolCallMessage) {
    const { toolCall, callId } = toolCallMessage;
    const tabId = toolCall.target?.tabId ?? -1;
    try {
      switch (toolCall.tool) {
        case "browser.ping":
          return { type: "tool.result", callId, result: okResult("browser.ping", tabId) };
        case "browser.query": {
          const selector = toolCall.args?.selector;
          if (!selector) {
            return { type: "tool.result", callId, result: errResult("browser.query", tabId, "missing_selector") };
          }
          const nodes = Array.from(document.querySelectorAll(selector));
          const preview = nodes[0]?.textContent?.trim().slice(0, 160) || "";
          return {
            type: "tool.result",
            callId,
            result: okResult("browser.query", tabId, {
              selector,
              count: nodes.length,
              preview
            })
          };
        }
        case "browser.get_dom": {
          const selector = toolCall.args?.selector;
          const limit = Number(toolCall.args?.limit || 5e3);
          const node = selector ? document.querySelector(selector) : document.documentElement;
          const html = node ? node.outerHTML.slice(0, limit) : "";
          return { type: "tool.result", callId, result: okResult("browser.get_dom", tabId, { html }) };
        }
        case "browser.click": {
          const selector = toolCall.args?.selector;
          const allowMissing = Boolean(toolCall.args?.allowMissing ?? false);
          const node = selector ? document.querySelector(selector) : null;
          if (!node) {
            if (allowMissing) {
              return {
                type: "tool.result",
                callId,
                result: okResult("browser.click", tabId, { selector, skipped: true })
              };
            }
            return { type: "tool.result", callId, result: errResult("browser.click", tabId, "element_not_found") };
          }
          node.click();
          return { type: "tool.result", callId, result: okResult("browser.click", tabId, { selector }) };
        }
        case "browser.click_text": {
          const text = String(toolCall.args?.text || "").trim();
          const selector = toolCall.args?.selector || "a,button,span,div,li";
          const exact = Boolean(toolCall.args?.exact ?? false);
          const timeoutMs = Number(toolCall.args?.timeoutMs || 8e3);
          const intervalMs = Number(toolCall.args?.intervalMs || 300);
          const allowMissing = Boolean(toolCall.args?.allowMissing ?? false);
          if (!text) {
            return { type: "tool.result", callId, result: errResult("browser.click_text", tabId, "missing_text") };
          }
          const targetText = text.toLowerCase();
          const start = Date.now();
          while (Date.now() - start < timeoutMs) {
            const nodes = collectNodes(selector).filter(isVisible);
            const found = nodes.find((node) => {
              const text2 = (node.textContent || "").trim();
              const aria = (node.getAttribute("aria-label") || "").trim();
              const title = (node.getAttribute("title") || "").trim();
              let value = "";
              if (node instanceof HTMLInputElement || node instanceof HTMLButtonElement) {
                value = (node.value || "").trim();
              }
              const candidates = [text2, aria, title, value].filter(Boolean).map((s) => s.toLowerCase());
              if (!candidates.length) return false;
              return exact ? candidates.some((s) => s === targetText) : candidates.some((s) => s.includes(targetText));
            });
            if (found) {
              found.scrollIntoView({ block: "center", inline: "center" });
              found.click();
              return {
                type: "tool.result",
                callId,
                result: okResult("browser.click_text", tabId, { selector, text })
              };
            }
            await new Promise((resolve) => setTimeout(resolve, intervalMs));
          }
          if (allowMissing) {
            return {
              type: "tool.result",
              callId,
              result: okResult("browser.click_text", tabId, { selector, text, skipped: true })
            };
          }
          return { type: "tool.result", callId, result: errResult("browser.click_text", tabId, "text_not_found") };
        }
        case "browser.exec": {
          const name = String(toolCall.args?.name || "").trim();
          if (name === "contact_flow") {
            const data = await runContactFlow();
            return { type: "tool.result", callId, result: okResult("browser.exec", tabId, data) };
          }
          return { type: "tool.result", callId, result: errResult("browser.exec", tabId, "unknown_exec") };
        }
        case "browser.type": {
          const selector = toolCall.args?.selector;
          const text = toolCall.args?.text ?? "";
          const clear = Boolean(toolCall.args?.clear ?? true);
          const input = selector ? document.querySelector(selector) : null;
          if (!input || !input.focus) {
            return { type: "tool.result", callId, result: errResult("browser.type", tabId, "element_not_found") };
          }
          const element = input;
          element.focus();
          if (clear) element.value = "";
          element.value = `${element.value}${text}`;
          element.dispatchEvent(new Event("input", { bubbles: true }));
          element.dispatchEvent(new Event("change", { bubbles: true }));
          return { type: "tool.result", callId, result: okResult("browser.type", tabId, { selector }) };
        }
        case "browser.type_message": {
          const text = String(toolCall.args?.text ?? "");
          const autoSend = Boolean(toolCall.args?.autoSend ?? false);
          const inputs = Array.from(
            document.querySelectorAll("textarea, [contenteditable='true'], [role='textbox']")
          ).filter(isVisible);
          const input = inputs[0] || null;
          if (!input) {
            return { type: "tool.result", callId, result: errResult("browser.type_message", tabId, "input_not_found") };
          }
          input.focus?.();
          if (input instanceof HTMLTextAreaElement || input instanceof HTMLInputElement) {
            input.value = text;
            input.dispatchEvent(new Event("input", { bubbles: true }));
            input.dispatchEvent(new Event("change", { bubbles: true }));
          } else {
            input.textContent = text;
            input.dispatchEvent(new Event("input", { bubbles: true }));
          }
          let sent = false;
          if (autoSend && text.trim().length >= 2) {
            const form = input.closest?.("form") || null;
            const sendSelectors = [
              'button[type="submit"]',
              'input[type="submit"]',
              'button[aria-label*="Send"]',
              'button[aria-label*="send"]'
            ];
            let sendBtn = null;
            if (form) {
              for (const sel of sendSelectors) {
                const candidate = form.querySelector(sel);
                if (candidate && isVisible(candidate)) {
                  sendBtn = candidate;
                  break;
                }
              }
            }
            if (!sendBtn) {
              const candidates = Array.from(
                document.querySelectorAll("button, input[type='submit']")
              ).filter(isVisible);
              sendBtn = candidates.find((node) => {
                const label = (node.getAttribute("aria-label") || "").toLowerCase();
                if (label.includes("send")) return true;
                const t = (node.textContent || "").trim().toLowerCase();
                if (t === "send" || t.includes("send message")) return true;
                const title = (node.getAttribute("title") || "").toLowerCase();
                return title.includes("send");
              }) || null;
            }
            if (sendBtn) {
              sendBtn.click();
              sent = true;
            }
          }
          return {
            type: "tool.result",
            callId,
            result: okResult("browser.type_message", tabId, { sent })
          };
        }
        case "browser.scroll": {
          const selector = toolCall.args?.selector;
          if (selector) {
            const node = document.querySelector(selector);
            if (!node) {
              return { type: "tool.result", callId, result: errResult("browser.scroll", tabId, "element_not_found") };
            }
            node.scrollIntoView({ block: "center", inline: "center" });
          } else {
            const top = Number(toolCall.args?.top || 0);
            window.scrollBy({ top, behavior: "smooth" });
          }
          return { type: "tool.result", callId, result: okResult("browser.scroll", tabId) };
        }
        case "browser.wait": {
          const ms = Number(toolCall.args?.ms || 500);
          await new Promise((resolve) => setTimeout(resolve, ms));
          return { type: "tool.result", callId, result: okResult("browser.wait", tabId, { ms }) };
        }
        case "browser.extract": {
          const schema = toolCall.args;
          if (!schema?.rootSelector || !schema?.fields) {
            return { type: "tool.result", callId, result: errResult("browser.extract", tabId, "invalid_schema") };
          }
          const data = await handleExtract(schema);
          return { type: "tool.result", callId, result: okResult("browser.extract", tabId, data) };
        }
        case "browser.screenshot": {
          const selector = toolCall.args?.selector;
          if (selector) {
            const node = document.querySelector(selector);
            if (!node) {
              return { type: "tool.result", callId, result: errResult("browser.screenshot", tabId, "element_not_found") };
            }
            node.scrollIntoView({ block: "center", inline: "center" });
            const rect = node.getBoundingClientRect();
            return {
              type: "tool.result",
              callId,
              result: okResult("browser.screenshot", tabId, {
                rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
              })
            };
          }
          return {
            type: "tool.result",
            callId,
            result: okResult("browser.screenshot", tabId, { rect: null })
          };
        }
        default:
          return { type: "tool.result", callId, result: errResult(toolCall.tool, tabId, "tool_not_supported") };
      }
    } catch (error) {
      return {
        type: "tool.result",
        callId,
        result: errResult(toolCall.tool, tabId, error instanceof Error ? error.message : "tool_failed")
      };
    }
  }
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message?.type === "ping") {
      sendResponse({ ok: true });
      return false;
    }
    if (message?.type === "tool.call") {
      handleToolCall(message).then((result) => sendResponse(result)).catch((error) => {
        sendResponse({
          type: "tool.result",
          callId: message.callId,
          result: errResult(message.toolCall.tool, message.toolCall.target?.tabId ?? -1, String(error))
        });
      });
      return true;
    }
    return false;
  });
})();


path: background/sw.js
content: "use strict";
(() => {
  // agent_demo/background/sw_helpers.ts
  async function ensureContentScript(tabId) {
    const pingResult = await pingContentScript(tabId);
    if (pingResult.ok) {
      return { ok: true };
    }
    try {
      await chrome.scripting.executeScript({
        target: { tabId },
        files: ["content/index.js"]
      });
    } catch (error) {
      return { ok: false, error: "cs_inject_failed" };
    }
    const pingAfter = await pingContentScript(tabId);
    if (!pingAfter.ok) {
      return { ok: false, error: "cs_unavailable" };
    }
    return { ok: true };
  }
  async function pingContentScript(tabId) {
    try {
      const response = await chrome.tabs.sendMessage(tabId, { type: "ping" });
      return { ok: Boolean(response?.ok) };
    } catch (error) {
      return { ok: false };
    }
  }

  // agent_demo/storage/artifacts.ts
  var DB_NAME = "agent_artifacts";
  var DB_VERSION = 1;
  var STORE_NAME = "artifacts";
  function openDb() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, DB_VERSION);
      request.onupgradeneeded = () => {
        const db = request.result;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          db.createObjectStore(STORE_NAME, { keyPath: "id" });
        }
      };
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
  function txDone(tx) {
    return new Promise((resolve, reject) => {
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error);
      tx.onabort = () => reject(tx.error);
    });
  }
  function requestToPromise(request) {
    return new Promise((resolve, reject) => {
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
  function createId() {
    if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
      return crypto.randomUUID();
    }
    return `artifact_${Date.now()}_${Math.random().toString(16).slice(2)}`;
  }
  async function putArtifact(blob, meta, mimeType = blob.type || "application/octet-stream") {
    const db = await openDb();
    const tx = db.transaction(STORE_NAME, "readwrite");
    const store = tx.objectStore(STORE_NAME);
    const record = {
      id: createId(),
      createdAt: Date.now(),
      mimeType,
      blob,
      meta
    };
    const req = store.put(record);
    await Promise.all([requestToPromise(req), txDone(tx)]);
    return record.id;
  }
  async function putArtifactFromDataUrl(dataUrl, meta) {
    const response = await fetch(dataUrl);
    const blob = await response.blob();
    return putArtifact(blob, meta, blob.type || "image/png");
  }

  // agent_demo/background/agent/runtime.ts
  var ALLOWED_TOOLS = [
    "browser.query",
    "browser.get_dom",
    "browser.navigate",
    "browser.click",
    "browser.click_text",
    "browser.exec",
    "browser.type",
    "browser.type_message",
    "browser.scroll",
    "browser.wait",
    "browser.extract",
    "browser.screenshot"
  ];
  var skillsCache = null;
  function buildDemoPlan(target) {
    return [
      {
        tool: "browser.query",
        target,
        args: { selector: "body" }
      },
      {
        tool: "browser.screenshot",
        target,
        args: {}
      }
    ];
  }
  async function buildPlanForRun(input, target) {
    const config = await getAgentConfig();
    if (!config || !config.apiKey || !config.endpoint || !config.model) {
      return buildDemoPlan(target);
    }
    const skills = await loadSkillsRegistry();
    const planned = await planWithLLM(input, config, skills);
    const normalized = normalizeToolCalls(planned, target, skills);
    if (!normalized.length) {
      return buildDemoPlan(target);
    }
    return normalized;
  }
  async function getAgentConfig() {
    const result = await chrome.storage.local.get("agent_config");
    return result.agent_config || null;
  }
  async function planWithLLM(input, config, skills) {
    const skillsBlock = formatSkillsForPrompt(skills);
    const systemPrompt = `You are a browser agent planner. Return ONLY a JSON array of tool calls. Each tool call must be { tool: string, args: object }. Allowed tools: browser.query, browser.get_dom, browser.navigate, browser.click, browser.type, browser.scroll, browser.wait, browser.extract, browser.screenshot. You may also call a skill using tool name 'skill.<skill_name>'. Do not include target; it will be injected. For browser.extract you MUST use schema: { rootSelector: string, list?: boolean, fields: { [key]: { selector: string, attr?: string } } }. Example: [{"tool":"browser.extract","args":{"rootSelector":"body","fields":{"title":{"selector":"h1"}}}}]. ` + skillsBlock;
    const userPrompt = `User goal: ${input}`;
    const response = await fetch(config.endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${config.apiKey}`
      },
      body: JSON.stringify({
        model: config.model,
        temperature: config.temperature ?? 0.2,
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userPrompt }
        ]
      })
    });
    if (!response.ok) {
      throw new Error(`planner_http_${response.status}`);
    }
    const data = await response.json();
    const content = data?.choices?.[0]?.message?.content;
    if (!content || typeof content !== "string") {
      return [];
    }
    try {
      const parsed = JSON.parse(content);
      return Array.isArray(parsed) ? parsed : [];
    } catch (error) {
      return [];
    }
  }
  async function loadSkillsRegistry() {
    if (skillsCache) return skillsCache;
    try {
      const registryUrl = chrome.runtime.getURL("skills/registry.json");
      const registryRes = await fetch(registryUrl);
      const registry = await registryRes.json();
      const entries = Array.isArray(registry?.skills) ? registry.skills : [];
      const skills = [];
      for (const entry of entries) {
        if (!entry?.file) continue;
        const fileUrl = chrome.runtime.getURL(`skills/${entry.file}`);
        const res = await fetch(fileUrl);
        const data = await res.json();
        if (data?.name) skills.push(data);
      }
      skillsCache = skills;
      return skills;
    } catch (error) {
      skillsCache = [];
      return [];
    }
  }
  function formatSkillsForPrompt(skills) {
    if (!skills.length) return "";
    const lines = skills.map((skill) => {
      const inputs = skill.input_schema ? JSON.stringify(skill.input_schema) : "{}";
      const outputs = skill.output_schema ? JSON.stringify(skill.output_schema) : "{}";
      return `Skill ${skill.name}: ${skill.description || ""} Input=${inputs} Output=${outputs}`;
    });
    return ` Available skills: ${lines.join(" | ")}`;
  }
  function normalizeToolCalls(raw, target, skills) {
    const normalized = [];
    const skillNames = new Set(skills.map((s) => s.name));
    for (const call of raw) {
      if (!call || typeof call !== "object") continue;
      const tool = String(call.tool || "");
      if (tool.startsWith("skill.") || skillNames.has(tool)) {
        const skillName = tool.startsWith("skill.") ? tool.slice(6) : tool;
        const expanded = expandSkillCall(skillName, call.args || {}, target);
        normalized.push(...expanded);
        continue;
      }
      if (!ALLOWED_TOOLS.includes(tool)) continue;
      normalized.push({
        tool,
        target,
        args: call.args || {}
      });
    }
    return normalized.slice(0, 20);
  }
  function expandSkillCall(skillName, args, target) {
    switch (skillName) {
      case "amazon_orders_opener":
        const ordersArgs = { kind: "orders_page" };
        if (args?.timeFilter) ordersArgs.timeFilter = args.timeFilter;
        if (args?.year) ordersArgs.year = args.year;
        if (!ordersArgs.timeFilter && !ordersArgs.year) {
          ordersArgs.year = 2025;
        }
        return [
          {
            tool: "browser.navigate",
            target,
            args: ordersArgs
          }
        ];
      case "amazon_product_detector":
        return [
          {
            tool: "browser.extract",
            target,
            args: {
              rootSelector: "body",
              fields: {
                asin: { selector: "#ASIN, input[name='ASIN']", attr: "value" },
                title: { selector: "#productTitle" },
                price: { selector: ".a-price .a-offscreen" },
                image_url: { selector: "#landingImage", attr: "src" },
                rating: { selector: "#acrPopover span.a-icon-alt" },
                review_count: { selector: "#acrCustomerReviewText" }
              }
            }
          }
        ];
      case "amazon_orders_scraper":
        const extractCall = {
          tool: "browser.extract",
          target,
          args: {
            rootSelector: ".order-card.js-order-card, .order-card",
            list: true,
            fields: {
              order_id: { selector: "[data-order-id]", attr: "data-order-id" },
              title: { selector: ".yohtmlc-item-title, .a-link-normal" },
              price: { selector: ".a-price .a-offscreen" },
              details_url: {
                selector: "a[href*='order-details']",
                attr: "href"
              }
            }
          }
        };
        if (args?.openFirstDetails) {
          return [
            extractCall,
            {
              tool: "browser.click",
              target,
              args: { selector: "a[href*='order-details']" }
            }
          ];
        }
        return [extractCall];
      case "order_reader":
        return [
          {
            tool: "browser.extract",
            target,
            args: {
              rootSelector: args.rootSelector || ".order-card",
              fields: args.fields || {
                title: { selector: ".item-title" },
                price: { selector: ".a-price .a-offscreen" }
              }
            }
          }
        ];
      case "evidence_builder": {
        const selectors = Array.isArray(args.selectors) ? args.selectors : [];
        if (!selectors.length) {
          return [{ tool: "browser.screenshot", target, args: {} }];
        }
        return selectors.map((selector) => ({
          tool: "browser.screenshot",
          target,
          args: { selector }
        }));
      }
      case "case_exporter": {
        const selectors = Array.isArray(args.selectors) ? args.selectors : [];
        const calls = [];
        if (args.includeDom) {
          calls.push({ tool: "browser.get_dom", target, args: {} });
        }
        if (!selectors.length) {
          calls.push({ tool: "browser.screenshot", target, args: {} });
        } else {
          selectors.forEach(
            (selector) => calls.push({ tool: "browser.screenshot", target, args: { selector } })
          );
        }
        return calls;
      }
      case "form_filler": {
        const calls = [];
        const fields = Array.isArray(args.fields) ? args.fields : [];
        fields.forEach((field) => {
          if (!field?.selector) return;
          calls.push({
            tool: "browser.type",
            target,
            args: {
              selector: field.selector,
              text: field.value || "",
              clear: field.clear !== false
            }
          });
        });
        if (args.submitSelector) {
          calls.push({
            tool: "browser.click",
            target,
            args: { selector: args.submitSelector }
          });
        }
        return calls;
      }
      case "amazon_contact_flow": {
        const calls = [];
        if (args?.details_url) {
          calls.push({
            tool: "browser.navigate",
            target,
            args: { url: args.details_url }
          });
          calls.push({ tool: "browser.wait", target, args: { ms: 1500 } });
        }
        calls.push({
          tool: "browser.click_text",
          target,
          args: {
            text: "ask product question",
            selector: "a,button",
            exact: false,
            timeoutMs: 8e3,
            allowMissing: true
          }
        });
        calls.push({ tool: "browser.wait", target, args: { ms: 1200 } });
        calls.push({ tool: "browser.exec", target, args: { name: "contact_flow" } });
        calls.push({ tool: "browser.wait", target, args: { ms: 2e3 } });
        calls.push({
          tool: "browser.type_message",
          target,
          args: { text: args?.message || "", autoSend: args?.auto_send === true }
        });
        return calls;
      }
      default:
        return [];
    }
  }
  async function executeToolCall(context) {
    const { toolCall } = context;
    const tabId = toolCall.target.tabId;
    try {
      const tabExists = await chrome.tabs.get(tabId).then(() => true).catch(() => false);
      if (!tabExists) {
        return {
          ok: false,
          tool: toolCall.tool,
          tabId,
          error: "tab_closed"
        };
      }
      if (toolCall.tool === "browser.navigate") {
        const destination = await resolveNavigationTarget(tabId, toolCall.args);
        if (!destination) {
          return {
            ok: false,
            tool: toolCall.tool,
            tabId,
            error: "navigate_target_missing"
          };
        }
        await chrome.tabs.update(tabId, { url: destination, active: true });
        return {
          ok: true,
          tool: toolCall.tool,
          tabId,
          data: { url: destination }
        };
      }
      const csReady = await ensureContentScript(tabId);
      if (!csReady.ok) {
        return {
          ok: false,
          tool: toolCall.tool,
          tabId,
          error: csReady.error || "cs_unavailable"
        };
      }
      if (toolCall.tool === "browser.screenshot") {
        const toolResultMessage = await chrome.tabs.sendMessage(tabId, {
          type: "tool.call",
          callId: context.callId,
          toolCall
        });
        const baseResult = toolResultMessage?.result;
        if (!baseResult || !baseResult.ok) {
          return {
            ok: false,
            tool: toolCall.tool,
            tabId,
            error: baseResult?.error || "screenshot_preflight_failed"
          };
        }
        const tab = await chrome.tabs.get(tabId);
        const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {
          format: "png"
        });
        const artifactId = await putArtifactFromDataUrl(dataUrl, {
          runId: context.runId,
          callId: context.callId,
          tool: toolCall.tool
        });
        return {
          ok: true,
          tool: toolCall.tool,
          tabId,
          data: {
            rect: baseResult.data?.rect || null,
            viewportCaptured: true
          },
          artifacts: { artifactId }
        };
      }
      const resultMessage = await chrome.tabs.sendMessage(tabId, {
        type: "tool.call",
        callId: context.callId,
        toolCall
      });
      if (!resultMessage || !resultMessage.result) {
        return {
          ok: false,
          tool: toolCall.tool,
          tabId,
          error: "no_tool_result"
        };
      }
      return {
        ...resultMessage.result,
        tabId
      };
    } catch (error) {
      return {
        ok: false,
        tool: toolCall.tool,
        tabId,
        error: error instanceof Error ? error.message : "tool_failed"
      };
    }
  }
  async function resolveNavigationTarget(tabId, args) {
    if (args?.url) {
      return String(args.url);
    }
    if (args?.kind === "orders_page") {
      const base = await resolveAmazonOrdersBase(tabId);
      const params = new URLSearchParams();
      if (args.timeFilter) {
        params.set("timeFilter", String(args.timeFilter));
      } else if (args.year) {
        params.set("timeFilter", `year-${String(args.year)}`);
      }
      const timeFilter = params.get("timeFilter");
      if (timeFilter) {
        const match = timeFilter.match(/year-(\d{4})/);
        if (match) {
          params.set("ref_", `ppx_yo2ov_dt_b_filter_all_y${match[1]}`);
        }
      }
      const qs = params.toString();
      const url = qs ? `${base}/your-orders/orders?${qs}` : `${base}/your-orders/orders`;
      console.log("[agent][orders] navigate:", url);
      return url;
    }
    return null;
  }
  async function resolveAmazonOrdersBase(tabId) {
    const current = await chrome.tabs.get(tabId).catch(() => null);
    const base = getAmazonBaseFromUrl(current?.url || "");
    if (base) return base;
    const tabs = await chrome.tabs.query({});
    for (const tab of tabs) {
      const tabBase = getAmazonBaseFromUrl(tab.url || "");
      if (tabBase) return tabBase;
    }
    return "https://www.amazon.com";
  }
  function getAmazonBaseFromUrl(url) {
    try {
      const u = new URL(url);
      if (!/(^|\.)amazon\./i.test(u.hostname)) return null;
      return `${u.protocol}//${u.host}`;
    } catch (_) {
      return null;
    }
  }

  // agent_demo/storage/runs.ts
  var RUN_PREFIX = "agent_run:";
  function keyFor(runId) {
    return `${RUN_PREFIX}${runId}`;
  }
  async function saveRunState(state) {
    state.updatedAt = Date.now();
    await chrome.storage.local.set({ [keyFor(state.runId)]: state });
  }
  async function getRunState(runId) {
    const result = await chrome.storage.local.get(keyFor(runId));
    return result[keyFor(runId)] || null;
  }
  async function listRuns() {
    const result = await chrome.storage.local.get(null);
    return Object.keys(result).filter((key) => key.startsWith(RUN_PREFIX)).map((key) => result[key]);
  }
  async function appendTraceEntry(runId, entry) {
    const state = await getRunState(runId);
    if (!state) return null;
    state.trace.push(entry);
    await saveRunState(state);
    return state;
  }

  // agent_demo/background/sw.ts
  var pendingCalls = /* @__PURE__ */ new Map();
  function createCallId() {
    return `call_${Date.now()}_${Math.random().toString(16).slice(2)}`;
  }
  function sendToPanel(message) {
    chrome.runtime.sendMessage(message).catch(() => {
    });
  }
  async function createRun(request) {
    const plan = await buildPlanForRun(request.input, request.target);
    if (!plan.length) {
      throw new Error("empty_plan");
    }
    const state = {
      runId: request.runId,
      target: request.target,
      input: request.input,
      plan,
      stepIndex: 0,
      status: "idle",
      trace: [],
      createdAt: Date.now(),
      updatedAt: Date.now()
    };
    await saveRunState(state);
    return state;
  }
  async function requestConfirm(state, toolCall) {
    const callId = createCallId();
    pendingCalls.set(callId, { runId: state.runId, toolCall });
    state.status = "waiting_confirm";
    state.pendingCallId = callId;
    state.pendingToolCall = toolCall;
    await saveRunState(state);
    sendToPanel({
      type: "agent.request_confirm",
      runId: state.runId,
      callId,
      toolCall
    });
  }
  async function handleConfirm(message) {
    const { callId, approved, runId } = message;
    let pending = pendingCalls.get(callId);
    if (!pending) {
      const state2 = await getRunState(runId);
      if (state2?.pendingCallId === callId && state2.pendingToolCall) {
        pending = { runId, toolCall: state2.pendingToolCall };
        pendingCalls.set(callId, pending);
      }
    }
    if (!pending) {
      sendToPanel({
        type: "agent.error",
        runId,
        callId,
        error: "pending_call_not_found"
      });
      return;
    }
    const state = await getRunState(pending.runId);
    if (!state) {
      sendToPanel({
        type: "agent.error",
        runId,
        callId,
        error: "run_not_found"
      });
      return;
    }
    if (!approved) {
      pendingCalls.delete(callId);
      state.status = "error";
      await saveRunState(state);
      sendToPanel({
        type: "agent.error",
        runId,
        callId,
        error: "user_rejected"
      });
      return;
    }
    pendingCalls.delete(callId);
    state.status = "running";
    await saveRunState(state);
    const callTrace = {
      runId: state.runId,
      callId,
      ts: Date.now(),
      tool: pending.toolCall.tool,
      args: pending.toolCall.args
    };
    await appendTraceEntry(state.runId, callTrace);
    const result = await executeToolCall({
      runId: state.runId,
      callId,
      toolCall: pending.toolCall
    });
    const resultTrace = {
      runId: state.runId,
      callId,
      ts: Date.now(),
      tool: pending.toolCall.tool,
      args: pending.toolCall.args,
      ok: result.ok,
      error: result.error,
      artifacts: result.artifacts
    };
    await appendTraceEntry(state.runId, resultTrace);
    sendToPanel({
      type: "agent.tool_result",
      runId: state.runId,
      callId,
      result
    });
    if (!result.ok) {
      state.status = "error";
      await saveRunState(state);
      sendToPanel({
        type: "agent.error",
        runId: state.runId,
        callId,
        error: result.error || "tool_failed"
      });
      return;
    }
    state.stepIndex += 1;
    state.pendingCallId = void 0;
    state.pendingToolCall = void 0;
    if (state.stepIndex >= state.plan.length) {
      state.status = "done";
      await saveRunState(state);
      sendToPanel({ type: "agent.done", runId: state.runId });
      return;
    }
    const nextTool = state.plan[state.stepIndex];
    await requestConfirm(state, nextTool);
  }
  chrome.runtime.onMessage.addListener(
    (message, sender, sendResponse) => {
      if (message?.type === "agent.run") {
        createRun(message).then((state) => requestConfirm(state, state.plan[state.stepIndex])).then(() => sendResponse({ ok: true })).catch((error) => {
          const err = error?.message || "run_failed";
          sendToPanel({
            type: "agent.error",
            runId: message.runId,
            error: err
          });
          sendResponse({ ok: false, error: err });
        });
        return true;
      }
      if (message?.type === "agent.confirm") {
        handleConfirm(message).then(() => sendResponse({ ok: true })).catch(
          (error) => sendResponse({ ok: false, error: error?.message || "confirm_failed" })
        );
        return true;
      }
      return false;
    }
  );
  async function recoverRuns() {
    const runs = await listRuns();
    for (const run of runs) {
      if (run.status === "waiting_confirm" && run.pendingCallId && run.pendingToolCall) {
        pendingCalls.set(run.pendingCallId, {
          runId: run.runId,
          toolCall: run.pendingToolCall
        });
      }
    }
  }
  recoverRuns().catch(() => {
  });
})();


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)