omnius 1.0.855 → 1.0.856
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/py-embed.js +4 -4
- package/dist/index.js +2 -2
- package/dist/library.js +2777 -1439
- package/dist/scripts/crawlee-scraper.py +165 -185
- package/dist/scripts/web_scrape.py +22 -20
- package/dist/update-worker.js +4 -4
- package/docs/DISCOVERY.json +440 -0
- package/docs/DISCOVERY.md +12 -0
- package/docs/guides/tools-and-web-search.md +44 -0
- package/docs/work-orders/WO-fallback-contract-cooldown-20261013.md +83 -0
- package/docs/work-orders/WO-telegram-public-research-stream-progress-20261013.md +100 -0
- package/docs/work-orders/WO-web-browser-crawl-full-coverage-20261013.md +65 -0
- package/docs/work-orders/WO-web-source-full-coverage-extraction-20261013.md +135 -0
- package/docs/work-orders/evidence/browser-complete-source-live-20261013.json +111 -0
- package/docs/work-orders/evidence/fallback-contract-cooldown-before-20261013.json +259 -0
- package/docs/work-orders/evidence/telegram-public-research-stream-live-20261013.json +754 -0
- package/docs/work-orders/evidence/web-source-installed-capacity-guard-20261013.json +127 -0
- package/docs/work-orders/evidence/web-source-installed-public-live-20261013.json +651 -0
- package/docs/work-orders/evidence/web-source-parent-capacity-before-20261013.json +233 -0
- package/docs/work-orders/evidence/web-source-wide-coordinator-live-20261013.json +485 -0
- package/docs/work-orders/evidence/web-source-worker-validation-before-20261013.json +97 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -21,6 +21,9 @@ import json
|
|
|
21
21
|
import subprocess
|
|
22
22
|
import sys
|
|
23
23
|
import importlib
|
|
24
|
+
import re
|
|
25
|
+
from datetime import timedelta
|
|
26
|
+
from urllib.parse import urljoin, urlparse
|
|
24
27
|
from typing import Any
|
|
25
28
|
|
|
26
29
|
# ---------------------------------------------------------------------------
|
|
@@ -86,182 +89,160 @@ def ensure_playwright():
|
|
|
86
89
|
|
|
87
90
|
|
|
88
91
|
# ---------------------------------------------------------------------------
|
|
89
|
-
#
|
|
92
|
+
# Complete page observations, with explicit resource failures (never prefixes)
|
|
90
93
|
# ---------------------------------------------------------------------------
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
94
|
+
MAX_PAGE_BYTES = 5 * 1024 * 1024
|
|
95
|
+
MAX_RESULT_BYTES = 8 * 1024 * 1024
|
|
96
|
+
DESTINATION_ATTRIBUTES = ("href", "src", "srcset", "action", "formaction", "poster", "data-src", "data-srcset", "data-original")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def extract_page(soup: Any, url: str) -> dict[str, Any]:
|
|
100
|
+
"""Retain the entire parser/DOM snapshot and clean complete body view.
|
|
101
|
+
|
|
102
|
+
Snapshot provenance is deliberately not an HTTP-status or full-site claim.
|
|
103
|
+
Script/style/template payloads stay in the retained snapshot; JSON-LD is
|
|
104
|
+
additionally exposed without discarding malformed or duplicate entries.
|
|
105
|
+
"""
|
|
106
|
+
from bs4 import Comment, NavigableString, Tag
|
|
107
|
+
snapshot = str(soup)
|
|
108
|
+
if len(snapshot.encode("utf-8")) > MAX_PAGE_BYTES:
|
|
109
|
+
raise ValueError("Page snapshot exceeded the complete-page resource limit; no prefix was retained")
|
|
110
|
+
body = soup.body or soup
|
|
111
|
+
links = [{"url": urljoin(url, str(tag.get("href"))), "text": tag.get_text(" ", strip=True)}
|
|
112
|
+
for tag in soup.find_all("a", href=True)]
|
|
113
|
+
metadata = [{"name": str(tag.get("name") or tag.get("property") or tag.get("http-equiv") or ""),
|
|
114
|
+
"content": str(tag.get("content") or "")} for tag in soup.find_all("meta")]
|
|
115
|
+
structured = []
|
|
116
|
+
for tag in soup.find_all("script"):
|
|
117
|
+
if "ld+json" in str(tag.get("type", "")).lower():
|
|
118
|
+
payload = tag.string if tag.string is not None else tag.get_text()
|
|
119
|
+
try:
|
|
120
|
+
structured.append({"type": str(tag.get("type")), "value": json.loads(payload), "raw": payload})
|
|
121
|
+
except (ValueError, TypeError):
|
|
122
|
+
structured.append({"type": str(tag.get("type")), "raw": payload, "parse_error": True})
|
|
123
|
+
|
|
124
|
+
parts: list[str] = []
|
|
125
|
+
def visit(node: Any, preformatted: bool = False) -> None:
|
|
126
|
+
if isinstance(node, Comment):
|
|
118
127
|
return
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
content = meta.get("content", "")
|
|
156
|
-
if name and content:
|
|
157
|
-
meta_tags[str(name)] = str(content)
|
|
158
|
-
if meta_tags:
|
|
159
|
-
page_data["meta"] = meta_tags
|
|
160
|
-
|
|
161
|
-
results.append(page_data)
|
|
162
|
-
|
|
163
|
-
# Enqueue links for deeper crawling if depth allows
|
|
164
|
-
if max_depth > 0:
|
|
165
|
-
await context.enqueue_links(strategy="same-domain")
|
|
166
|
-
|
|
167
|
-
await crawler.run([start_url])
|
|
128
|
+
if isinstance(node, NavigableString):
|
|
129
|
+
# Preserve code/pre whitespace exactly; normal HTML whitespace is
|
|
130
|
+
# collapsed without removing navigation/footer or document tails.
|
|
131
|
+
parts.append(str(node) if preformatted else re.sub(r"\s+", " ", str(node)))
|
|
132
|
+
return
|
|
133
|
+
if not isinstance(node, Tag):
|
|
134
|
+
return
|
|
135
|
+
for attribute in DESTINATION_ATTRIBUTES:
|
|
136
|
+
value = node.get(attribute)
|
|
137
|
+
if value is not None:
|
|
138
|
+
parts.append("\n[" + attribute + "=" + str(value) + "]\n")
|
|
139
|
+
if node.name == "img" and node.get("alt") is not None:
|
|
140
|
+
parts.append(str(node.get("alt")))
|
|
141
|
+
if node.name in ("script", "style", "template"):
|
|
142
|
+
return
|
|
143
|
+
block = node.name in ("address", "article", "aside", "blockquote", "br", "caption", "div", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "li", "main", "nav", "p", "pre", "section", "table", "tr")
|
|
144
|
+
if block:
|
|
145
|
+
parts.append("\n")
|
|
146
|
+
for child in node.children:
|
|
147
|
+
visit(child, preformatted or node.name in ("pre", "code"))
|
|
148
|
+
if node.name in ("td", "th"):
|
|
149
|
+
parts.append("\t")
|
|
150
|
+
if block:
|
|
151
|
+
parts.append("\n")
|
|
152
|
+
visit(body)
|
|
153
|
+
title = soup.find("title")
|
|
154
|
+
return {"url": url, "title": title.get_text(" ", strip=True) if title else "", "text": "".join(parts),
|
|
155
|
+
"html_snapshot": snapshot, "links": links, "metadata": metadata, "structured_data": structured,
|
|
156
|
+
"text_coverage": {"scope": "all_static_body_text", "rendered": False,
|
|
157
|
+
"excluded_payload_elements": ["script", "style", "template"],
|
|
158
|
+
"included_attributes": list(DESTINATION_ATTRIBUTES)}}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def checked_results(results: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
162
|
+
if len(json.dumps(results, ensure_ascii=False).encode("utf-8")) > MAX_RESULT_BYTES:
|
|
163
|
+
raise ValueError("Crawl exceeded the complete-result resource limit; no partial success was retained")
|
|
168
164
|
return results
|
|
169
165
|
|
|
170
166
|
|
|
171
|
-
|
|
172
|
-
# Playwright crawler
|
|
173
|
-
# ---------------------------------------------------------------------------
|
|
174
|
-
|
|
175
|
-
async def crawl_playwright(
|
|
176
|
-
start_url: str,
|
|
177
|
-
max_pages: int = 5,
|
|
178
|
-
max_depth: int = 1,
|
|
179
|
-
extract: str = "all",
|
|
180
|
-
) -> list[dict[str, Any]]:
|
|
181
|
-
"""Crawl using Playwright (headless browser, JS rendering)."""
|
|
182
|
-
from crawlee.playwright_crawler import PlaywrightCrawler, PlaywrightCrawlingContext
|
|
167
|
+
async def crawl_pages(start_url: str, max_pages: int, max_depth: int, rendered: bool) -> list[dict[str, Any]]:
|
|
183
168
|
from crawlee import ConcurrencySettings
|
|
184
|
-
|
|
169
|
+
if rendered:
|
|
170
|
+
from crawlee.playwright_crawler import PlaywrightCrawler as Crawler
|
|
171
|
+
else:
|
|
172
|
+
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawler as Crawler
|
|
185
173
|
results: list[dict[str, Any]] = []
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
174
|
+
visited: set[str] = set()
|
|
175
|
+
frontier = [start_url]
|
|
176
|
+
domain = urlparse(start_url).netloc
|
|
177
|
+
# Breadth-first explicit levels avoid relying on crawler-specific private
|
|
178
|
+
# request depth fields. max_pages and max_depth define requested scope.
|
|
179
|
+
for depth in range(max_depth + 1):
|
|
180
|
+
urls = [url for url in frontier if url not in visited][:max_pages - len(results)]
|
|
181
|
+
if not urls:
|
|
182
|
+
break
|
|
183
|
+
failures: list[str] = []
|
|
184
|
+
crawler_options: dict[str, Any] = {
|
|
185
|
+
"max_request_retries": 2,
|
|
186
|
+
"request_handler_timeout": timedelta(seconds=60 if rendered else 30),
|
|
187
|
+
"max_requests_per_crawl": len(urls),
|
|
188
|
+
"concurrency_settings": ConcurrencySettings(max_concurrency=1),
|
|
189
|
+
}
|
|
190
|
+
if rendered:
|
|
191
|
+
crawler_options.update({"headless": True, "browser_type": "chromium"})
|
|
192
|
+
crawler = Crawler(**crawler_options)
|
|
193
|
+
|
|
194
|
+
@crawler.router.default_handler
|
|
195
|
+
async def handler(context: Any) -> None:
|
|
196
|
+
url = context.request.url
|
|
197
|
+
if rendered:
|
|
198
|
+
from bs4 import BeautifulSoup
|
|
199
|
+
await context.page.wait_for_load_state("domcontentloaded", timeout=15000)
|
|
200
|
+
soup = BeautifulSoup(await context.page.content(), "html.parser")
|
|
201
|
+
else:
|
|
202
|
+
soup = context.soup
|
|
203
|
+
page = extract_page(soup, url)
|
|
204
|
+
page["depth"] = depth
|
|
205
|
+
page["text_coverage"]["rendered"] = rendered
|
|
206
|
+
# Record an observed status only when the actual response exposes it.
|
|
207
|
+
response = getattr(context, "response", None) if rendered else getattr(context, "http_response", None)
|
|
208
|
+
status = getattr(response, "status", None) if rendered else getattr(response, "status_code", None)
|
|
209
|
+
if isinstance(status, int):
|
|
210
|
+
if status >= 400:
|
|
211
|
+
raise RuntimeError(f"Page request failed with observed HTTP {status}")
|
|
212
|
+
page["observed_status"] = status
|
|
213
|
+
results.append(page)
|
|
214
|
+
checked_results(results)
|
|
215
|
+
|
|
216
|
+
@crawler.failed_request_handler
|
|
217
|
+
async def failed(context: Any, error: Exception) -> None:
|
|
218
|
+
failures.append(str(context.request.url))
|
|
219
|
+
|
|
220
|
+
await crawler.run(urls)
|
|
221
|
+
if failures or len(results) < len(visited) + len(urls):
|
|
222
|
+
raise RuntimeError("Crawl did not retrieve all dispatched pages; no partial success was retained")
|
|
223
|
+
visited.update(urls)
|
|
224
|
+
if len(results) >= max_pages:
|
|
225
|
+
break
|
|
226
|
+
frontier = []
|
|
227
|
+
seen = set(visited)
|
|
228
|
+
for page in results:
|
|
229
|
+
if page["depth"] != depth:
|
|
230
|
+
continue
|
|
231
|
+
for link in page["links"]:
|
|
232
|
+
candidate = link["url"]
|
|
233
|
+
parsed = urlparse(candidate)
|
|
234
|
+
if parsed.scheme in ("http", "https") and parsed.netloc == domain and candidate not in seen:
|
|
235
|
+
frontier.append(candidate)
|
|
236
|
+
seen.add(candidate)
|
|
237
|
+
return checked_results(results)
|
|
198
238
|
|
|
199
|
-
@crawler.router.default_handler
|
|
200
|
-
async def handler(context: PlaywrightCrawlingContext) -> None:
|
|
201
|
-
nonlocal pages_crawled
|
|
202
|
-
if pages_crawled >= max_pages:
|
|
203
|
-
return
|
|
204
|
-
pages_crawled += 1
|
|
205
239
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
"url": context.request.url,
|
|
209
|
-
"status": 200,
|
|
210
|
-
}
|
|
240
|
+
async def crawl_beautifulsoup(start_url: str, max_pages: int = 5, max_depth: int = 1, extract: str = "all") -> list[dict[str, Any]]:
|
|
241
|
+
return await crawl_pages(start_url, max_pages, max_depth, False)
|
|
211
242
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
except Exception:
|
|
216
|
-
await page.wait_for_load_state("domcontentloaded", timeout=10000)
|
|
217
|
-
|
|
218
|
-
# Extract title
|
|
219
|
-
page_data["title"] = await page.title()
|
|
220
|
-
|
|
221
|
-
# Extract text content
|
|
222
|
-
if extract in ("text", "all"):
|
|
223
|
-
# Remove non-content elements via JS
|
|
224
|
-
text = await page.evaluate("""() => {
|
|
225
|
-
const remove = document.querySelectorAll('script, style, nav, footer, header, [role="navigation"]');
|
|
226
|
-
remove.forEach(el => el.remove());
|
|
227
|
-
return document.body ? document.body.innerText : '';
|
|
228
|
-
}""")
|
|
229
|
-
lines = [line.strip() for line in str(text).splitlines() if line.strip()]
|
|
230
|
-
page_data["text"] = "\n".join(lines)
|
|
231
|
-
|
|
232
|
-
# Extract links
|
|
233
|
-
if extract in ("links", "all"):
|
|
234
|
-
links = await page.evaluate("""() => {
|
|
235
|
-
const anchors = document.querySelectorAll('a[href]');
|
|
236
|
-
return Array.from(anchors).slice(0, 50).map(a => ({
|
|
237
|
-
url: a.href,
|
|
238
|
-
text: a.innerText.trim()
|
|
239
|
-
})).filter(l => l.url.startsWith('http'));
|
|
240
|
-
}""")
|
|
241
|
-
page_data["links"] = links
|
|
242
|
-
|
|
243
|
-
# Extract meta tags
|
|
244
|
-
meta_tags = await page.evaluate("""() => {
|
|
245
|
-
const metas = document.querySelectorAll('meta[name], meta[property]');
|
|
246
|
-
const result = {};
|
|
247
|
-
metas.forEach(m => {
|
|
248
|
-
const key = m.getAttribute('name') || m.getAttribute('property');
|
|
249
|
-
const val = m.getAttribute('content');
|
|
250
|
-
if (key && val) result[key] = val;
|
|
251
|
-
});
|
|
252
|
-
return result;
|
|
253
|
-
}""")
|
|
254
|
-
if meta_tags:
|
|
255
|
-
page_data["meta"] = meta_tags
|
|
256
|
-
|
|
257
|
-
results.append(page_data)
|
|
258
|
-
|
|
259
|
-
# Enqueue links for deeper crawling
|
|
260
|
-
if max_depth > 0:
|
|
261
|
-
await context.enqueue_links(strategy="same-domain")
|
|
262
|
-
|
|
263
|
-
await crawler.run([start_url])
|
|
264
|
-
return results
|
|
243
|
+
|
|
244
|
+
async def crawl_playwright(start_url: str, max_pages: int = 5, max_depth: int = 1, extract: str = "all") -> list[dict[str, Any]]:
|
|
245
|
+
return await crawl_pages(start_url, max_pages, max_depth, True)
|
|
265
246
|
|
|
266
247
|
|
|
267
248
|
# ---------------------------------------------------------------------------
|
|
@@ -297,6 +278,8 @@ async def main():
|
|
|
297
278
|
)
|
|
298
279
|
|
|
299
280
|
args = parser.parse_args()
|
|
281
|
+
if not 1 <= args.max_pages <= 50 or not 0 <= args.max_depth <= 5:
|
|
282
|
+
parser.error("max-pages must be 1..50 and max-depth must be 0..5")
|
|
300
283
|
|
|
301
284
|
# Ensure crawlee is installed
|
|
302
285
|
if not ensure_crawlee():
|
|
@@ -332,26 +315,23 @@ async def main():
|
|
|
332
315
|
print(json.dumps(result))
|
|
333
316
|
sys.exit(1)
|
|
334
317
|
|
|
335
|
-
#
|
|
318
|
+
# Both display formats retain the complete structured source, including
|
|
319
|
+
# metadata and JSON-LD. Refuse an oversized result instead of a prefix.
|
|
320
|
+
output = {
|
|
321
|
+
"success": True,
|
|
322
|
+
"strategy": args.strategy,
|
|
323
|
+
"content_complete": True,
|
|
324
|
+
"requested_scope": {"max_pages": args.max_pages, "max_depth": args.max_depth},
|
|
325
|
+
"pages_crawled": len(results),
|
|
326
|
+
"results": results,
|
|
327
|
+
}
|
|
328
|
+
serialized = json.dumps(output, ensure_ascii=False, indent=2)
|
|
329
|
+
if len(serialized.encode("utf-8")) > MAX_RESULT_BYTES:
|
|
330
|
+
print(json.dumps({"error": "Crawl exceeded the complete-result resource limit; no partial success was retained"}))
|
|
331
|
+
sys.exit(1)
|
|
336
332
|
if args.output == "text":
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
print(f"URL: {page.get('url', '')}")
|
|
340
|
-
if "text" in page:
|
|
341
|
-
print(page["text"][:5000])
|
|
342
|
-
if "links" in page:
|
|
343
|
-
print(f"\nLinks ({len(page['links'])}):")
|
|
344
|
-
for link in page["links"][:20]:
|
|
345
|
-
print(f" - {link.get('text', '')}: {link.get('url', '')}")
|
|
346
|
-
print()
|
|
347
|
-
else:
|
|
348
|
-
output = {
|
|
349
|
-
"success": True,
|
|
350
|
-
"strategy": args.strategy,
|
|
351
|
-
"pages_crawled": len(results),
|
|
352
|
-
"results": results,
|
|
353
|
-
}
|
|
354
|
-
print(json.dumps(output, ensure_ascii=False, indent=2))
|
|
333
|
+
print("Complete structured crawl source:")
|
|
334
|
+
print(serialized)
|
|
355
335
|
|
|
356
336
|
|
|
357
337
|
if __name__ == "__main__":
|
|
@@ -184,8 +184,6 @@ def _serialize_script_result(value, depth: int = 0, seen: Optional[set[int]] = N
|
|
|
184
184
|
seen = set()
|
|
185
185
|
if value is None or isinstance(value, (str, int, float, bool)):
|
|
186
186
|
return value
|
|
187
|
-
if depth > 5:
|
|
188
|
-
return str(value)
|
|
189
187
|
if isinstance(value, WebElement):
|
|
190
188
|
try:
|
|
191
189
|
rect = value.rect or {}
|
|
@@ -212,7 +210,8 @@ def _serialize_script_result(value, depth: int = 0, seen: Optional[set[int]] = N
|
|
|
212
210
|
"type": attr("type"),
|
|
213
211
|
"role": attr("role"),
|
|
214
212
|
"ariaLabel": attr("aria-label"),
|
|
215
|
-
"text": text
|
|
213
|
+
"text": text,
|
|
214
|
+
"__omnius_observation_scope": "element_descriptor",
|
|
216
215
|
"rect": {
|
|
217
216
|
"x": rect.get("x", 0),
|
|
218
217
|
"y": rect.get("y", 0),
|
|
@@ -220,21 +219,25 @@ def _serialize_script_result(value, depth: int = 0, seen: Optional[set[int]] = N
|
|
|
220
219
|
"height": rect.get("height", 0),
|
|
221
220
|
},
|
|
222
221
|
}
|
|
223
|
-
if isinstance(value, (list, tuple
|
|
224
|
-
|
|
222
|
+
if isinstance(value, (list, tuple)):
|
|
223
|
+
ident = id(value)
|
|
224
|
+
if ident in seen:
|
|
225
|
+
raise ValueError("Circular evaluation result is not JSON serializable")
|
|
226
|
+
seen.add(ident)
|
|
227
|
+
try:
|
|
228
|
+
return [_serialize_script_result(item, depth + 1, seen) for item in value]
|
|
229
|
+
finally:
|
|
230
|
+
seen.remove(ident)
|
|
225
231
|
if isinstance(value, dict):
|
|
226
232
|
ident = id(value)
|
|
227
233
|
if ident in seen:
|
|
228
|
-
|
|
234
|
+
raise ValueError("Circular evaluation result is not JSON serializable")
|
|
229
235
|
seen.add(ident)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
out[str(key)] = _serialize_script_result(item, depth + 1, seen)
|
|
236
|
-
return out
|
|
237
|
-
return str(value)
|
|
236
|
+
try:
|
|
237
|
+
return {str(key): _serialize_script_result(item, depth + 1, seen) for key, item in value.items()}
|
|
238
|
+
finally:
|
|
239
|
+
seen.remove(ident)
|
|
240
|
+
raise ValueError(f"Unsupported evaluation result type: {type(value).__name__}")
|
|
238
241
|
|
|
239
242
|
|
|
240
243
|
class Tools:
|
|
@@ -486,13 +489,11 @@ class Tools:
|
|
|
486
489
|
return f"Error typing into {selector}: {e}"
|
|
487
490
|
|
|
488
491
|
@staticmethod
|
|
489
|
-
def get_dom_snapshot(
|
|
492
|
+
def get_dom_snapshot() -> str:
|
|
490
493
|
if not Tools._driver:
|
|
491
494
|
return ""
|
|
492
495
|
try:
|
|
493
496
|
dom = Tools._driver.execute_script("return document.documentElement.outerHTML;")
|
|
494
|
-
if dom and len(dom) > max_chars:
|
|
495
|
-
dom = dom[:max_chars]
|
|
496
497
|
return dom or ""
|
|
497
498
|
except Exception as exc:
|
|
498
499
|
log_message(f"[dom] snapshot failed: {exc}", "WARNING")
|
|
@@ -508,6 +509,7 @@ class Tools:
|
|
|
508
509
|
"ok": True,
|
|
509
510
|
"result": _serialize_script_result(result),
|
|
510
511
|
"result_type": "undefined" if result is None else type(result).__name__,
|
|
512
|
+
"url": Tools._driver.current_url,
|
|
511
513
|
}
|
|
512
514
|
except Exception as exc:
|
|
513
515
|
log_message(f"[evaluate] script failed: {exc}", "ERROR")
|
|
@@ -1155,7 +1157,7 @@ def evaluate_script():
|
|
|
1155
1157
|
return _error(result.get("error") if isinstance(result, dict) else "evaluate failed", 500)
|
|
1156
1158
|
sid = data.get("sid") or next(iter(_SESSIONS), "")
|
|
1157
1159
|
_queue_event(sid, {"type": "status", "msg": "evaluate", "ts": int(time.time() * 1000)})
|
|
1158
|
-
return _ok(result=result.get("result"), result_type=result.get("result_type"))
|
|
1160
|
+
return _ok(result=result.get("result"), result_type=result.get("result_type"), url=result.get("url"), content_coverage="complete_returned_view")
|
|
1159
1161
|
|
|
1160
1162
|
|
|
1161
1163
|
@app.post("/scroll")
|
|
@@ -1430,12 +1432,12 @@ def drag():
|
|
|
1430
1432
|
def dom_snapshot():
|
|
1431
1433
|
if not _auth_ok(request):
|
|
1432
1434
|
return _error("unauthorized", 401)
|
|
1433
|
-
html = Tools.get_dom_snapshot(
|
|
1435
|
+
html = Tools.get_dom_snapshot()
|
|
1434
1436
|
if not html:
|
|
1435
1437
|
return _error("no dom (browser closed?)", 409)
|
|
1436
1438
|
sid = request.args.get("sid") or next(iter(_SESSIONS), "")
|
|
1437
1439
|
_queue_event(sid, {"type": "dom", "chars": len(html), "ts": int(time.time() * 1000)})
|
|
1438
|
-
return _ok(dom=html, length=len(html))
|
|
1440
|
+
return _ok(dom=html, length=len(html), url=Tools._driver.current_url if Tools._driver else None, content_coverage="complete_returned_view")
|
|
1439
1441
|
|
|
1440
1442
|
|
|
1441
1443
|
def _record_frame_meta(sid: str, fname: str, width: int, height: int) -> None:
|
package/dist/update-worker.js
CHANGED
|
@@ -250778,7 +250778,7 @@ var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
|
250778
250778
|
var MAX_SEARCH_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
250779
250779
|
|
|
250780
250780
|
// packages/execution/dist/tools/web-crawl.js
|
|
250781
|
-
var
|
|
250781
|
+
var MAX_CRAWL_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
250782
250782
|
|
|
250783
250783
|
// packages/execution/dist/tools/osint-catalog-data.js
|
|
250784
250784
|
var OSINT_CATALOG_RESOURCES = [
|
|
@@ -295733,10 +295733,10 @@ for (const resource of OSINT_CATALOG_RESOURCES) {
|
|
|
295733
295733
|
}
|
|
295734
295734
|
|
|
295735
295735
|
// packages/execution/dist/tools/carbonyl-browser.js
|
|
295736
|
-
init_venv_paths();
|
|
295737
|
-
init_process_lifecycle();
|
|
295738
295736
|
import { homedir as homedir4 } from "node:os";
|
|
295739
295737
|
import { join as join3 } from "node:path";
|
|
295738
|
+
init_venv_paths();
|
|
295739
|
+
init_process_lifecycle();
|
|
295740
295740
|
|
|
295741
295741
|
// packages/execution/dist/tools/playwright-browser.js
|
|
295742
295742
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
@@ -298686,10 +298686,10 @@ var MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
|
298686
298686
|
init_process_async();
|
|
298687
298687
|
|
|
298688
298688
|
// packages/execution/dist/tools/browser-action.js
|
|
298689
|
-
init_process_lifecycle();
|
|
298690
298689
|
import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
298691
298690
|
import { basename as basename2, dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
|
|
298692
298691
|
import { fileURLToPath } from "node:url";
|
|
298692
|
+
init_process_lifecycle();
|
|
298693
298693
|
init_model_store();
|
|
298694
298694
|
var __dirname2 = dirname4(fileURLToPath(import.meta.url));
|
|
298695
298695
|
var DEFAULT_PORT = 8130;
|