Skip to content

Commit 7de4865

Browse files
committed
update web agent; increase call limit to 10 and enhance browser tool actions with new coordinates support
1 parent 909ec1a commit 7de4865

4 files changed

Lines changed: 83 additions & 14 deletions

File tree

argus/tools/ask_web_agent.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class AskWebAgentTool(Tool):
2424
"Ask the web-browsing agent to solve a task by navigating to a URL and describing what it sees. "
2525
"Use this to verify that a page or server is working as expected. "
2626
"Returns a description of the page appearance along with a screenshot image when available. "
27-
"IMPORTANT: you may call this tool at most 5 times in total — use these calls wisely (e.g. only after the server is confirmed up, and after each meaningful fix)."
27+
"IMPORTANT: you may call this tool at most 10 times in total — use these calls wisely (e.g. only after the server is confirmed up, and after each meaningful fix)."
2828
)
2929
parameters = {
3030
"type": "object",
@@ -45,7 +45,7 @@ class AskWebAgentTool(Tool):
4545
"required": ["url"],
4646
}
4747

48-
def __init__(self, web_agent: WebAgent, max_calls: int = 5) -> None:
48+
def __init__(self, web_agent: WebAgent, max_calls: int = 10) -> None:
4949
self._web_agent = web_agent
5050
self._max_calls = max_calls
5151
self._call_count = 0
@@ -54,7 +54,7 @@ def execute(self, url: str, question: str = "", **_: Any) -> Content: # type: i
5454
if self._call_count >= self._max_calls:
5555
return Content(
5656
text=(
57-
f"[ask_web_agent] Call limit reached ({self._max_calls}/{self._max_calls}). "
57+
f"[ask_web_agent] Call limit reached ({self._call_count}/{self._max_calls}). "
5858
"No more web agent calls are allowed. Use curl or inspect HTML to verify instead."
5959
)
6060
)

argus/tools/browser.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
class BrowserTool(Tool):
1212
"""Control a headless Chromium browser via Playwright.
1313
14-
Supports navigate, screenshot, click, type, scroll, get_text, and
15-
get_console_logs actions. The ``screenshot`` action returns a ``Content``
16-
object with the PNG image attached; all other actions return text-only
17-
``Content``.
14+
Supports navigate, screenshot, click, dblclick, hover, drag_and_drop,
15+
type, press, scroll, reload, get_text, and get_console_logs actions.
16+
For ``hover`` and ``click``, pass ``x``/``y`` pixel coordinates to target
17+
canvas elements or precise positions; otherwise pass a CSS ``selector``.
18+
The ``screenshot`` action returns a ``Content`` object with the PNG image
19+
attached; all other actions return text-only ``Content``.
1820
"""
1921

