control-dom 0.1.0__py3-none-any.whl
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.
control_dom/__init__.py
ADDED
|
File without changes
|
control_dom/main.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
control_dom Tool
|
|
3
|
+
=================
|
|
4
|
+
Evaluates arbitrary JavaScript in a Tauri child webview using
|
|
5
|
+
the in-browser Playwright Page API polyfill (window.__page).
|
|
6
|
+
|
|
7
|
+
The JS code runs inside the target webview's renderer context.
|
|
8
|
+
Console output (console.log / warn / error) is captured and
|
|
9
|
+
returned so the agent can observe results.
|
|
10
|
+
|
|
11
|
+
Parameters:
|
|
12
|
+
js_code – JavaScript source to evaluate. Has full access to
|
|
13
|
+
window.__page and the Playwright polyfill API.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import logging
|
|
20
|
+
import uuid
|
|
21
|
+
from typing import Any, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
from openchadpy.context import console_messages
|
|
24
|
+
from openchadpy.tool_base import ToolBase
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# How long (seconds) to wait for console output after the script fires.
|
|
29
|
+
_DEFAULT_WAIT = 8.0
|
|
30
|
+
# How long (seconds) between console-poll ticks.
|
|
31
|
+
_POLL_INTERVAL = 0.15
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Tool(ToolBase):
|
|
35
|
+
"""Evaluate JavaScript inside a Tauri child webview (DOM control)."""
|
|
36
|
+
|
|
37
|
+
name = "control_dom"
|
|
38
|
+
description = (
|
|
39
|
+
"Evaluate JavaScript code inside a Tauri child webview. "
|
|
40
|
+
"The code runs in the page context and has access to "
|
|
41
|
+
"window.__page — a Playwright Page API polyfill — so you can "
|
|
42
|
+
"click, fill, navigate, scrape, wait for selectors, and more. "
|
|
43
|
+
"All console.log / console.error / console.warn output is captured "
|
|
44
|
+
"and returned so you can observe results. "
|
|
45
|
+
"Always write console.log() statements so you can verify actions succeeded. "
|
|
46
|
+
"Prefer window.__page methods over raw DOM when possible."
|
|
47
|
+
)
|
|
48
|
+
input_schema = {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"js_code": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": (
|
|
54
|
+
"JavaScript code to evaluate in the webview. "
|
|
55
|
+
"Top-level await is supported — wrap async logic directly. "
|
|
56
|
+
"Use window.__page for Playwright-style DOM control. "
|
|
57
|
+
"Use console.log() to emit observable output. "
|
|
58
|
+
"Example:\n"
|
|
59
|
+
" const loc = window.__page.getByRole('button', { name: 'Submit' });\n"
|
|
60
|
+
" await loc.click();\n"
|
|
61
|
+
" console.log('clicked:', await loc.textContent());"
|
|
62
|
+
),
|
|
63
|
+
},
|
|
64
|
+
"timeout": {
|
|
65
|
+
"type": "number",
|
|
66
|
+
"description": (
|
|
67
|
+
"Seconds to wait for console output after the script runs "
|
|
68
|
+
f"(default: {_DEFAULT_WAIT}). Increase for long-running scripts."
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
"required": ["js_code"],
|
|
73
|
+
}
|
|
74
|
+
allowed_callers = ["direct", "code_execution", "mcp_client"]
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
# helpers
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def _resolve_target(self, target_webview: Optional[str]) -> str:
|
|
81
|
+
"""Return a best-effort webview label."""
|
|
82
|
+
if target_webview:
|
|
83
|
+
return target_webview
|
|
84
|
+
# Fall back to the active tab id formatted as a webview agent label.
|
|
85
|
+
tid = self.tab_id
|
|
86
|
+
if tid and tid != "global":
|
|
87
|
+
return f"webview-agent-{tid}"
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
async def _collect_new_console(
|
|
91
|
+
self,
|
|
92
|
+
label: str,
|
|
93
|
+
before_count: int,
|
|
94
|
+
timeout: float,
|
|
95
|
+
) -> List[Dict[str, Any]]:
|
|
96
|
+
"""
|
|
97
|
+
Poll console_messages[label] until new entries appear or timeout.
|
|
98
|
+
Returns the list of new message dicts appended after `before_count`.
|
|
99
|
+
"""
|
|
100
|
+
deadline = asyncio.get_event_loop().time() + timeout
|
|
101
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
102
|
+
entries: List[Any] = console_messages.get(label, [])
|
|
103
|
+
if len(entries) > before_count:
|
|
104
|
+
return list(entries[before_count:])
|
|
105
|
+
await asyncio.sleep(_POLL_INTERVAL)
|
|
106
|
+
# Return whatever arrived even if we timed out
|
|
107
|
+
entries = console_messages.get(label, [])
|
|
108
|
+
return list(entries[before_count:])
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
# execute
|
|
112
|
+
# ------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
async def execute(self, **kwargs) -> Dict[str, Any]: # noqa: C901
|
|
115
|
+
js_code: str = kwargs.get("js_code", "").strip()
|
|
116
|
+
target_webview: str = self.get_field("Browser ID") or ""
|
|
117
|
+
timeout: float = float(kwargs.get("timeout", _DEFAULT_WAIT))
|
|
118
|
+
|
|
119
|
+
if not js_code:
|
|
120
|
+
return {"error": "js_code is required and must not be empty."}
|
|
121
|
+
|
|
122
|
+
label = self._resolve_target(target_webview or None)
|
|
123
|
+
if not label:
|
|
124
|
+
return {
|
|
125
|
+
"error": (
|
|
126
|
+
"Could not determine target webview. "
|
|
127
|
+
"Please supply target_webview explicitly."
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Snapshot current console length so we can detect new messages.
|
|
132
|
+
before_count: int = len(console_messages.get(label, []))
|
|
133
|
+
|
|
134
|
+
# Wrap the user code in an async IIFE so top-level await works
|
|
135
|
+
# and unhandled errors surface as console.error output.
|
|
136
|
+
wrapped = (
|
|
137
|
+
"(async () => { try {\n"
|
|
138
|
+
+ js_code
|
|
139
|
+
+ "\n} catch (e) { console.error('[control_dom error]', e && (e.stack || e.message || String(e))); } })();"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
if not self.event_emitter:
|
|
144
|
+
return {"error": "event_emitter not available (tool not fully initialised)."}
|
|
145
|
+
|
|
146
|
+
await self.event_emitter.emit("eval", {"script": wrapped, "label": label})
|
|
147
|
+
except Exception as exc:
|
|
148
|
+
logger.error(f"[control_dom] emit eval failed: {exc}", exc_info=True)
|
|
149
|
+
return {"error": f"Failed to send script to webview: {exc}"}
|
|
150
|
+
|
|
151
|
+
# Collect console output that arrives within the timeout window.
|
|
152
|
+
new_messages = await self._collect_new_console(label, before_count, timeout)
|
|
153
|
+
|
|
154
|
+
# Build human-readable output lines.
|
|
155
|
+
output_lines: List[str] = []
|
|
156
|
+
has_error = False
|
|
157
|
+
for msg in new_messages:
|
|
158
|
+
mtype = msg.get("type", "log")
|
|
159
|
+
text = msg.get("text", "")
|
|
160
|
+
output_lines.append(f"[{mtype}] {text}")
|
|
161
|
+
if mtype == "error":
|
|
162
|
+
has_error = True
|
|
163
|
+
|
|
164
|
+
result: Dict[str, Any] = {
|
|
165
|
+
"label": label,
|
|
166
|
+
"console": output_lines,
|
|
167
|
+
}
|
|
168
|
+
if has_error:
|
|
169
|
+
result["warning"] = "One or more console.error() calls were recorded."
|
|
170
|
+
if not output_lines:
|
|
171
|
+
result["note"] = (
|
|
172
|
+
"No console output was captured within the timeout window. "
|
|
173
|
+
"Add console.log() calls to your js_code to verify execution."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return result
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
control_dom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
control_dom/main.py,sha256=vdF0mfU5uOq9Bm8RTjBew2o_XOihyLUwISCHeDk6BF0,6787
|
|
3
|
+
control_dom-0.1.0.dist-info/METADATA,sha256=y2PK4zLZakQJTE229mLio-L9AKPZCvksRZokS9c8WB0,217
|
|
4
|
+
control_dom-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
control_dom-0.1.0.dist-info/top_level.txt,sha256=oAOFPYp3QHhrMjhd255o0pHYLw-Dig_-YpnAc9D04Pw,12
|
|
6
|
+
control_dom-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
control_dom
|