abtars 0.1.0-alpha.1
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/LICENSE +190 -0
- package/README.md +84 -0
- package/bundle/_registry.generated-M4WY2MMI.js +35 -0
- package/bundle/_registry.generated-M4WY2MMI.js.map +7 -0
- package/bundle/abtars-browser.js +162 -0
- package/bundle/abtars-browser.js.map +7 -0
- package/bundle/abtars-cli.js +1438 -0
- package/bundle/abtars-cli.js.map +7 -0
- package/bundle/abtars-restart.js +12 -0
- package/bundle/abtars-restart.js.map +7 -0
- package/bundle/abtars-rss.js +165 -0
- package/bundle/abtars-rss.js.map +7 -0
- package/bundle/abtars-task.js +258 -0
- package/bundle/abtars-task.js.map +7 -0
- package/bundle/abtars.js +4072 -0
- package/bundle/abtars.js.map +7 -0
- package/bundle/agent-api-rate-limit-OQNFMXTZ.js +38 -0
- package/bundle/agent-api-rate-limit-OQNFMXTZ.js.map +7 -0
- package/bundle/agent-registry-LT4JNQH6.js +18 -0
- package/bundle/agent-registry-LT4JNQH6.js.map +7 -0
- package/bundle/agents/default.md +29 -0
- package/bundle/anthropic-adapter-2APTH3LA.js +40 -0
- package/bundle/anthropic-adapter-2APTH3LA.js.map +7 -0
- package/bundle/bridge-lock-transport-4AC2G5G6.js +39 -0
- package/bundle/bridge-lock-transport-4AC2G5G6.js.map +7 -0
- package/bundle/browse-delivery-JXBY36GK.js +17 -0
- package/bundle/browse-delivery-JXBY36GK.js.map +7 -0
- package/bundle/browser-ELNDVPLC.js +18 -0
- package/bundle/browser-ELNDVPLC.js.map +7 -0
- package/bundle/capability-CIL3G4FI.js +17 -0
- package/bundle/capability-CIL3G4FI.js.map +7 -0
- package/bundle/chunk-265TPOPC.js +289 -0
- package/bundle/chunk-265TPOPC.js.map +7 -0
- package/bundle/chunk-2UENBO6M.js +223 -0
- package/bundle/chunk-2UENBO6M.js.map +7 -0
- package/bundle/chunk-2UPU3OW6.js +67 -0
- package/bundle/chunk-2UPU3OW6.js.map +7 -0
- package/bundle/chunk-2XU2X4OI.js +125 -0
- package/bundle/chunk-2XU2X4OI.js.map +7 -0
- package/bundle/chunk-3B7BBE4F.js +758 -0
- package/bundle/chunk-3B7BBE4F.js.map +7 -0
- package/bundle/chunk-3E545J66.js +69 -0
- package/bundle/chunk-3E545J66.js.map +7 -0
- package/bundle/chunk-5R2ANXQ7.js +510 -0
- package/bundle/chunk-5R2ANXQ7.js.map +7 -0
- package/bundle/chunk-6CPN4IGS.js +507 -0
- package/bundle/chunk-6CPN4IGS.js.map +7 -0
- package/bundle/chunk-6NR3OHEW.js +88 -0
- package/bundle/chunk-6NR3OHEW.js.map +7 -0
- package/bundle/chunk-6SETMHNN.js +206 -0
- package/bundle/chunk-6SETMHNN.js.map +7 -0
- package/bundle/chunk-6UCRKRWR.js +644 -0
- package/bundle/chunk-6UCRKRWR.js.map +7 -0
- package/bundle/chunk-AR6GO6YC.js +83 -0
- package/bundle/chunk-AR6GO6YC.js.map +7 -0
- package/bundle/chunk-AZJIODTQ.js +54 -0
- package/bundle/chunk-AZJIODTQ.js.map +7 -0
- package/bundle/chunk-BHMZ4RCC.js +3706 -0
- package/bundle/chunk-BHMZ4RCC.js.map +7 -0
- package/bundle/chunk-BQ2L4GMG.js +9175 -0
- package/bundle/chunk-BQ2L4GMG.js.map +7 -0
- package/bundle/chunk-BSSBCSCL.js +159 -0
- package/bundle/chunk-BSSBCSCL.js.map +7 -0
- package/bundle/chunk-BUUVFUPO.js +157 -0
- package/bundle/chunk-BUUVFUPO.js.map +7 -0
- package/bundle/chunk-CEVRHKJY.js +131 -0
- package/bundle/chunk-CEVRHKJY.js.map +7 -0
- package/bundle/chunk-CWOHNFUV.js +39 -0
- package/bundle/chunk-CWOHNFUV.js.map +7 -0
- package/bundle/chunk-D2DCBO6M.js +228 -0
- package/bundle/chunk-D2DCBO6M.js.map +7 -0
- package/bundle/chunk-FMWKEPM7.js +31 -0
- package/bundle/chunk-FMWKEPM7.js.map +7 -0
- package/bundle/chunk-GRNENTPA.js +145 -0
- package/bundle/chunk-GRNENTPA.js.map +7 -0
- package/bundle/chunk-GST5T3WZ.js +93 -0
- package/bundle/chunk-GST5T3WZ.js.map +7 -0
- package/bundle/chunk-GUQVJC3U.js +299 -0
- package/bundle/chunk-GUQVJC3U.js.map +7 -0
- package/bundle/chunk-HX7Y7EYP.js +3659 -0
- package/bundle/chunk-HX7Y7EYP.js.map +7 -0
- package/bundle/chunk-JCJS4ZIB.js +296 -0
- package/bundle/chunk-JCJS4ZIB.js.map +7 -0
- package/bundle/chunk-JW6RU47G.js +184 -0
- package/bundle/chunk-JW6RU47G.js.map +7 -0
- package/bundle/chunk-LSPKJQCI.js +24 -0
- package/bundle/chunk-LSPKJQCI.js.map +7 -0
- package/bundle/chunk-M6VBAPNT.js +16 -0
- package/bundle/chunk-M6VBAPNT.js.map +7 -0
- package/bundle/chunk-MPX525QO.js +129 -0
- package/bundle/chunk-MPX525QO.js.map +7 -0
- package/bundle/chunk-MW6WDLU7.js +130 -0
- package/bundle/chunk-MW6WDLU7.js.map +7 -0
- package/bundle/chunk-NT3OBORC.js +215 -0
- package/bundle/chunk-NT3OBORC.js.map +7 -0
- package/bundle/chunk-NWDBD4PA.js +50 -0
- package/bundle/chunk-NWDBD4PA.js.map +7 -0
- package/bundle/chunk-OP7BTAWY.js +29 -0
- package/bundle/chunk-OP7BTAWY.js.map +7 -0
- package/bundle/chunk-PLCY3GFH.js +77 -0
- package/bundle/chunk-PLCY3GFH.js.map +7 -0
- package/bundle/chunk-PNEDC45Y.js +97 -0
- package/bundle/chunk-PNEDC45Y.js.map +7 -0
- package/bundle/chunk-QBGBT5QS.js +81 -0
- package/bundle/chunk-QBGBT5QS.js.map +7 -0
- package/bundle/chunk-RVE2N7FA.js +70 -0
- package/bundle/chunk-RVE2N7FA.js.map +7 -0
- package/bundle/chunk-TZHIDLDS.js +71910 -0
- package/bundle/chunk-TZHIDLDS.js.map +7 -0
- package/bundle/chunk-UCQ2WC3B.js +126 -0
- package/bundle/chunk-UCQ2WC3B.js.map +7 -0
- package/bundle/chunk-UHRP745J.js +214 -0
- package/bundle/chunk-UHRP745J.js.map +7 -0
- package/bundle/chunk-V76TVMCM.js +58 -0
- package/bundle/chunk-V76TVMCM.js.map +7 -0
- package/bundle/chunk-VVEDVGCR.js +981 -0
- package/bundle/chunk-VVEDVGCR.js.map +7 -0
- package/bundle/chunk-W6FAL35D.js +102 -0
- package/bundle/chunk-W6FAL35D.js.map +7 -0
- package/bundle/chunk-X6TERNVJ.js +15902 -0
- package/bundle/chunk-X6TERNVJ.js.map +7 -0
- package/bundle/chunk-X76UX47U.js +47 -0
- package/bundle/chunk-X76UX47U.js.map +7 -0
- package/bundle/chunk-XREWVCUO.js +518 -0
- package/bundle/chunk-XREWVCUO.js.map +7 -0
- package/bundle/chunk-Y6XAEX2Q.js +408 -0
- package/bundle/chunk-Y6XAEX2Q.js.map +7 -0
- package/bundle/chunk-YOCTDKKL.js +28 -0
- package/bundle/chunk-YOCTDKKL.js.map +7 -0
- package/bundle/chunk-ZXPXCDA6.js +160 -0
- package/bundle/chunk-ZXPXCDA6.js.map +7 -0
- package/bundle/commands-BHVUOU3V.js +31 -0
- package/bundle/commands-BHVUOU3V.js.map +7 -0
- package/bundle/completion-buffer-P253ONKF.js +13 -0
- package/bundle/completion-buffer-P253ONKF.js.map +7 -0
- package/bundle/config-RGSDAPZN.js +19 -0
- package/bundle/config-RGSDAPZN.js.map +7 -0
- package/bundle/config-show-ERTATR6E.js +40 -0
- package/bundle/config-show-ERTATR6E.js.map +7 -0
- package/bundle/context-HCEGZNDC.js +72 -0
- package/bundle/context-HCEGZNDC.js.map +7 -0
- package/bundle/delegation-tools-GYTS2D6A.js +27 -0
- package/bundle/delegation-tools-GYTS2D6A.js.map +7 -0
- package/bundle/deploy-lib-import-32ZFKHWP.js +49 -0
- package/bundle/deploy-lib-import-32ZFKHWP.js.map +7 -0
- package/bundle/digital-signature-OFCGSHWO.js +13 -0
- package/bundle/digital-signature-OFCGSHWO.js.map +7 -0
- package/bundle/direct-api-transport-YR7SXXNN.js +860 -0
- package/bundle/direct-api-transport-YR7SXXNN.js.map +7 -0
- package/bundle/discord-adapter-YYWVMPPU.js +584 -0
- package/bundle/discord-adapter-YYWVMPPU.js.map +7 -0
- package/bundle/dist-MTMKARCP.js +1969 -0
- package/bundle/dist-MTMKARCP.js.map +7 -0
- package/bundle/dns-wakeup-27M7D2MR.js +107 -0
- package/bundle/dns-wakeup-27M7D2MR.js.map +7 -0
- package/bundle/doctor-QNUSDY73.js +248 -0
- package/bundle/doctor-QNUSDY73.js.map +7 -0
- package/bundle/ensure-invariants-NMXNS476.js +49 -0
- package/bundle/ensure-invariants-NMXNS476.js.map +7 -0
- package/bundle/env-schema-2KBHBDGN.js +19 -0
- package/bundle/env-schema-2KBHBDGN.js.map +7 -0
- package/bundle/esm-DDP6NCZG.js +100663 -0
- package/bundle/esm-DDP6NCZG.js.map +7 -0
- package/bundle/fallback-policy-L4QV2PEJ.js +46 -0
- package/bundle/fallback-policy-L4QV2PEJ.js.map +7 -0
- package/bundle/health-check-SPA7NT6N.js +56 -0
- package/bundle/health-check-SPA7NT6N.js.map +7 -0
- package/bundle/hook-system-6Q5YTR53.js +17 -0
- package/bundle/hook-system-6Q5YTR53.js.map +7 -0
- package/bundle/hotskills-K7BM4YLB.js +12 -0
- package/bundle/hotskills-K7BM4YLB.js.map +7 -0
- package/bundle/install-6HRZVKUM.js +15 -0
- package/bundle/install-6HRZVKUM.js.map +7 -0
- package/bundle/install-log-IAPHYKD4.js +28 -0
- package/bundle/install-log-IAPHYKD4.js.map +7 -0
- package/bundle/install-manifest-SPQRUNXL.js +102 -0
- package/bundle/install-manifest-SPQRUNXL.js.map +7 -0
- package/bundle/install-validate-PVLZXYLQ.js +53 -0
- package/bundle/install-validate-PVLZXYLQ.js.map +7 -0
- package/bundle/irc-adapter-OI5UZSQF.js +293 -0
- package/bundle/irc-adapter-OI5UZSQF.js.map +7 -0
- package/bundle/irc-config-55YO6EGB.js +88 -0
- package/bundle/irc-config-55YO6EGB.js.map +7 -0
- package/bundle/logs-ZNYXX5PA.js +19 -0
- package/bundle/logs-ZNYXX5PA.js.map +7 -0
- package/bundle/media-utils-XNNDTYFI.js +4662 -0
- package/bundle/media-utils-XNNDTYFI.js.map +7 -0
- package/bundle/message-pipeline-LLH5SYMO.js +33 -0
- package/bundle/message-pipeline-LLH5SYMO.js.map +7 -0
- package/bundle/meta.json +41304 -0
- package/bundle/model-health-registry-35LQNVQR.js +11 -0
- package/bundle/model-health-registry-35LQNVQR.js.map +7 -0
- package/bundle/notification-Y5S5MMLV.js +13 -0
- package/bundle/notification-Y5S5MMLV.js.map +7 -0
- package/bundle/openrouter-credits-EDY7ETAU.js +32 -0
- package/bundle/openrouter-credits-EDY7ETAU.js.map +7 -0
- package/bundle/passwd-RRFV4CC5.js +133 -0
- package/bundle/passwd-RRFV4CC5.js.map +7 -0
- package/bundle/paths-G33RZWZ7.js +17 -0
- package/bundle/paths-G33RZWZ7.js.map +7 -0
- package/bundle/peer-client-52XYMNI7.js +156 -0
- package/bundle/peer-client-52XYMNI7.js.map +7 -0
- package/bundle/peer-config-VK6EDLN5.js +16 -0
- package/bundle/peer-config-VK6EDLN5.js.map +7 -0
- package/bundle/peer-sessions-EAXTNQ36.js +49 -0
- package/bundle/peer-sessions-EAXTNQ36.js.map +7 -0
- package/bundle/pending-callback-RIMQZ7FJ.js +40 -0
- package/bundle/pending-callback-RIMQZ7FJ.js.map +7 -0
- package/bundle/phase-transport-KYERDL2O.js +22 -0
- package/bundle/phase-transport-KYERDL2O.js.map +7 -0
- package/bundle/public/css/dashboard.css +542 -0
- package/bundle/public/index.html +180 -0
- package/bundle/public/js/app.js +437 -0
- package/bundle/public/memory-universe.js +384 -0
- package/bundle/responses-adapter-AAQTY3K4.js +30 -0
- package/bundle/responses-adapter-AAQTY3K4.js.map +7 -0
- package/bundle/restore-ZE3SEPSS.js +46 -0
- package/bundle/restore-ZE3SEPSS.js.map +7 -0
- package/bundle/self-healer-utils-DMUUXC47.js +43 -0
- package/bundle/self-healer-utils-DMUUXC47.js.map +7 -0
- package/bundle/skill-stats-LLEXEXLR.js +22 -0
- package/bundle/skill-stats-LLEXEXLR.js.map +7 -0
- package/bundle/sleep-OYIUOVQD.js +19 -0
- package/bundle/sleep-OYIUOVQD.js.map +7 -0
- package/bundle/soul-loader-54WCVNLJ.js +16 -0
- package/bundle/soul-loader-54WCVNLJ.js.map +7 -0
- package/bundle/src-JL4PVO23.js +8 -0
- package/bundle/src-JL4PVO23.js.map +7 -0
- package/bundle/sse-parser-anthropic-P7CE2MH2.js +72 -0
- package/bundle/sse-parser-anthropic-P7CE2MH2.js.map +7 -0
- package/bundle/sse-parser-responses-EQQA5FWN.js +63 -0
- package/bundle/sse-parser-responses-EQQA5FWN.js.map +7 -0
- package/bundle/ssrf-guard-FZCBYIVW.js +64 -0
- package/bundle/ssrf-guard-FZCBYIVW.js.map +7 -0
- package/bundle/start-FH3GRMJ4.js +35 -0
- package/bundle/start-FH3GRMJ4.js.map +7 -0
- package/bundle/stream-single-WSG4D53C.js +33 -0
- package/bundle/stream-single-WSG4D53C.js.map +7 -0
- package/bundle/stt-2UH3RITX.js +14 -0
- package/bundle/stt-2UH3RITX.js.map +7 -0
- package/bundle/subagent-runtime-LE2ZXH3G.js +12 -0
- package/bundle/subagent-runtime-LE2ZXH3G.js.map +7 -0
- package/bundle/system-message-T5R3EYYN.js +30 -0
- package/bundle/system-message-T5R3EYYN.js.map +7 -0
- package/bundle/system-status-KQ6KHFJ6.js +189 -0
- package/bundle/system-status-KQ6KHFJ6.js.map +7 -0
- package/bundle/task-store-K7CQDEPI.js +22 -0
- package/bundle/task-store-K7CQDEPI.js.map +7 -0
- package/bundle/telegram-adapter-2V3XUMT5.js +1060 -0
- package/bundle/telegram-adapter-2V3XUMT5.js.map +7 -0
- package/bundle/tool-registry-MU3OX4UI.js +38 -0
- package/bundle/tool-registry-MU3OX4UI.js.map +7 -0
- package/bundle/tool-sandbox-VYOK4ZOA.js +20 -0
- package/bundle/tool-sandbox-VYOK4ZOA.js.map +7 -0
- package/bundle/transport-config-YLXU33RO.js +57 -0
- package/bundle/transport-config-YLXU33RO.js.map +7 -0
- package/bundle/update-QCW5LXRN.js +13 -0
- package/bundle/update-QCW5LXRN.js.map +7 -0
- package/bundle/update-check-27KZSAP6.js +12 -0
- package/bundle/update-check-27KZSAP6.js.map +7 -0
- package/bundle/usage-tracker-OVVEVMOY.js +17 -0
- package/bundle/usage-tracker-OVVEVMOY.js.map +7 -0
- package/bundle/user-registry-D4SD73UV.js +16 -0
- package/bundle/user-registry-D4SD73UV.js.map +7 -0
- package/core/professor.json +14 -0
- package/core/prompts/browsing_prompt.md +39 -0
- package/core/prompts/compaction.md +32 -0
- package/core/skills/memory/classification/SKILL.md +37 -0
- package/core/skills/memory/memory-anomalies/SKILL.md +39 -0
- package/core/skills/memory/memory-search/SKILL.md +48 -0
- package/core/skills/memory/topic-save/SKILL.md +44 -0
- package/core/skills/ops/cron/SKILL.md +51 -0
- package/core/skills/ops/gdrive-backup/SKILL.md +15 -0
- package/core/skills/ops/session-start/SKILL.md +11 -0
- package/core/skills/ops/skill-authoring/SKILL.md +54 -0
- package/core/skills/ops/system-health/SKILL.md +104 -0
- package/core/skills/ops/troubleshooting/SKILL.md +48 -0
- package/core/skills/ops/trust-gating/SKILL.md +30 -0
- package/core/skills/tools/a2a-communication/SKILL.md +68 -0
- package/core/skills/tools/browse-delegate/SKILL.md +27 -0
- package/core/skills/tools/browser/SKILL.md +36 -0
- package/core/skills/tools/clawhub/SKILL.md +44 -0
- package/core/skills/tools/delegation/SKILL.md +48 -0
- package/core/skills/tools/fxtwitter/SKILL.md +52 -0
- package/core/skills/tools/gmail/SKILL.md +44 -0
- package/core/skills/tools/irc-chat/SKILL.md +84 -0
- package/core/skills/tools/linear/SKILL.md +90 -0
- package/core/skills/tools/mcporter/SKILL.md +46 -0
- package/core/skills/tools/model-scout/SKILL.md +132 -0
- package/core/skills/tools/model-scout/scout-add-model.py +67 -0
- package/core/skills/tools/model-scout/scout-ollama.py +116 -0
- package/core/skills/tools/model-scout/scout-openrouter.py +85 -0
- package/core/skills/tools/nlm/SKILL.md +40 -0
- package/core/skills/tools/todo/SKILL.md +30 -0
- package/core/skills/tools/twitterX/SKILL.md +52 -0
- package/core/skills/tools/twitterX/scripts/abtars-tweet.js +532 -0
- package/core/skills/tools/twitterX/scripts/package.json +1 -0
- package/core/skills/tools/web-fetch/SKILL.md +29 -0
- package/package.json +59 -0
- package/scripts/abtars-daemon.service +23 -0
- package/scripts/abtars-fetch.sh +42 -0
- package/scripts/abtars-watchdog.service +13 -0
- package/scripts/abtars.sh +14 -0
- package/scripts/abtars@.service +21 -0
- package/scripts/browser-patchright.sh +79 -0
- package/scripts/com.abtars.daemon.plist +24 -0
- package/scripts/com.abtars.watchdog.plist +27 -0
- package/scripts/daily-backup.sh +62 -0
- package/scripts/doctor.sh +553 -0
- package/scripts/hooks/audit-logger.sh +22 -0
- package/scripts/upgrade-deps.sh +64 -0
- package/scripts/watchdog.sh +309 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import { createRequire as __bundleCreateRequire } from 'node:module'; import { fileURLToPath as __bundleFileURLToPath } from 'node:url'; import { dirname as __bundleDirname } from 'node:path'; const require = __bundleCreateRequire(import.meta.url); const __chunk_filename = __bundleFileURLToPath(import.meta.url); const __chunk_dirname = __bundleDirname(__chunk_filename);
|
|
2
|
+
import {
|
|
3
|
+
init_log_and_swallow,
|
|
4
|
+
logAndSwallow
|
|
5
|
+
} from "./chunk-FMWKEPM7.js";
|
|
6
|
+
import {
|
|
7
|
+
getEnv,
|
|
8
|
+
init_env_schema
|
|
9
|
+
} from "./chunk-JCJS4ZIB.js";
|
|
10
|
+
import {
|
|
11
|
+
init_logger,
|
|
12
|
+
logDebug,
|
|
13
|
+
logInfo,
|
|
14
|
+
logWarn
|
|
15
|
+
} from "./chunk-BUUVFUPO.js";
|
|
16
|
+
import {
|
|
17
|
+
abtarsHome,
|
|
18
|
+
init_paths
|
|
19
|
+
} from "./chunk-X76UX47U.js";
|
|
20
|
+
|
|
21
|
+
// src/capabilities/browser/browser-manager.ts
|
|
22
|
+
init_log_and_swallow();
|
|
23
|
+
init_logger();
|
|
24
|
+
init_env_schema();
|
|
25
|
+
import { chromium } from "patchright";
|
|
26
|
+
import { execFileSync } from "node:child_process";
|
|
27
|
+
var TAG = "browser_mgr";
|
|
28
|
+
function readPositiveInt(key, fallback) {
|
|
29
|
+
const raw = process.env[key];
|
|
30
|
+
if (!raw) return fallback;
|
|
31
|
+
const n = Number(raw);
|
|
32
|
+
return Number.isFinite(n) && Number.isInteger(n) && n > 0 ? n : fallback;
|
|
33
|
+
}
|
|
34
|
+
var DEFAULTS = {
|
|
35
|
+
BROWSER_SESSION_TIMEOUT_MS: 3e5,
|
|
36
|
+
BROWSER_MAX_SESSIONS: 3,
|
|
37
|
+
WEB_SCRAPE_USER_AGENT: "Mozilla/5.0 (compatible; Abtars/1.0)"
|
|
38
|
+
};
|
|
39
|
+
function parseBrowserConfig() {
|
|
40
|
+
const sessionTimeoutMs = readPositiveInt(
|
|
41
|
+
"BROWSER_SESSION_TIMEOUT_MS",
|
|
42
|
+
DEFAULTS.BROWSER_SESSION_TIMEOUT_MS
|
|
43
|
+
);
|
|
44
|
+
const maxSessions = readPositiveInt(
|
|
45
|
+
"BROWSER_MAX_SESSIONS",
|
|
46
|
+
DEFAULTS.BROWSER_MAX_SESSIONS
|
|
47
|
+
);
|
|
48
|
+
const userAgent = process.env["WEB_SCRAPE_USER_AGENT"]?.trim() || DEFAULTS.WEB_SCRAPE_USER_AGENT;
|
|
49
|
+
const engine = "patchright";
|
|
50
|
+
return { sessionTimeoutMs, maxSessions, userAgent, engine };
|
|
51
|
+
}
|
|
52
|
+
var BrowserManager = class _BrowserManager {
|
|
53
|
+
static _instance = null;
|
|
54
|
+
_browser = null;
|
|
55
|
+
_launching = null;
|
|
56
|
+
_sessions = /* @__PURE__ */ new Map();
|
|
57
|
+
_idleTimer = null;
|
|
58
|
+
_config;
|
|
59
|
+
_lastActivityAt = 0;
|
|
60
|
+
_containerIdleStopMs;
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this._config = config ?? parseBrowserConfig();
|
|
63
|
+
this._containerIdleStopMs = getEnv().browserIdleStopMin * 6e4;
|
|
64
|
+
this._startIdleCheck();
|
|
65
|
+
}
|
|
66
|
+
/** Get (or create) the singleton instance. */
|
|
67
|
+
static getInstance() {
|
|
68
|
+
if (!_BrowserManager._instance) {
|
|
69
|
+
_BrowserManager._instance = new _BrowserManager();
|
|
70
|
+
}
|
|
71
|
+
return _BrowserManager._instance;
|
|
72
|
+
}
|
|
73
|
+
/** Reset singleton — primarily for testing. */
|
|
74
|
+
static resetInstance() {
|
|
75
|
+
_BrowserManager._instance = null;
|
|
76
|
+
}
|
|
77
|
+
// -------------------------------------------------------------------------
|
|
78
|
+
// Browser lifecycle
|
|
79
|
+
// -------------------------------------------------------------------------
|
|
80
|
+
/** Lazily launch or return the existing browser instance. */
|
|
81
|
+
async _ensureBrowser() {
|
|
82
|
+
if (this._browser?.isConnected()) return this._browser;
|
|
83
|
+
if (this._launching) return this._launching;
|
|
84
|
+
this._launching = this._launchPatchright().then((browser) => {
|
|
85
|
+
this._browser = browser;
|
|
86
|
+
this._launching = null;
|
|
87
|
+
browser.on("disconnected", () => {
|
|
88
|
+
this._browser = null;
|
|
89
|
+
this._sessions.clear();
|
|
90
|
+
});
|
|
91
|
+
return browser;
|
|
92
|
+
}).catch((err) => {
|
|
93
|
+
this._launching = null;
|
|
94
|
+
throw err;
|
|
95
|
+
});
|
|
96
|
+
return this._launching;
|
|
97
|
+
}
|
|
98
|
+
async _launchPatchright() {
|
|
99
|
+
const headed = getEnv().browserHeaded;
|
|
100
|
+
const args = headed ? [] : ["--headless=new"];
|
|
101
|
+
if (getEnv().browserNoSandbox) args.push("--no-sandbox");
|
|
102
|
+
return chromium.launch({
|
|
103
|
+
headless: !headed,
|
|
104
|
+
channel: getEnv().browserChannel,
|
|
105
|
+
args
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// -------------------------------------------------------------------------
|
|
109
|
+
// Named sessions
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
/**
|
|
112
|
+
* Get or create a named browser session.
|
|
113
|
+
* Reusing an existing session updates `lastActivityAt`.
|
|
114
|
+
*/
|
|
115
|
+
async getSession(sessionId) {
|
|
116
|
+
const existing = this._sessions.get(sessionId);
|
|
117
|
+
if (existing) {
|
|
118
|
+
existing.lastActivityAt = Date.now();
|
|
119
|
+
return existing;
|
|
120
|
+
}
|
|
121
|
+
if (this._sessions.size >= this._config.maxSessions) {
|
|
122
|
+
const activeIds = [...this._sessions.keys()].join(", ");
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Maximum concurrent sessions (${this._config.maxSessions}) reached. Close an existing session first. Active sessions: ${activeIds}`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const browser = await this._ensureBrowser();
|
|
128
|
+
const context = await browser.newContext({
|
|
129
|
+
userAgent: this._config.userAgent
|
|
130
|
+
});
|
|
131
|
+
const page = await context.newPage();
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
const session = {
|
|
134
|
+
sessionId,
|
|
135
|
+
context,
|
|
136
|
+
page,
|
|
137
|
+
createdAt: now,
|
|
138
|
+
lastActivityAt: now
|
|
139
|
+
};
|
|
140
|
+
this._sessions.set(sessionId, session);
|
|
141
|
+
this._lastActivityAt = now;
|
|
142
|
+
return session;
|
|
143
|
+
}
|
|
144
|
+
/** Close a named session and release its resources. */
|
|
145
|
+
async closeSession(sessionId) {
|
|
146
|
+
const session = this._sessions.get(sessionId);
|
|
147
|
+
if (!session) return;
|
|
148
|
+
this._sessions.delete(sessionId);
|
|
149
|
+
try {
|
|
150
|
+
await session.context.close();
|
|
151
|
+
} catch (err) {
|
|
152
|
+
logAndSwallow(TAG, "closeSession context.close", err);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// -------------------------------------------------------------------------
|
|
156
|
+
// One-off contexts (ingestion scrapes)
|
|
157
|
+
// -------------------------------------------------------------------------
|
|
158
|
+
/** Create a disposable context + page for ingestion. No session tracking. */
|
|
159
|
+
async createOneOffContext() {
|
|
160
|
+
const browser = await this._ensureBrowser();
|
|
161
|
+
const context = await browser.newContext({
|
|
162
|
+
userAgent: this._config.userAgent
|
|
163
|
+
});
|
|
164
|
+
const page = await context.newPage();
|
|
165
|
+
return { context, page };
|
|
166
|
+
}
|
|
167
|
+
/** Close a one-off context after use. */
|
|
168
|
+
async closeContext(context) {
|
|
169
|
+
try {
|
|
170
|
+
await context.close();
|
|
171
|
+
} catch (err) {
|
|
172
|
+
logAndSwallow(TAG, "closeContext", err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// -------------------------------------------------------------------------
|
|
176
|
+
// Shutdown & cleanup
|
|
177
|
+
// -------------------------------------------------------------------------
|
|
178
|
+
/** Shut down everything: all sessions, the browser, cleanup timers. */
|
|
179
|
+
async shutdown() {
|
|
180
|
+
this._stopIdleCheck();
|
|
181
|
+
const closePromises = [...this._sessions.keys()].map(
|
|
182
|
+
(id) => this.closeSession(id)
|
|
183
|
+
);
|
|
184
|
+
await Promise.all(closePromises);
|
|
185
|
+
if (this._browser) {
|
|
186
|
+
try {
|
|
187
|
+
await this._browser.close();
|
|
188
|
+
} catch (err) {
|
|
189
|
+
logAndSwallow(TAG, "browser.close shutdown", err);
|
|
190
|
+
}
|
|
191
|
+
this._browser = null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// -------------------------------------------------------------------------
|
|
195
|
+
// Idle-check interval
|
|
196
|
+
// -------------------------------------------------------------------------
|
|
197
|
+
_startIdleCheck() {
|
|
198
|
+
this._idleTimer = setInterval(() => {
|
|
199
|
+
void this._sweepIdleSessions();
|
|
200
|
+
}, 3e4);
|
|
201
|
+
if (this._idleTimer && typeof this._idleTimer === "object" && "unref" in this._idleTimer) {
|
|
202
|
+
this._idleTimer.unref();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
_stopIdleCheck() {
|
|
206
|
+
if (this._idleTimer) {
|
|
207
|
+
clearInterval(this._idleTimer);
|
|
208
|
+
this._idleTimer = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async _sweepIdleSessions() {
|
|
212
|
+
const now = Date.now();
|
|
213
|
+
const expired = [];
|
|
214
|
+
for (const [id, session] of this._sessions) {
|
|
215
|
+
if (now - session.lastActivityAt > this._config.sessionTimeoutMs) {
|
|
216
|
+
expired.push(id);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (const id of expired) {
|
|
220
|
+
logWarn("browser", `Closing idle session "${id}".`);
|
|
221
|
+
await this.closeSession(id);
|
|
222
|
+
}
|
|
223
|
+
if (this._sessions.size === 0 && this._browser && this._lastActivityAt > 0 && now - this._lastActivityAt > this._containerIdleStopMs) {
|
|
224
|
+
logInfo("browser", `No sessions for ${Math.round((now - this._lastActivityAt) / 6e4)}min \u2014 stopping container.`);
|
|
225
|
+
await this.shutdown();
|
|
226
|
+
this._stopContainer();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
_stopContainer() {
|
|
230
|
+
try {
|
|
231
|
+
execFileSync("docker", ["stop", "abtars-browser"], { stdio: "pipe", timeout: 1e4 });
|
|
232
|
+
} catch (err) {
|
|
233
|
+
logAndSwallow("browser_manager", "op", err);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// -------------------------------------------------------------------------
|
|
237
|
+
// Accessors
|
|
238
|
+
// -------------------------------------------------------------------------
|
|
239
|
+
/** Number of active named sessions. */
|
|
240
|
+
get activeSessionCount() {
|
|
241
|
+
return this._sessions.size;
|
|
242
|
+
}
|
|
243
|
+
/** Exposed for testing — the resolved config. */
|
|
244
|
+
get config() {
|
|
245
|
+
return this._config;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/capabilities/browser/browser-tool.ts
|
|
250
|
+
init_logger();
|
|
251
|
+
init_log_and_swallow();
|
|
252
|
+
init_env_schema();
|
|
253
|
+
import * as os from "node:os";
|
|
254
|
+
import * as path from "node:path";
|
|
255
|
+
import { readFileSync } from "node:fs";
|
|
256
|
+
|
|
257
|
+
// src/components/content-extractor.ts
|
|
258
|
+
var BROWSER_EXTRACT_FN = `
|
|
259
|
+
(sel) => {
|
|
260
|
+
const TAGS = ["script", "style", "nav", "footer", "header", "aside"];
|
|
261
|
+
const ROLES = ["navigation", "banner"];
|
|
262
|
+
|
|
263
|
+
const root = sel ? document.querySelector(sel) : document.body;
|
|
264
|
+
if (!root) return "";
|
|
265
|
+
|
|
266
|
+
const clone = root.cloneNode(true);
|
|
267
|
+
|
|
268
|
+
for (const tag of TAGS) {
|
|
269
|
+
for (const el of clone.querySelectorAll(tag)) el.remove();
|
|
270
|
+
}
|
|
271
|
+
for (const role of ROLES) {
|
|
272
|
+
for (const el of clone.querySelectorAll('[role="' + role + '"]')) el.remove();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let text = clone.textContent || "";
|
|
276
|
+
text = text.replace(/\\u00a0/g, " ");
|
|
277
|
+
text = text.replace(/[ \\t]+/g, " ");
|
|
278
|
+
text = text.replace(/\\n[ ]+/g, "\\n");
|
|
279
|
+
text = text.replace(/[ ]+\\n/g, "\\n");
|
|
280
|
+
text = text.replace(/\\n{2,}/g, "\\n");
|
|
281
|
+
return text.trim();
|
|
282
|
+
}
|
|
283
|
+
`;
|
|
284
|
+
async function extractTextFromPage(page, selector) {
|
|
285
|
+
const text = await page.evaluate(
|
|
286
|
+
new Function("sel", `return (${BROWSER_EXTRACT_FN})(sel)`),
|
|
287
|
+
selector ?? null
|
|
288
|
+
);
|
|
289
|
+
return text ?? "";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/capabilities/browser/browser-tool.ts
|
|
293
|
+
var TAG2 = "browser_tool";
|
|
294
|
+
var LOG_PREFIX = "[browser-tool]";
|
|
295
|
+
var TEXT_TRUNCATION_LIMIT = 4e3;
|
|
296
|
+
var MAX_PAGE_ELEMENTS = 50;
|
|
297
|
+
function getNavigationTimeout() {
|
|
298
|
+
const raw = String(getEnv().webScrapePlaywrightTimeoutMs);
|
|
299
|
+
if (raw === void 0 || raw === "") return 3e4;
|
|
300
|
+
const n = Number(raw);
|
|
301
|
+
if (!Number.isFinite(n) || n <= 0) return 3e4;
|
|
302
|
+
return n;
|
|
303
|
+
}
|
|
304
|
+
var BrowserTool = class {
|
|
305
|
+
_browserManager;
|
|
306
|
+
_domainAllowlist;
|
|
307
|
+
constructor(browserManager, domainAllowlist) {
|
|
308
|
+
this._browserManager = browserManager;
|
|
309
|
+
this._domainAllowlist = domainAllowlist;
|
|
310
|
+
}
|
|
311
|
+
/** Execute a browser action and return a JSON-serializable result. */
|
|
312
|
+
async execute(action) {
|
|
313
|
+
try {
|
|
314
|
+
switch (action.action) {
|
|
315
|
+
case "navigate":
|
|
316
|
+
return await this._handleNavigate(action);
|
|
317
|
+
case "click":
|
|
318
|
+
return await this._handleClick(action);
|
|
319
|
+
case "fill":
|
|
320
|
+
return await this._handleFill(action);
|
|
321
|
+
case "extract_text":
|
|
322
|
+
return await this._handleExtractText(action);
|
|
323
|
+
case "screenshot":
|
|
324
|
+
return await this._handleScreenshot(action);
|
|
325
|
+
case "get_page_info":
|
|
326
|
+
return await this._handleGetPageInfo(action);
|
|
327
|
+
case "close_session":
|
|
328
|
+
return await this._handleCloseSession(action);
|
|
329
|
+
case "set_cookie":
|
|
330
|
+
return await this._handleSetCookie(action);
|
|
331
|
+
default:
|
|
332
|
+
return { success: false, error: `Unknown action: ${String(action.action)}` };
|
|
333
|
+
}
|
|
334
|
+
} catch (err) {
|
|
335
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
336
|
+
return { success: false, error: message };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// -------------------------------------------------------------------------
|
|
340
|
+
// navigate
|
|
341
|
+
// -------------------------------------------------------------------------
|
|
342
|
+
async _handleNavigate(action) {
|
|
343
|
+
const url = action.url;
|
|
344
|
+
if (!url) {
|
|
345
|
+
return { success: false, error: "navigate action requires a url" };
|
|
346
|
+
}
|
|
347
|
+
if (!this._domainAllowlist.isAllowed(url)) {
|
|
348
|
+
let hostname;
|
|
349
|
+
try {
|
|
350
|
+
hostname = new URL(url).hostname;
|
|
351
|
+
} catch (err) {
|
|
352
|
+
logAndSwallow(TAG2, "parse URL hostname", err);
|
|
353
|
+
hostname = url;
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
success: false,
|
|
357
|
+
error: `Domain "${hostname}" is not in the allowed list. Allowed patterns: ${this._domainAllowlist.patterns.join(", ")}`
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (getEnv().ssrfCheck) {
|
|
361
|
+
try {
|
|
362
|
+
const { hostname } = new URL(url);
|
|
363
|
+
const { isPrivateHost } = await import("./ssrf-guard-FZCBYIVW.js");
|
|
364
|
+
if (await isPrivateHost(hostname)) {
|
|
365
|
+
return { success: false, error: `Blocked: "${hostname}" resolves to a private/internal IP address` };
|
|
366
|
+
}
|
|
367
|
+
} catch (err) {
|
|
368
|
+
logAndSwallow(TAG2, "SSRF check", err);
|
|
369
|
+
return { success: false, error: "Invalid URL" };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const session = await this._browserManager.getSession(action.sessionId);
|
|
373
|
+
const timeout = getNavigationTimeout();
|
|
374
|
+
logDebug("browser", `${LOG_PREFIX} navigate session="${action.sessionId}" url="${url}"`);
|
|
375
|
+
try {
|
|
376
|
+
const response = await session.page.goto(url, {
|
|
377
|
+
waitUntil: "domcontentloaded",
|
|
378
|
+
timeout
|
|
379
|
+
});
|
|
380
|
+
const title = await session.page.title();
|
|
381
|
+
const finalUrl = session.page.url();
|
|
382
|
+
const status = response?.status();
|
|
383
|
+
return { success: true, title, url: finalUrl, status };
|
|
384
|
+
} catch (err) {
|
|
385
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
386
|
+
return { success: false, error: `Navigation failed for ${url}: ${message}` };
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// -------------------------------------------------------------------------
|
|
390
|
+
// click
|
|
391
|
+
// -------------------------------------------------------------------------
|
|
392
|
+
async _handleClick(action) {
|
|
393
|
+
const selector = action.selector;
|
|
394
|
+
if (!selector) {
|
|
395
|
+
return { success: false, error: "click action requires a selector" };
|
|
396
|
+
}
|
|
397
|
+
const session = await this._browserManager.getSession(action.sessionId);
|
|
398
|
+
logDebug("browser", `${LOG_PREFIX} click session="${action.sessionId}" selector="${selector}"`);
|
|
399
|
+
try {
|
|
400
|
+
const navigationPromise = session.page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 5e3 }).catch((err) => {
|
|
401
|
+
logAndSwallow(TAG2, "waitForNavigation click", err);
|
|
402
|
+
return null;
|
|
403
|
+
});
|
|
404
|
+
await session.page.click(selector);
|
|
405
|
+
const navResult = await navigationPromise;
|
|
406
|
+
if (navResult) {
|
|
407
|
+
const title = await session.page.title();
|
|
408
|
+
const url = session.page.url();
|
|
409
|
+
return { success: true, navigated: true, title, url };
|
|
410
|
+
}
|
|
411
|
+
return { success: true, navigated: false };
|
|
412
|
+
} catch (err) {
|
|
413
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
414
|
+
if (message.includes("waiting for selector") || message.includes("No element")) {
|
|
415
|
+
return { success: false, error: `Selector not found: "${selector}"` };
|
|
416
|
+
}
|
|
417
|
+
return { success: false, error: `Click failed for selector "${selector}": ${message}` };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// -------------------------------------------------------------------------
|
|
421
|
+
// fill
|
|
422
|
+
// -------------------------------------------------------------------------
|
|
423
|
+
async _handleFill(action) {
|
|
424
|
+
const selector = action.selector;
|
|
425
|
+
const value = action.value;
|
|
426
|
+
if (!selector) {
|
|
427
|
+
return { success: false, error: "fill action requires a selector" };
|
|
428
|
+
}
|
|
429
|
+
if (value === void 0) {
|
|
430
|
+
return { success: false, error: "fill action requires a value" };
|
|
431
|
+
}
|
|
432
|
+
const session = await this._browserManager.getSession(action.sessionId);
|
|
433
|
+
let isPassword = false;
|
|
434
|
+
try {
|
|
435
|
+
isPassword = await session.page.evaluate(
|
|
436
|
+
`(sel) => { const el = document.querySelector(sel); return el instanceof HTMLInputElement && el.type === "password"; }`,
|
|
437
|
+
selector
|
|
438
|
+
);
|
|
439
|
+
} catch (err) {
|
|
440
|
+
logAndSwallow(TAG2, "evaluate isPassword", err);
|
|
441
|
+
}
|
|
442
|
+
const logValue = isPassword ? "***" : value;
|
|
443
|
+
logDebug(
|
|
444
|
+
"browser",
|
|
445
|
+
`${LOG_PREFIX} fill session="${action.sessionId}" selector="${selector}" value="${logValue}"`
|
|
446
|
+
);
|
|
447
|
+
try {
|
|
448
|
+
await session.page.fill(selector, value);
|
|
449
|
+
return { success: true };
|
|
450
|
+
} catch (err) {
|
|
451
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
452
|
+
if (message.includes("waiting for selector") || message.includes("No element")) {
|
|
453
|
+
return { success: false, error: `Selector not found: "${selector}"` };
|
|
454
|
+
}
|
|
455
|
+
return { success: false, error: `Fill failed for selector "${selector}": ${message}` };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// -------------------------------------------------------------------------
|
|
459
|
+
// extract_text
|
|
460
|
+
// -------------------------------------------------------------------------
|
|
461
|
+
async _handleExtractText(action) {
|
|
462
|
+
const session = await this._browserManager.getSession(action.sessionId);
|
|
463
|
+
logDebug(
|
|
464
|
+
"browser",
|
|
465
|
+
`${LOG_PREFIX} extract_text session="${action.sessionId}" selector="${action.selector ?? "(full page)"}"`
|
|
466
|
+
);
|
|
467
|
+
try {
|
|
468
|
+
const text = await extractTextFromPage(session.page, action.selector ?? void 0);
|
|
469
|
+
if (!text || text.trim().length === 0) {
|
|
470
|
+
return { success: false, error: "No text content found on the page" };
|
|
471
|
+
}
|
|
472
|
+
const truncated = text.length > TEXT_TRUNCATION_LIMIT;
|
|
473
|
+
const resultText = truncated ? text.slice(0, TEXT_TRUNCATION_LIMIT) : text;
|
|
474
|
+
return { success: true, text: resultText, truncated };
|
|
475
|
+
} catch (err) {
|
|
476
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
477
|
+
if (message.includes("waiting for selector") || message.includes("No element")) {
|
|
478
|
+
return { success: false, error: `Selector not found: "${action.selector}"` };
|
|
479
|
+
}
|
|
480
|
+
return { success: false, error: `Text extraction failed: ${message}` };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// -------------------------------------------------------------------------
|
|
484
|
+
// screenshot
|
|
485
|
+
// -------------------------------------------------------------------------
|
|
486
|
+
async _handleScreenshot(action) {
|
|
487
|
+
const session = await this._browserManager.getSession(action.sessionId);
|
|
488
|
+
const tmpFile = path.join(
|
|
489
|
+
os.tmpdir(),
|
|
490
|
+
`abtars-screenshot-${Date.now()}.png`
|
|
491
|
+
);
|
|
492
|
+
logDebug(
|
|
493
|
+
"browser",
|
|
494
|
+
`${LOG_PREFIX} screenshot session="${action.sessionId}" fullPage=${action.fullPage ?? false} path="${tmpFile}"`
|
|
495
|
+
);
|
|
496
|
+
await session.page.screenshot({
|
|
497
|
+
fullPage: action.fullPage ?? false,
|
|
498
|
+
path: tmpFile
|
|
499
|
+
});
|
|
500
|
+
return { success: true, filePath: tmpFile };
|
|
501
|
+
}
|
|
502
|
+
// -------------------------------------------------------------------------
|
|
503
|
+
// get_page_info
|
|
504
|
+
// -------------------------------------------------------------------------
|
|
505
|
+
async _handleGetPageInfo(action) {
|
|
506
|
+
const session = await this._browserManager.getSession(action.sessionId);
|
|
507
|
+
logDebug("browser", `${LOG_PREFIX} get_page_info session="${action.sessionId}"`);
|
|
508
|
+
const title = await session.page.title();
|
|
509
|
+
const url = session.page.url();
|
|
510
|
+
const elements = await session.page.evaluate(`(maxElements) => {
|
|
511
|
+
const selectors = "a, button, input, select, textarea, [role='button'], [role='link']";
|
|
512
|
+
const nodes = document.querySelectorAll(selectors);
|
|
513
|
+
const results = [];
|
|
514
|
+
|
|
515
|
+
for (let i = 0; i < nodes.length && results.length < maxElements; i++) {
|
|
516
|
+
const el = nodes[i];
|
|
517
|
+
if (!el || !(el instanceof HTMLElement)) continue;
|
|
518
|
+
|
|
519
|
+
const style = window.getComputedStyle(el);
|
|
520
|
+
if (style.display === "none" || style.visibility === "hidden") continue;
|
|
521
|
+
|
|
522
|
+
let selector = el.tagName.toLowerCase();
|
|
523
|
+
if (el.id) {
|
|
524
|
+
selector = "#" + el.id;
|
|
525
|
+
} else if (el.className && typeof el.className === "string" && el.className.trim()) {
|
|
526
|
+
const cls = el.className.trim().split(/\\s+/).slice(0, 2).join(".");
|
|
527
|
+
selector = el.tagName.toLowerCase() + "." + cls;
|
|
528
|
+
} else {
|
|
529
|
+
const parent = el.parentElement;
|
|
530
|
+
if (parent) {
|
|
531
|
+
const siblings = parent.querySelectorAll(":scope > " + el.tagName.toLowerCase());
|
|
532
|
+
if (siblings.length > 1) {
|
|
533
|
+
const idx = Array.from(siblings).indexOf(el) + 1;
|
|
534
|
+
selector = el.tagName.toLowerCase() + ":nth-of-type(" + idx + ")";
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const entry = { tag: el.tagName.toLowerCase(), selector };
|
|
540
|
+
const text = (el.textContent || "").trim().slice(0, 100);
|
|
541
|
+
if (text) entry.text = text;
|
|
542
|
+
|
|
543
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
|
|
544
|
+
if (el instanceof HTMLInputElement && el.type) entry.type = el.type;
|
|
545
|
+
if (el.name) entry.name = el.name;
|
|
546
|
+
if (el instanceof HTMLInputElement && el.placeholder) entry.placeholder = el.placeholder;
|
|
547
|
+
if (el instanceof HTMLTextAreaElement && el.placeholder) entry.placeholder = el.placeholder;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (el instanceof HTMLAnchorElement && el.href) {
|
|
551
|
+
entry.href = el.href;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
results.push(entry);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return results;
|
|
558
|
+
}`, MAX_PAGE_ELEMENTS);
|
|
559
|
+
return {
|
|
560
|
+
success: true,
|
|
561
|
+
url,
|
|
562
|
+
title,
|
|
563
|
+
elements
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
// -------------------------------------------------------------------------
|
|
567
|
+
// close_session
|
|
568
|
+
// -------------------------------------------------------------------------
|
|
569
|
+
async _handleCloseSession(action) {
|
|
570
|
+
logDebug("browser", `${LOG_PREFIX} close_session session="${action.sessionId}"`);
|
|
571
|
+
await this._browserManager.closeSession(action.sessionId);
|
|
572
|
+
return { success: true };
|
|
573
|
+
}
|
|
574
|
+
// -------------------------------------------------------------------------
|
|
575
|
+
// set_cookie
|
|
576
|
+
// -------------------------------------------------------------------------
|
|
577
|
+
async _handleSetCookie(action) {
|
|
578
|
+
const file = action.cookieFile;
|
|
579
|
+
if (!file) return { success: false, error: "set_cookie requires --cookie-file" };
|
|
580
|
+
const COOKIES_DIR = "/run/browser/cookies";
|
|
581
|
+
const resolved = path.resolve(file);
|
|
582
|
+
if (!resolved.startsWith(COOKIES_DIR)) {
|
|
583
|
+
return { success: false, error: `cookie file must be under ${COOKIES_DIR}` };
|
|
584
|
+
}
|
|
585
|
+
const raw = readFileSync(resolved, "utf-8");
|
|
586
|
+
const json = JSON.parse(raw);
|
|
587
|
+
const session = await this._browserManager.getSession(action.sessionId);
|
|
588
|
+
const url = action.url ?? session.page.url();
|
|
589
|
+
let domain;
|
|
590
|
+
try {
|
|
591
|
+
domain = new URL(url).hostname;
|
|
592
|
+
} catch (err) {
|
|
593
|
+
logAndSwallow(TAG2, "parse cookie domain", err);
|
|
594
|
+
domain = "";
|
|
595
|
+
}
|
|
596
|
+
const cookies = Object.entries(json).map(([name, value]) => ({
|
|
597
|
+
name,
|
|
598
|
+
value: String(value),
|
|
599
|
+
domain,
|
|
600
|
+
path: "/"
|
|
601
|
+
}));
|
|
602
|
+
await session.context.addCookies(cookies);
|
|
603
|
+
logDebug("browser", `${LOG_PREFIX} set_cookie session="${action.sessionId}" loaded ${cookies.length} cookies for ${domain}`);
|
|
604
|
+
return { success: true, text: `Loaded ${cookies.length} cookies for ${domain}` };
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// src/capabilities/browser/browser-ipc-server.ts
|
|
609
|
+
init_logger();
|
|
610
|
+
init_log_and_swallow();
|
|
611
|
+
init_paths();
|
|
612
|
+
import * as net from "node:net";
|
|
613
|
+
import * as fs from "node:fs";
|
|
614
|
+
import * as path2 from "node:path";
|
|
615
|
+
var TAG3 = "browser_ipc";
|
|
616
|
+
var LOG_PREFIX2 = "[browser-ipc]";
|
|
617
|
+
function getDefaultSocketPath() {
|
|
618
|
+
return path2.join(abtarsHome(), "browser-socket", "browser.sock");
|
|
619
|
+
}
|
|
620
|
+
var BrowserIpcServer = class {
|
|
621
|
+
_server = null;
|
|
622
|
+
_tool;
|
|
623
|
+
_socketPath;
|
|
624
|
+
constructor(tool, socketPath) {
|
|
625
|
+
this._tool = tool;
|
|
626
|
+
this._socketPath = socketPath ?? getDefaultSocketPath();
|
|
627
|
+
}
|
|
628
|
+
get socketPath() {
|
|
629
|
+
return this._socketPath;
|
|
630
|
+
}
|
|
631
|
+
get isListening() {
|
|
632
|
+
return this._server?.listening ?? false;
|
|
633
|
+
}
|
|
634
|
+
/** Start listening. Removes stale socket file if present. */
|
|
635
|
+
async start() {
|
|
636
|
+
const dir = path2.dirname(this._socketPath);
|
|
637
|
+
if (!fs.existsSync(dir)) {
|
|
638
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
639
|
+
}
|
|
640
|
+
this._removeSocketFile();
|
|
641
|
+
return new Promise((resolve2, reject) => {
|
|
642
|
+
const server = net.createServer({ allowHalfOpen: true }, (conn) => {
|
|
643
|
+
this._handleConnection(conn);
|
|
644
|
+
});
|
|
645
|
+
server.on("error", (err) => {
|
|
646
|
+
console.error(`${LOG_PREFIX2} Server error: ${err.message}`);
|
|
647
|
+
reject(err);
|
|
648
|
+
});
|
|
649
|
+
server.listen(this._socketPath, () => {
|
|
650
|
+
this._server = server;
|
|
651
|
+
logDebug("browser", `${LOG_PREFIX2} Listening on ${this._socketPath}`);
|
|
652
|
+
resolve2();
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
/** Stop the server and remove the socket file. */
|
|
657
|
+
async shutdown() {
|
|
658
|
+
return new Promise((resolve2) => {
|
|
659
|
+
if (!this._server) {
|
|
660
|
+
this._removeSocketFile();
|
|
661
|
+
resolve2();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
this._server.close(() => {
|
|
665
|
+
this._server = null;
|
|
666
|
+
this._removeSocketFile();
|
|
667
|
+
logDebug("browser", `${LOG_PREFIX2} Shut down`);
|
|
668
|
+
resolve2();
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
// ── Connection handler ──────────────────────────────────────────────
|
|
673
|
+
_handleConnection(conn) {
|
|
674
|
+
let data = "";
|
|
675
|
+
conn.on("data", (chunk) => {
|
|
676
|
+
data += chunk.toString();
|
|
677
|
+
});
|
|
678
|
+
conn.on("end", () => {
|
|
679
|
+
void this._processRequest(data, conn);
|
|
680
|
+
});
|
|
681
|
+
conn.on("error", (err) => {
|
|
682
|
+
console.error(`${LOG_PREFIX2} Connection error: ${err.message}`);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
async _processRequest(raw, conn) {
|
|
686
|
+
let result;
|
|
687
|
+
try {
|
|
688
|
+
const action = JSON.parse(raw.trim());
|
|
689
|
+
result = await this._tool.execute(action);
|
|
690
|
+
} catch (err) {
|
|
691
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
692
|
+
result = { success: false, error: `IPC parse/execute error: ${message}` };
|
|
693
|
+
}
|
|
694
|
+
conn.end(JSON.stringify(result) + "\n");
|
|
695
|
+
}
|
|
696
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
697
|
+
_removeSocketFile() {
|
|
698
|
+
try {
|
|
699
|
+
if (fs.existsSync(this._socketPath)) {
|
|
700
|
+
fs.unlinkSync(this._socketPath);
|
|
701
|
+
}
|
|
702
|
+
} catch (err) {
|
|
703
|
+
logAndSwallow(TAG3, "unlink socket", err);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/capabilities/browser/domain-allowlist.ts
|
|
709
|
+
init_env_schema();
|
|
710
|
+
init_log_and_swallow();
|
|
711
|
+
var TAG4 = "domain_allowlist";
|
|
712
|
+
var DomainAllowlist = class _DomainAllowlist {
|
|
713
|
+
_patterns;
|
|
714
|
+
constructor(patterns) {
|
|
715
|
+
this._patterns = patterns.map((p) => p.trim().toLowerCase()).filter((p) => p.length > 0);
|
|
716
|
+
}
|
|
717
|
+
/** Check if a URL's hostname matches the allowlist. Returns true if allowed. */
|
|
718
|
+
isAllowed(url) {
|
|
719
|
+
if (this._patterns.length === 0) return true;
|
|
720
|
+
let hostname;
|
|
721
|
+
try {
|
|
722
|
+
hostname = new URL(url).hostname.toLowerCase();
|
|
723
|
+
} catch (err) {
|
|
724
|
+
logAndSwallow(TAG4, "parse URL", err);
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
return this._patterns.some((pattern) => {
|
|
728
|
+
if (pattern.startsWith("*.")) {
|
|
729
|
+
const suffix = pattern.slice(2);
|
|
730
|
+
return hostname === suffix || hostname.endsWith(`.${suffix}`);
|
|
731
|
+
}
|
|
732
|
+
return hostname === pattern;
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
/** Get the list of configured patterns (for error messages). */
|
|
736
|
+
get patterns() {
|
|
737
|
+
return [...this._patterns];
|
|
738
|
+
}
|
|
739
|
+
/** True if no patterns configured (open mode). */
|
|
740
|
+
get isOpenMode() {
|
|
741
|
+
return this._patterns.length === 0;
|
|
742
|
+
}
|
|
743
|
+
/** Create a DomainAllowlist from the BROWSER_ALLOWED_DOMAINS env var. */
|
|
744
|
+
static fromEnv() {
|
|
745
|
+
const raw = getEnv().browserAllowedDomains;
|
|
746
|
+
const patterns = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
747
|
+
return new _DomainAllowlist(patterns);
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
export {
|
|
752
|
+
BrowserManager,
|
|
753
|
+
BrowserTool,
|
|
754
|
+
getDefaultSocketPath,
|
|
755
|
+
BrowserIpcServer,
|
|
756
|
+
DomainAllowlist
|
|
757
|
+
};
|
|
758
|
+
//# sourceMappingURL=chunk-3B7BBE4F.js.map
|