2022
name = "browser"
@@ -41,15 +43,35 @@ class BrowserTool(Tool):
4143
"reload",
4244
"get_text",
4345
"get_console_logs",
46+
"get_element_bounds",
4447
],
45-
"description": "The browser action to perform.",
48+
"description": (
49+
"The browser action to perform. "
50+
"Use get_element_bounds to get the pixel position and size of an element "
51+
"(e.g. a canvas), then use those coordinates with hover or click."
52+
),
4653
},
4754
"url": {"type": "string", "description": "URL to navigate to (navigate action)."},
4855
"selector": {
4956
"type": "string",
5057
"description": (
5158
"CSS selector for the target element "
52-
"(click, dblclick, hover, drag_and_drop source, type, press actions)."
59+
"(click, dblclick, hover, drag_and_drop source, type, press actions). "
60+
"For hover and click, x/y coordinates take precedence over selector."
61+
),
62+
},
63+
"x": {
64+
"type": "number",
65+
"description": (
66+
"Absolute x coordinate in pixels for hover or click. "
67+
"Use instead of selector for canvas elements or precise positions."
68+
),
69+
},
70+
"y": {
71+
"type": "number",
72+
"description": (
73+
"Absolute y coordinate in pixels for hover or click. "
74+
"Use instead of selector for canvas elements or precise positions."
5375
),
5476
},
5577
"target_selector": {
@@ -112,6 +134,10 @@ async def _execute_async(self, action: str, **kwargs: Any) -> Content:
112134
return Content(text="Screenshot captured.", images=[b64])
113135

114136
if action == "click":
137+
x, y = kwargs.get("x"), kwargs.get("y")
138+
if x is not None and y is not None:
139+
await self._page.mouse.click(x, y)
140+
return Content(text=f"Clicked at ({x}, {y})")
115141
selector = kwargs.get("selector", "")
116142
await self._page.click(selector)
117143
return Content(text=f"Clicked '{selector}'")
@@ -122,6 +148,10 @@ async def _execute_async(self, action: str, **kwargs: Any) -> Content:
122148
return Content(text=f"Double-clicked '{selector}'")
123149

124150
if action == "hover":
151+
x, y = kwargs.get("x"), kwargs.get("y")
152+
if x is not None and y is not None:
153+
await self._page.mouse.move(x, y)
154+
return Content(text=f"Hovered at ({x}, {y})")
125155
selector = kwargs.get("selector", "")
126156
await self._page.hover(selector)
127157
return Content(text=f"Hovered over '{selector}'")
@@ -160,6 +190,23 @@ async def _execute_async(self, action: str, **kwargs: Any) -> Content:
160190
if action == "get_text":
161191
return Content(text=await self._page.inner_text("body"))
162192

193+
if action == "get_element_bounds":
194+
selector = kwargs.get("selector", "")
195+
el = await self._page.query_selector(selector)
196+
if el is None:
197+
return Content(text=f"[error: element not found for selector '{selector}']")
198+
box = await el.bounding_box()
199+
if box is None:
200+
return Content(text=f"[error: element '{selector}' has no bounding box (hidden?)]")
201+
return Content(
202+
text=(
203+
f"Element '{selector}' bounds: "
204+
f"x={box['x']:.0f}, y={box['y']:.0f}, "
205+
f"width={box['width']:.0f}, height={box['height']:.0f}. "
206+
f"Center: ({box['x'] + box['width'] / 2:.0f}, {box['y'] + box['height'] / 2:.0f})"
207+
)
208+
)
209+
163210
if action == "get_console_logs":
164211
if not self._console_logs:
165212
return Content(text="No console output.")

argus/tools/checklist.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ class ChecklistTool(Tool):
3939
"type": "integer",
4040
"description": "0-based index of the item to complete. Required for action='complete'.",
4141
},
42+
"note": {
43+
"type": "string",
44+
"description": (
45+
"Evidence that the step was completed. Required for action='complete'. "
46+
"For fix/implementation steps, paste the output of `git diff <filename>` here. "
47+
"For other steps, briefly describe what was done (e.g. 'found bug in src/core/ticks.js:42')."
48+
),
49+
},
4250
"item": {
4351
"type": "string",
4452
"description": "Description of the new step to append. Required for action='add'.",
@@ -61,6 +69,8 @@ def render(self) -> str:
6169
for i, it in enumerate(self._items):
6270
mark = "x" if it["done"] else " "
6371
lines.append(f" [{mark}] {i}. {it['text']}")
72+
if it.get("note"):
73+
lines.append(f" note: {it['note']}")
6474
done = sum(1 for it in self._items if it["done"])
6575
lines.append(f"\nProgress: {done}/{len(self._items)} completed")
6676
return "\n".join(lines)
@@ -70,6 +80,7 @@ def execute(
7080
action: str,
7181
items: list[str] | None = None,
7282
index: int | None = None,
83+
note: str | None = None,
7384
item: str | None = None,
7485
) -> Content:
7586
if action == "init":
@@ -87,7 +98,16 @@ def execute(
8798
return Content(
8899
text=f"[error: index {index} out of range (valid: 0–{len(self._items) - 1})]"
89100
)
101+
if not note:
102+
return Content(
103+
text=(
104+
"[error: 'note' is required for action='complete'. "
105+
"For fix/implementation steps, paste the `git diff` output. "
106+
"For other steps, briefly describe what was done.]"
107+
)
108+
)
90109
self._items[index]["done"] = True
110+
self._items[index]["note"] = note
91111
return Content(text=f"Item {index} marked as done.\n\n{self.render()}")
92112

93113
if action == "add":

config.example.yaml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ agent:
1818
Your plan should not be too long — ideally around 5-8 steps. Do NOT make a plan with 12+ steps as that is likely too detailed and will not be effective.
1919
2020
## Recommended Workflow Phases (follow in order)
21+
The key principle is: **if you are stuck on a phase, skip it and move on — a partial fix is better than no fix.**
2122
You have a limited number of steps. Spend them wisely across these phases:
22-
1. **Explore** (≤12 steps): Understand the codebase and locate the relevant code.
23-
2. **Reproduce** (≤5 steps): Build the project if needed and create a minimal reproduction app.
24-
3. **Fix** (≤8 steps): Edit the source code to resolve the issue.
25-
4. **Verify** (≤10 steps): Confirm the fix works using ask_web_agent and test edge cases.
26-
If you are still exploring after 15 steps, stop and move to reproduction immediately. Do NOT spend too many steps exploring!
23+
1. **Explore** (≤24 steps): Understand the codebase and locate the relevant code.
24+
2. **Reproduce** (≤10 steps): Build the project if needed and create a minimal reproduction app.
25+
3. **Fix** (≤16 steps): Edit the source code to resolve the issue.
26+
4. **Verify** (≤20 steps): Confirm the fix works using ask_web_agent and test edge cases.
27+
If you are still exploring after 20 steps, stop and move to reproduction immediately. Do NOT spend too many steps exploring!
28+
If you cannot get a reproduction server working within 10 steps, skip visual reproduction and go directly to Fix. Implement the source-code change and use `git diff` to confirm it — you do not need a running server to fix code.
2729
2830
## Important Boundaries
2931
- DO NOT MODIFY: Tests, configuration files

0 commit comments

Comments
 (0)