@michael-joseph-miller/ant-bot 0.1.0
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/CHANGELOG.md +40 -0
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/browser-OHRD7YI3.js +524 -0
- package/dist/browser-OHRD7YI3.js.map +7 -0
- package/dist/bundled-46DUK5PG.js +133 -0
- package/dist/bundled-46DUK5PG.js.map +7 -0
- package/dist/chunk-7BOHBPB2.js +287 -0
- package/dist/chunk-7BOHBPB2.js.map +7 -0
- package/dist/chunk-AHAON6J7.js +24 -0
- package/dist/chunk-AHAON6J7.js.map +7 -0
- package/dist/chunk-DYIJTUMY.js +321 -0
- package/dist/chunk-DYIJTUMY.js.map +7 -0
- package/dist/chunk-KSQOVWP5.js +310 -0
- package/dist/chunk-KSQOVWP5.js.map +7 -0
- package/dist/chunk-Z2YT2PZN.js +64 -0
- package/dist/chunk-Z2YT2PZN.js.map +7 -0
- package/dist/index.js +1562 -0
- package/dist/index.js.map +7 -0
- package/dist/install-IY2M3OUQ.js +36 -0
- package/dist/install-IY2M3OUQ.js.map +7 -0
- package/dist/plugin-POMHVGD4.js +53 -0
- package/dist/plugin-POMHVGD4.js.map +7 -0
- package/dist/scheduler-Q7OHNGP6.js +274 -0
- package/dist/scheduler-Q7OHNGP6.js.map +7 -0
- package/dist/server.js +3016 -0
- package/dist/server.js.map +7 -0
- package/dist/skills-66WRX64H.js +17 -0
- package/dist/skills-66WRX64H.js.map +7 -0
- package/dist/skills-spec.js +172 -0
- package/dist/skills-spec.js.map +7 -0
- package/dist/tools-P5537ASX.js +148 -0
- package/dist/tools-P5537ASX.js.map +7 -0
- package/package.json +51 -0
- package/skills/README.md +69 -0
- package/skills/SPEC.md +274 -0
- package/skills/bug-repro/SKILL.md +53 -0
- package/skills/deep-research/SKILL.md +451 -0
- package/skills/deep-research/references/V6_1_improvements.md +112 -0
- package/skills/deep-research/references/completeness_review_checklist.md +25 -0
- package/skills/deep-research/references/counter_review_team_guide.md +181 -0
- package/skills/deep-research/references/enterprise_analysis_frameworks.md +135 -0
- package/skills/deep-research/references/enterprise_mode.md +99 -0
- package/skills/deep-research/references/enterprise_quality_checklist.md +160 -0
- package/skills/deep-research/references/enterprise_research_methodology.md +164 -0
- package/skills/deep-research/references/formatting_rules.md +31 -0
- package/skills/deep-research/references/quality_gates.md +77 -0
- package/skills/deep-research/references/report_template_v6.md +82 -0
- package/skills/deep-research/references/research_notes_format.md +147 -0
- package/skills/deep-research/references/research_plan_checklist.md +26 -0
- package/skills/deep-research/references/research_report_template.md +49 -0
- package/skills/deep-research/references/source_accessibility_policy.md +179 -0
- package/skills/deep-research/references/source_quality_rubric.md +28 -0
- package/skills/deep-research/references/subagent_prompt.md +116 -0
- package/skills/inbox-digest/SKILL.md +51 -0
- package/skills/skill-author/SKILL.md +92 -0
- package/skills/weekly-report/SKILL.md +49 -0
- package/web/dist/assets/index-BLQ8rPiN.js +130 -0
- package/web/dist/assets/index-IEIkG_jd.css +2 -0
- package/web/dist/index.html +13 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { createRequire as __antbotCreateRequire } from 'node:module';
|
|
2
|
+
const require = __antbotCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
logger
|
|
5
|
+
} from "./chunk-AHAON6J7.js";
|
|
6
|
+
|
|
7
|
+
// packages/server/src/computer/browser.ts
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { chromium } from "playwright";
|
|
12
|
+
var log = logger("browser");
|
|
13
|
+
var BrowserUnavailableError = class extends Error {
|
|
14
|
+
constructor(reason) {
|
|
15
|
+
super(`Browser is unavailable: ${reason}. Try: npx playwright install chromium`);
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
this.name = "BrowserUnavailableError";
|
|
18
|
+
}
|
|
19
|
+
reason;
|
|
20
|
+
};
|
|
21
|
+
var ScreenBusyError = class extends Error {
|
|
22
|
+
constructor(botId) {
|
|
23
|
+
super(`That bot's screen is already busy with another computer-use task. Wait for it to finish before starting another.`);
|
|
24
|
+
this.botId = botId;
|
|
25
|
+
this.name = "ScreenBusyError";
|
|
26
|
+
}
|
|
27
|
+
botId;
|
|
28
|
+
};
|
|
29
|
+
var InvalidUrlError = class extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "InvalidUrlError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var ScreenTakenOverError = class extends Error {
|
|
36
|
+
constructor() {
|
|
37
|
+
super("The human has taken control of this screen; wait until they return it.");
|
|
38
|
+
this.name = "ScreenTakenOverError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function normalizeUrl(input) {
|
|
42
|
+
const trimmed = (input ?? "").trim();
|
|
43
|
+
if (!trimmed) throw new InvalidUrlError("URL is empty.");
|
|
44
|
+
const schemeMatch = trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);
|
|
45
|
+
if (!schemeMatch) return `https://${trimmed}`;
|
|
46
|
+
const scheme = schemeMatch[1].toLowerCase();
|
|
47
|
+
if (scheme === "javascript") throw new InvalidUrlError("javascript: URLs are not allowed.");
|
|
48
|
+
if (scheme === "file") throw new InvalidUrlError("file: URLs are not allowed.");
|
|
49
|
+
return trimmed;
|
|
50
|
+
}
|
|
51
|
+
function detectBlockFromSignals(signals) {
|
|
52
|
+
const iframeBlob = (signals.iframeSrcs ?? []).join(" ").toLowerCase();
|
|
53
|
+
const body = signals.bodyText ?? "";
|
|
54
|
+
if (/recaptcha/i.test(iframeBlob) || /hcaptcha/i.test(iframeBlob)) {
|
|
55
|
+
return { kind: "captcha", message: "A CAPTCHA challenge (reCAPTCHA/hCaptcha) is present on this page." };
|
|
56
|
+
}
|
|
57
|
+
if (/challenges\.cloudflare\.com|cf-challenge|turnstile/i.test(iframeBlob) || /checking your browser|cloudflare/i.test(body)) {
|
|
58
|
+
return { kind: "captcha", message: "A Cloudflare browser challenge is present on this page." };
|
|
59
|
+
}
|
|
60
|
+
if (/verify (your \w+|it'?s you)|two-factor|one-time code/i.test(body)) {
|
|
61
|
+
return { kind: "2fa", message: "This page is asking for two-factor / one-time-code verification." };
|
|
62
|
+
}
|
|
63
|
+
if (signals.hasPasswordInput) {
|
|
64
|
+
return { kind: "login", message: "This page has a password field \u2014 it looks like a login wall." };
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
var ScreenLock = class {
|
|
69
|
+
busy = /* @__PURE__ */ new Set();
|
|
70
|
+
isBusy(key) {
|
|
71
|
+
return this.busy.has(key);
|
|
72
|
+
}
|
|
73
|
+
async run(key, fn) {
|
|
74
|
+
if (this.busy.has(key)) throw new ScreenBusyError(key);
|
|
75
|
+
this.busy.add(key);
|
|
76
|
+
try {
|
|
77
|
+
return await fn();
|
|
78
|
+
} finally {
|
|
79
|
+
this.busy.delete(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var FrameThrottle = class {
|
|
84
|
+
constructor(fps, clock = () => Date.now()) {
|
|
85
|
+
this.fps = fps;
|
|
86
|
+
this.clock = clock;
|
|
87
|
+
}
|
|
88
|
+
fps;
|
|
89
|
+
clock;
|
|
90
|
+
lastEmit = -Infinity;
|
|
91
|
+
get intervalMs() {
|
|
92
|
+
return 1e3 / this.fps;
|
|
93
|
+
}
|
|
94
|
+
shouldEmit() {
|
|
95
|
+
const now = this.clock();
|
|
96
|
+
if (now - this.lastEmit >= this.intervalMs) {
|
|
97
|
+
this.lastEmit = now;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
function errMsg(err) {
|
|
104
|
+
return err instanceof Error ? err.message : String(err);
|
|
105
|
+
}
|
|
106
|
+
var BrowserService = class {
|
|
107
|
+
constructor(deps) {
|
|
108
|
+
this.deps = deps;
|
|
109
|
+
this.headlessMode = deps.headless ?? true;
|
|
110
|
+
}
|
|
111
|
+
deps;
|
|
112
|
+
context = null;
|
|
113
|
+
launchPromise = null;
|
|
114
|
+
unavailableReason;
|
|
115
|
+
headlessMode;
|
|
116
|
+
pages = /* @__PURE__ */ new Map();
|
|
117
|
+
titleCache = /* @__PURE__ */ new Map();
|
|
118
|
+
takenOver = /* @__PURE__ */ new Set();
|
|
119
|
+
lock = new ScreenLock();
|
|
120
|
+
screencastSessions = /* @__PURE__ */ new Map();
|
|
121
|
+
screencastListeners = /* @__PURE__ */ new Map();
|
|
122
|
+
screencastThrottles = /* @__PURE__ */ new Map();
|
|
123
|
+
viewerCounts = /* @__PURE__ */ new Map();
|
|
124
|
+
get isHeadless() {
|
|
125
|
+
return this.headlessMode;
|
|
126
|
+
}
|
|
127
|
+
/** Lazily launches the shared persistent context. Concurrent callers share one launch. */
|
|
128
|
+
async ensureContext() {
|
|
129
|
+
if (this.context) return this.context;
|
|
130
|
+
if (this.launchPromise) return this.launchPromise;
|
|
131
|
+
this.launchPromise = (async () => {
|
|
132
|
+
try {
|
|
133
|
+
fs.mkdirSync(this.deps.profileDir, { recursive: true });
|
|
134
|
+
const ctx = await chromium.launchPersistentContext(this.deps.profileDir, {
|
|
135
|
+
headless: this.headlessMode,
|
|
136
|
+
viewport: { width: 1280, height: 800 }
|
|
137
|
+
});
|
|
138
|
+
this.context = ctx;
|
|
139
|
+
ctx.on("close", () => {
|
|
140
|
+
this.context = null;
|
|
141
|
+
this.launchPromise = null;
|
|
142
|
+
this.pages.clear();
|
|
143
|
+
});
|
|
144
|
+
return ctx;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
this.unavailableReason = errMsg(err);
|
|
147
|
+
this.launchPromise = null;
|
|
148
|
+
throw new BrowserUnavailableError(this.unavailableReason);
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
151
|
+
return this.launchPromise;
|
|
152
|
+
}
|
|
153
|
+
/** Returns this bot's dedicated page, creating it lazily. Pages persist across turns. */
|
|
154
|
+
async getPage(botId) {
|
|
155
|
+
const existing = this.pages.get(botId);
|
|
156
|
+
if (existing && !existing.isClosed()) return existing;
|
|
157
|
+
const ctx = await this.ensureContext();
|
|
158
|
+
const page = await ctx.newPage();
|
|
159
|
+
this.pages.set(botId, page);
|
|
160
|
+
page.on("close", () => {
|
|
161
|
+
if (this.pages.get(botId) === page) this.pages.delete(botId);
|
|
162
|
+
});
|
|
163
|
+
return page;
|
|
164
|
+
}
|
|
165
|
+
async closePage(botId) {
|
|
166
|
+
const page = this.pages.get(botId);
|
|
167
|
+
if (page) {
|
|
168
|
+
await page.close().catch(() => {
|
|
169
|
+
});
|
|
170
|
+
this.pages.delete(botId);
|
|
171
|
+
}
|
|
172
|
+
this.titleCache.delete(botId);
|
|
173
|
+
this.takenOver.delete(botId);
|
|
174
|
+
}
|
|
175
|
+
/** Serializes computer-use tasks per bot screen; a concurrent second call is rejected. */
|
|
176
|
+
async withScreen(botId, fn) {
|
|
177
|
+
return this.lock.run(botId, async () => {
|
|
178
|
+
if (this.takenOver.has(botId)) throw new ScreenTakenOverError();
|
|
179
|
+
const page = await this.getPage(botId);
|
|
180
|
+
return fn(page);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
async orient(botId, page) {
|
|
184
|
+
const url = page.url();
|
|
185
|
+
let title = this.titleCache.get(botId) ?? "";
|
|
186
|
+
try {
|
|
187
|
+
title = await page.title();
|
|
188
|
+
this.titleCache.set(botId, title);
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
return { url, title };
|
|
192
|
+
}
|
|
193
|
+
orientLine(o) {
|
|
194
|
+
return `URL: ${o.url}
|
|
195
|
+
Title: ${o.title}`;
|
|
196
|
+
}
|
|
197
|
+
/** Gathers page signals and runs them through the pure detector. Never throws. */
|
|
198
|
+
async detectBlock(page) {
|
|
199
|
+
try {
|
|
200
|
+
const hasPasswordInput = (await page.$$('input[type="password"]').catch(() => [])).length > 0;
|
|
201
|
+
const iframeSrcs = await page.$$eval("iframe", (els) => els.map((e) => e.getAttribute("src") ?? "")).catch(() => []);
|
|
202
|
+
const bodyText = await page.evaluate(() => document.body?.innerText ?? "").catch(() => "");
|
|
203
|
+
return detectBlockFromSignals({ hasPasswordInput, iframeSrcs, bodyText: bodyText.slice(0, 4e3) });
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** If a block is detected, publishes a warn notification and returns the STOP text to prepend. */
|
|
209
|
+
async checkBlock(botId, page) {
|
|
210
|
+
const block = await this.detectBlock(page);
|
|
211
|
+
if (!block) return null;
|
|
212
|
+
this.deps.bus.publish({
|
|
213
|
+
type: "notify",
|
|
214
|
+
botId,
|
|
215
|
+
threadId: null,
|
|
216
|
+
level: "warn",
|
|
217
|
+
title: "Browser needs a human",
|
|
218
|
+
body: block.message
|
|
219
|
+
});
|
|
220
|
+
return `${block.message} STOP: do not attempt to solve or bypass this yourself \u2014 ask the human to take over the computer and wait for them to return control.`;
|
|
221
|
+
}
|
|
222
|
+
/* ---------------------------------------------------------------------------------------- *
|
|
223
|
+
* Actions — each returns a short result string including the resulting URL + title, and is
|
|
224
|
+
* safe to call even if the page has navigated away, closed, or errored mid-action.
|
|
225
|
+
* ---------------------------------------------------------------------------------------- */
|
|
226
|
+
async navigate(botId, url) {
|
|
227
|
+
const target = normalizeUrl(url);
|
|
228
|
+
return this.withScreen(botId, async (page) => {
|
|
229
|
+
try {
|
|
230
|
+
await page.goto(target, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
231
|
+
} catch (err) {
|
|
232
|
+
return `Navigation to ${target} failed: ${errMsg(err)}
|
|
233
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
234
|
+
}
|
|
235
|
+
const block = await this.checkBlock(botId, page);
|
|
236
|
+
const o = await this.orient(botId, page);
|
|
237
|
+
return block ? `${block}
|
|
238
|
+
${this.orientLine(o)}` : `Navigated.
|
|
239
|
+
${this.orientLine(o)}`;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async click(botId, selector) {
|
|
243
|
+
return this.withScreen(botId, async (page) => {
|
|
244
|
+
try {
|
|
245
|
+
await page.click(selector, { timeout: 1e4 });
|
|
246
|
+
} catch (err) {
|
|
247
|
+
return `Click on "${selector}" failed: ${errMsg(err)}
|
|
248
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
249
|
+
}
|
|
250
|
+
const block = await this.checkBlock(botId, page);
|
|
251
|
+
const o = await this.orient(botId, page);
|
|
252
|
+
return block ? `${block}
|
|
253
|
+
${this.orientLine(o)}` : `Clicked "${selector}".
|
|
254
|
+
${this.orientLine(o)}`;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
async type(botId, selector, text, opts) {
|
|
258
|
+
return this.withScreen(botId, async (page) => {
|
|
259
|
+
try {
|
|
260
|
+
const isPassword = await page.$eval(selector, (el) => el.type === "password").catch(() => false);
|
|
261
|
+
if (isPassword) {
|
|
262
|
+
return `Refused: "${selector}" looks like a password field. Never type credentials into the browser \u2014 ask the human to take over the computer instead.
|
|
263
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
264
|
+
}
|
|
265
|
+
await page.fill(selector, text, { timeout: 1e4 });
|
|
266
|
+
if (opts?.submit) await page.press(selector, "Enter");
|
|
267
|
+
} catch (err) {
|
|
268
|
+
return `Type into "${selector}" failed: ${errMsg(err)}
|
|
269
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
270
|
+
}
|
|
271
|
+
const block = await this.checkBlock(botId, page);
|
|
272
|
+
const o = await this.orient(botId, page);
|
|
273
|
+
return block ? `${block}
|
|
274
|
+
${this.orientLine(o)}` : `Typed into "${selector}".
|
|
275
|
+
${this.orientLine(o)}`;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
async pressKey(botId, key) {
|
|
279
|
+
return this.withScreen(botId, async (page) => {
|
|
280
|
+
try {
|
|
281
|
+
await page.keyboard.press(key);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
return `Key press "${key}" failed: ${errMsg(err)}
|
|
284
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
285
|
+
}
|
|
286
|
+
return `Pressed "${key}".
|
|
287
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/** Returns visible text (page body, or a selector's text), capped at ~8000 characters. */
|
|
291
|
+
async readText(botId, selector) {
|
|
292
|
+
return this.withScreen(botId, async (page) => {
|
|
293
|
+
let text;
|
|
294
|
+
try {
|
|
295
|
+
text = selector ? await page.$eval(selector, (el) => el.innerText ?? "") ?? "" : await page.evaluate(() => document.body?.innerText ?? "");
|
|
296
|
+
} catch (err) {
|
|
297
|
+
return `Read${selector ? ` of "${selector}"` : ""} failed: ${errMsg(err)}
|
|
298
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
299
|
+
}
|
|
300
|
+
const capped = text.length > 8e3 ? `${text.slice(0, 8e3)}
|
|
301
|
+
\u2026(truncated)` : text;
|
|
302
|
+
return `${this.orientLine(await this.orient(botId, page))}
|
|
303
|
+
---
|
|
304
|
+
${capped}`;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
async readLinks(botId) {
|
|
308
|
+
return this.withScreen(botId, async (page) => {
|
|
309
|
+
let links;
|
|
310
|
+
try {
|
|
311
|
+
links = await page.$$eval(
|
|
312
|
+
"a[href]",
|
|
313
|
+
(els) => els.slice(0, 200).map((e) => ({ text: e.innerText.trim().slice(0, 120), href: e.href }))
|
|
314
|
+
);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
return `Reading links failed: ${errMsg(err)}
|
|
317
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
318
|
+
}
|
|
319
|
+
const body = links.map((l) => `- ${l.text || "(no text)"} -> ${l.href}`).join("\n") || "(no links found)";
|
|
320
|
+
return `${this.orientLine(await this.orient(botId, page))}
|
|
321
|
+
---
|
|
322
|
+
${body}`;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/** Saves a PNG screenshot (to `filePath`, or a temp file if omitted) and returns its path. */
|
|
326
|
+
async screenshot(botId, filePath) {
|
|
327
|
+
return this.withScreen(botId, async (page) => {
|
|
328
|
+
const target = filePath ?? path.join(os.tmpdir(), `antbot-screenshot-${botId}-${Date.now()}.png`);
|
|
329
|
+
try {
|
|
330
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
331
|
+
await page.screenshot({ path: target });
|
|
332
|
+
} catch (err) {
|
|
333
|
+
return `Screenshot failed: ${errMsg(err)}
|
|
334
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
335
|
+
}
|
|
336
|
+
return `Screenshot saved to ${target}
|
|
337
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async currentUrl(botId) {
|
|
341
|
+
return this.withScreen(botId, async (page) => this.orientLine(await this.orient(botId, page)));
|
|
342
|
+
}
|
|
343
|
+
async waitFor(botId, selector, timeoutMs) {
|
|
344
|
+
return this.withScreen(botId, async (page) => {
|
|
345
|
+
try {
|
|
346
|
+
await page.waitForSelector(selector, { timeout: timeoutMs });
|
|
347
|
+
} catch (err) {
|
|
348
|
+
return `Waiting for "${selector}" timed out: ${errMsg(err)}
|
|
349
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
350
|
+
}
|
|
351
|
+
return `Found "${selector}".
|
|
352
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
async scroll(botId, dy) {
|
|
356
|
+
return this.withScreen(botId, async (page) => {
|
|
357
|
+
try {
|
|
358
|
+
await page.mouse.wheel(0, dy);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
return `Scroll failed: ${errMsg(err)}
|
|
361
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
362
|
+
}
|
|
363
|
+
return `Scrolled by ${dy}px.
|
|
364
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
async goBack(botId) {
|
|
368
|
+
return this.withScreen(botId, async (page) => {
|
|
369
|
+
try {
|
|
370
|
+
await page.goBack({ waitUntil: "domcontentloaded", timeout: 15e3 });
|
|
371
|
+
} catch (err) {
|
|
372
|
+
return `Go back failed: ${errMsg(err)}
|
|
373
|
+
${this.orientLine(await this.orient(botId, page))}`;
|
|
374
|
+
}
|
|
375
|
+
const block = await this.checkBlock(botId, page);
|
|
376
|
+
const o = await this.orient(botId, page);
|
|
377
|
+
return block ? `${block}
|
|
378
|
+
${this.orientLine(o)}` : `Went back.
|
|
379
|
+
${this.orientLine(o)}`;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/* ---------------------------------------------------------------------------------------- *
|
|
383
|
+
* Screencast — CDP-driven JPEG frames, throttled to ~4fps, reference-counted per bot so it
|
|
384
|
+
* auto-stops once the last viewer detaches.
|
|
385
|
+
* ---------------------------------------------------------------------------------------- */
|
|
386
|
+
async startScreencast(botId, onFrame) {
|
|
387
|
+
const page = await this.getPage(botId);
|
|
388
|
+
let listeners = this.screencastListeners.get(botId);
|
|
389
|
+
if (!listeners) {
|
|
390
|
+
listeners = /* @__PURE__ */ new Set();
|
|
391
|
+
this.screencastListeners.set(botId, listeners);
|
|
392
|
+
}
|
|
393
|
+
listeners.add(onFrame);
|
|
394
|
+
this.viewerCounts.set(botId, (this.viewerCounts.get(botId) ?? 0) + 1);
|
|
395
|
+
if (!this.screencastSessions.has(botId)) {
|
|
396
|
+
const ctx = await this.ensureContext();
|
|
397
|
+
const session = await ctx.newCDPSession(page);
|
|
398
|
+
const throttle = new FrameThrottle(4);
|
|
399
|
+
this.screencastSessions.set(botId, session);
|
|
400
|
+
this.screencastThrottles.set(botId, throttle);
|
|
401
|
+
session.on("Page.screencastFrame", (evt) => {
|
|
402
|
+
void session.send("Page.screencastFrameAck", { sessionId: evt.sessionId }).catch(() => {
|
|
403
|
+
});
|
|
404
|
+
if (!throttle.shouldEmit()) return;
|
|
405
|
+
const w = evt.metadata?.deviceWidth ?? 0;
|
|
406
|
+
const h = evt.metadata?.deviceHeight ?? 0;
|
|
407
|
+
for (const l of listeners) {
|
|
408
|
+
try {
|
|
409
|
+
l(evt.data, w, h);
|
|
410
|
+
} catch (err) {
|
|
411
|
+
log.warn("screencast frame listener threw", err);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
await session.send("Page.startScreencast", { format: "jpeg", quality: 60, maxWidth: 1280, everyNthFrame: 2 });
|
|
416
|
+
}
|
|
417
|
+
let stopped = false;
|
|
418
|
+
return () => {
|
|
419
|
+
if (stopped) return;
|
|
420
|
+
stopped = true;
|
|
421
|
+
listeners.delete(onFrame);
|
|
422
|
+
const remaining = Math.max(0, (this.viewerCounts.get(botId) ?? 1) - 1);
|
|
423
|
+
this.viewerCounts.set(botId, remaining);
|
|
424
|
+
if (remaining > 0) return;
|
|
425
|
+
const session = this.screencastSessions.get(botId);
|
|
426
|
+
this.screencastSessions.delete(botId);
|
|
427
|
+
this.screencastThrottles.delete(botId);
|
|
428
|
+
this.screencastListeners.delete(botId);
|
|
429
|
+
if (session) {
|
|
430
|
+
void session.send("Page.stopScreencast").catch(() => {
|
|
431
|
+
}).then(() => session.detach().catch(() => {
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
/* ---------------------------------------------------------------------------------------- *
|
|
437
|
+
* Takeover — pauses agent-driven actions on a bot's screen for the human.
|
|
438
|
+
* ---------------------------------------------------------------------------------------- */
|
|
439
|
+
isTakenOver(botId) {
|
|
440
|
+
return this.takenOver.has(botId);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Marks the screen as human-controlled. When headless, there is no window to bring forward
|
|
444
|
+
* (and launching a second headed context on the same profile dir isn't possible — Chromium
|
|
445
|
+
* locks the profile), so the human acts through the screencast view instead; interactive input
|
|
446
|
+
* forwarding through that view is out of scope here. When headed, brings the real window to
|
|
447
|
+
* the front for the human to drive directly.
|
|
448
|
+
*/
|
|
449
|
+
async takeOver(botId) {
|
|
450
|
+
this.takenOver.add(botId);
|
|
451
|
+
if (this.headlessMode) {
|
|
452
|
+
return {
|
|
453
|
+
ok: true,
|
|
454
|
+
mode: "screencast-only",
|
|
455
|
+
message: "This computer is running headless, so there is no window to bring to the front. Act through the screencast view for the blocked step (password, 2FA, CAPTCHA), then return control. Never paste secrets into chat."
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
const page = await this.getPage(botId);
|
|
460
|
+
await page.bringToFront();
|
|
461
|
+
} catch (err) {
|
|
462
|
+
log.warn("failed to bring page to front for takeover", err);
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
ok: true,
|
|
466
|
+
mode: "window",
|
|
467
|
+
message: "The browser window for this bot has been brought to the front. Complete the blocked step, then return control."
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
returnControl(botId) {
|
|
471
|
+
this.takenOver.delete(botId);
|
|
472
|
+
}
|
|
473
|
+
/* ---------------------------------------------------------------------------------------- */
|
|
474
|
+
/** Never launches the browser. Reports optimistically until a launch attempt has failed. */
|
|
475
|
+
status() {
|
|
476
|
+
if (this.unavailableReason) {
|
|
477
|
+
return { available: false, reason: this.unavailableReason, mode: "host", headless: this.headlessMode, pages: [] };
|
|
478
|
+
}
|
|
479
|
+
const pages = [];
|
|
480
|
+
if (this.context) {
|
|
481
|
+
for (const [botId, page] of this.pages) {
|
|
482
|
+
if (page.isClosed()) continue;
|
|
483
|
+
pages.push({ botId, url: page.url(), title: this.titleCache.get(botId) ?? "" });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return { available: true, mode: "host", headless: this.headlessMode, pages };
|
|
487
|
+
}
|
|
488
|
+
async shutdown() {
|
|
489
|
+
for (const session of this.screencastSessions.values()) {
|
|
490
|
+
await session.send("Page.stopScreencast").catch(() => {
|
|
491
|
+
}).then(() => session.detach().catch(() => {
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
this.screencastSessions.clear();
|
|
495
|
+
this.screencastListeners.clear();
|
|
496
|
+
this.screencastThrottles.clear();
|
|
497
|
+
this.viewerCounts.clear();
|
|
498
|
+
for (const page of this.pages.values()) {
|
|
499
|
+
await page.close().catch(() => {
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
this.pages.clear();
|
|
503
|
+
this.titleCache.clear();
|
|
504
|
+
this.takenOver.clear();
|
|
505
|
+
if (this.context) {
|
|
506
|
+
await this.context.close().catch(() => {
|
|
507
|
+
});
|
|
508
|
+
this.context = null;
|
|
509
|
+
}
|
|
510
|
+
this.launchPromise = null;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
export {
|
|
514
|
+
BrowserService,
|
|
515
|
+
BrowserUnavailableError,
|
|
516
|
+
FrameThrottle,
|
|
517
|
+
InvalidUrlError,
|
|
518
|
+
ScreenBusyError,
|
|
519
|
+
ScreenLock,
|
|
520
|
+
ScreenTakenOverError,
|
|
521
|
+
detectBlockFromSignals,
|
|
522
|
+
normalizeUrl
|
|
523
|
+
};
|
|
524
|
+
//# sourceMappingURL=browser-OHRD7YI3.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../packages/server/src/computer/browser.ts"],
|
|
4
|
+
"sourcesContent": ["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { chromium } from 'playwright';\nimport type { BrowserContext, CDPSession, Page } from 'playwright';\nimport type { EventBus } from '../util/bus.js';\nimport { logger } from '../util/log.js';\n\nconst log = logger('browser');\n\n/* ------------------------------------------------------------------------------------------- *\n * Errors\n * ------------------------------------------------------------------------------------------- */\n\n/** Thrown when the browser cannot be launched (e.g. Chromium isn't installed). */\nexport class BrowserUnavailableError extends Error {\n constructor(public readonly reason: string) {\n super(`Browser is unavailable: ${reason}. Try: npx playwright install chromium`);\n this.name = 'BrowserUnavailableError';\n }\n}\n\n/** Thrown by ScreenLock when a bot's screen already has a computer-use task in flight. */\nexport class ScreenBusyError extends Error {\n constructor(public readonly botId: string) {\n super(`That bot's screen is already busy with another computer-use task. Wait for it to finish before starting another.`);\n this.name = 'ScreenBusyError';\n }\n}\n\n/** Thrown by normalizeUrl for disallowed / malformed URLs. */\nexport class InvalidUrlError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidUrlError';\n }\n}\n\n/** Thrown when an action targets a bot screen the human currently has taken over. */\nexport class ScreenTakenOverError extends Error {\n constructor() {\n super('The human has taken control of this screen; wait until they return it.');\n this.name = 'ScreenTakenOverError';\n }\n}\n\n/* ------------------------------------------------------------------------------------------- *\n * Pure helpers (no browser required) \u2014 exported for unit testing.\n * ------------------------------------------------------------------------------------------- */\n\n/**\n * Adds `https://` to bare domains/paths, rejects `javascript:` and `file:` URLs outright, and\n * otherwise passes the input through unchanged. Deliberately permissive about other schemes\n * (e.g. `data:`, `about:`) \u2014 computer-use tasks legitimately need to load those in tests and\n * sandboxes; the block list is specifically the two schemes that are dangerous or meaningless\n * for a screen-driving agent.\n */\nexport function normalizeUrl(input: string): string {\n const trimmed = (input ?? '').trim();\n if (!trimmed) throw new InvalidUrlError('URL is empty.');\n const schemeMatch = trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/);\n if (!schemeMatch) return `https://${trimmed}`;\n const scheme = schemeMatch[1]!.toLowerCase();\n if (scheme === 'javascript') throw new InvalidUrlError('javascript: URLs are not allowed.');\n if (scheme === 'file') throw new InvalidUrlError('file: URLs are not allowed.');\n return trimmed;\n}\n\nexport interface BlockSignals {\n hasPasswordInput: boolean;\n iframeSrcs: string[];\n bodyText: string;\n}\n\nexport interface BlockDetection {\n kind: 'login' | 'captcha' | '2fa';\n message: string;\n}\n\n/**\n * Heuristic login-wall / CAPTCHA / 2FA detector over already-extracted page signals. Kept pure\n * (no Page dependency) so it can run without a browser in unit tests; `BrowserService.detectBlock`\n * gathers the signals from a live Page and delegates here.\n */\nexport function detectBlockFromSignals(signals: BlockSignals): BlockDetection | null {\n const iframeBlob = (signals.iframeSrcs ?? []).join(' ').toLowerCase();\n const body = signals.bodyText ?? '';\n\n if (/recaptcha/i.test(iframeBlob) || /hcaptcha/i.test(iframeBlob)) {\n return { kind: 'captcha', message: 'A CAPTCHA challenge (reCAPTCHA/hCaptcha) is present on this page.' };\n }\n if (/challenges\\.cloudflare\\.com|cf-challenge|turnstile/i.test(iframeBlob) || /checking your browser|cloudflare/i.test(body)) {\n return { kind: 'captcha', message: 'A Cloudflare browser challenge is present on this page.' };\n }\n if (/verify (your \\w+|it'?s you)|two-factor|one-time code/i.test(body)) {\n return { kind: '2fa', message: \"This page is asking for two-factor / one-time-code verification.\" };\n }\n if (signals.hasPasswordInput) {\n return { kind: 'login', message: 'This page has a password field \u2014 it looks like a login wall.' };\n }\n return null;\n}\n\n/**\n * Per-key mutex: `run` executes `fn` immediately if the key is free, otherwise rejects with\n * `ScreenBusyError` rather than queueing \u2014 mirrors \"one computer-use task per Bot screen at a\n * time\" (outline \u00A713): a second concurrent call is a caller bug, not something to serialize.\n */\nexport class ScreenLock {\n private busy = new Set<string>();\n\n isBusy(key: string): boolean {\n return this.busy.has(key);\n }\n\n async run<T>(key: string, fn: () => Promise<T>): Promise<T> {\n if (this.busy.has(key)) throw new ScreenBusyError(key);\n this.busy.add(key);\n try {\n return await fn();\n } finally {\n this.busy.delete(key);\n }\n }\n}\n\n/**\n * Frame-rate limiter for the screencast. `shouldEmit()` returns true at most once per\n * `1000/fps` ms according to the injected clock (defaults to `Date.now`), so tests can drive it\n * deterministically without real timers.\n */\nexport class FrameThrottle {\n private lastEmit = -Infinity;\n\n constructor(\n private readonly fps: number,\n private readonly clock: () => number = () => Date.now(),\n ) {}\n\n get intervalMs(): number {\n return 1000 / this.fps;\n }\n\n shouldEmit(): boolean {\n const now = this.clock();\n if (now - this.lastEmit >= this.intervalMs) {\n this.lastEmit = now;\n return true;\n }\n return false;\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/* ------------------------------------------------------------------------------------------- *\n * BrowserService\n * ------------------------------------------------------------------------------------------- */\n\nexport interface BrowserServiceDeps {\n profileDir: string;\n bus: EventBus;\n headless?: boolean;\n}\n\nexport interface ComputerPageStatus {\n botId: string;\n url: string;\n title: string;\n}\n\nexport interface BrowserStatus {\n available: boolean;\n reason?: string;\n mode: 'host';\n headless: boolean;\n pages: ComputerPageStatus[];\n}\n\nexport interface TakeOverResult {\n ok: boolean;\n mode: 'screencast-only' | 'window';\n message: string;\n}\n\ntype FrameListener = (jpegBase64: string, w: number, h: number) => void;\n\n/**\n * One Playwright persistent browser context shared by every Bot (\"the computer\"), with a\n * dedicated Page (\"screen\") per botId. Because all pages share one persistent context, cookies\n * and logins are shared across bots by design (outline \u00A75: \"one computer, shared by all your\n * Bots\") \u2014 this is intentional, not a bug.\n */\nexport class BrowserService {\n private context: BrowserContext | null = null;\n private launchPromise: Promise<BrowserContext> | null = null;\n private unavailableReason: string | undefined;\n\n private readonly headlessMode: boolean;\n private readonly pages = new Map<string, Page>();\n private readonly titleCache = new Map<string, string>();\n private readonly takenOver = new Set<string>();\n private readonly lock = new ScreenLock();\n\n private readonly screencastSessions = new Map<string, CDPSession>();\n private readonly screencastListeners = new Map<string, Set<FrameListener>>();\n private readonly screencastThrottles = new Map<string, FrameThrottle>();\n private readonly viewerCounts = new Map<string, number>();\n\n constructor(private readonly deps: BrowserServiceDeps) {\n this.headlessMode = deps.headless ?? true;\n }\n\n get isHeadless(): boolean {\n return this.headlessMode;\n }\n\n /** Lazily launches the shared persistent context. Concurrent callers share one launch. */\n private async ensureContext(): Promise<BrowserContext> {\n if (this.context) return this.context;\n if (this.launchPromise) return this.launchPromise;\n\n this.launchPromise = (async () => {\n try {\n fs.mkdirSync(this.deps.profileDir, { recursive: true });\n const ctx = await chromium.launchPersistentContext(this.deps.profileDir, {\n headless: this.headlessMode,\n viewport: { width: 1280, height: 800 },\n });\n this.context = ctx;\n ctx.on('close', () => {\n this.context = null;\n this.launchPromise = null;\n this.pages.clear();\n });\n return ctx;\n } catch (err) {\n this.unavailableReason = errMsg(err);\n this.launchPromise = null;\n throw new BrowserUnavailableError(this.unavailableReason);\n }\n })();\n return this.launchPromise;\n }\n\n /** Returns this bot's dedicated page, creating it lazily. Pages persist across turns. */\n async getPage(botId: string): Promise<Page> {\n const existing = this.pages.get(botId);\n if (existing && !existing.isClosed()) return existing;\n const ctx = await this.ensureContext();\n const page = await ctx.newPage();\n this.pages.set(botId, page);\n page.on('close', () => {\n if (this.pages.get(botId) === page) this.pages.delete(botId);\n });\n return page;\n }\n\n async closePage(botId: string): Promise<void> {\n const page = this.pages.get(botId);\n if (page) {\n await page.close().catch(() => {});\n this.pages.delete(botId);\n }\n this.titleCache.delete(botId);\n this.takenOver.delete(botId);\n }\n\n /** Serializes computer-use tasks per bot screen; a concurrent second call is rejected. */\n async withScreen<T>(botId: string, fn: (page: Page) => Promise<T>): Promise<T> {\n return this.lock.run(botId, async () => {\n if (this.takenOver.has(botId)) throw new ScreenTakenOverError();\n const page = await this.getPage(botId);\n return fn(page);\n });\n }\n\n private async orient(botId: string, page: Page): Promise<{ url: string; title: string }> {\n const url = page.url();\n let title = this.titleCache.get(botId) ?? '';\n try {\n title = await page.title();\n this.titleCache.set(botId, title);\n } catch {\n // page may be mid-navigation or closed; fall back to the cached title.\n }\n return { url, title };\n }\n\n private orientLine(o: { url: string; title: string }): string {\n return `URL: ${o.url}\\nTitle: ${o.title}`;\n }\n\n /** Gathers page signals and runs them through the pure detector. Never throws. */\n async detectBlock(page: Page): Promise<BlockDetection | null> {\n try {\n const hasPasswordInput = (await page.$$('input[type=\"password\"]').catch(() => [])).length > 0;\n const iframeSrcs = await page\n .$$eval('iframe', (els) => els.map((e) => e.getAttribute('src') ?? ''))\n .catch(() => [] as string[]);\n const bodyText = await page\n .evaluate(() => document.body?.innerText ?? '')\n .catch(() => '');\n return detectBlockFromSignals({ hasPasswordInput, iframeSrcs, bodyText: bodyText.slice(0, 4000) });\n } catch {\n return null;\n }\n }\n\n /** If a block is detected, publishes a warn notification and returns the STOP text to prepend. */\n private async checkBlock(botId: string, page: Page): Promise<string | null> {\n const block = await this.detectBlock(page);\n if (!block) return null;\n this.deps.bus.publish({\n type: 'notify',\n botId,\n threadId: null,\n level: 'warn',\n title: 'Browser needs a human',\n body: block.message,\n });\n return (\n `${block.message} STOP: do not attempt to solve or bypass this yourself \u2014 ` +\n `ask the human to take over the computer and wait for them to return control.`\n );\n }\n\n /* ---------------------------------------------------------------------------------------- *\n * Actions \u2014 each returns a short result string including the resulting URL + title, and is\n * safe to call even if the page has navigated away, closed, or errored mid-action.\n * ---------------------------------------------------------------------------------------- */\n\n async navigate(botId: string, url: string): Promise<string> {\n const target = normalizeUrl(url);\n return this.withScreen(botId, async (page) => {\n try {\n await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 30000 });\n } catch (err) {\n return `Navigation to ${target} failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Navigated.\\n${this.orientLine(o)}`;\n });\n }\n\n async click(botId: string, selector: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.click(selector, { timeout: 10000 });\n } catch (err) {\n return `Click on \"${selector}\" failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Clicked \"${selector}\".\\n${this.orientLine(o)}`;\n });\n }\n\n async type(botId: string, selector: string, text: string, opts?: { submit?: boolean }): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n const isPassword = await page\n .$eval(selector, (el) => (el as HTMLInputElement).type === 'password')\n .catch(() => false);\n if (isPassword) {\n return (\n `Refused: \"${selector}\" looks like a password field. Never type credentials into the browser \u2014 ` +\n `ask the human to take over the computer instead.\\n${this.orientLine(await this.orient(botId, page))}`\n );\n }\n await page.fill(selector, text, { timeout: 10000 });\n if (opts?.submit) await page.press(selector, 'Enter');\n } catch (err) {\n return `Type into \"${selector}\" failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Typed into \"${selector}\".\\n${this.orientLine(o)}`;\n });\n }\n\n async pressKey(botId: string, key: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.keyboard.press(key);\n } catch (err) {\n return `Key press \"${key}\" failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Pressed \"${key}\".\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n /** Returns visible text (page body, or a selector's text), capped at ~8000 characters. */\n async readText(botId: string, selector?: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n let text: string;\n try {\n text = selector\n ? ((await page.$eval(selector, (el) => (el as HTMLElement).innerText ?? '')) ?? '')\n : await page.evaluate(() => document.body?.innerText ?? '');\n } catch (err) {\n return `Read${selector ? ` of \"${selector}\"` : ''} failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const capped = text.length > 8000 ? `${text.slice(0, 8000)}\\n\u2026(truncated)` : text;\n return `${this.orientLine(await this.orient(botId, page))}\\n---\\n${capped}`;\n });\n }\n\n async readLinks(botId: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n let links: Array<{ text: string; href: string }>;\n try {\n links = await page.$$eval('a[href]', (els) =>\n els\n .slice(0, 200)\n .map((e) => ({ text: (e as HTMLElement).innerText.trim().slice(0, 120), href: (e as HTMLAnchorElement).href })),\n );\n } catch (err) {\n return `Reading links failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const body = links.map((l) => `- ${l.text || '(no text)'} -> ${l.href}`).join('\\n') || '(no links found)';\n return `${this.orientLine(await this.orient(botId, page))}\\n---\\n${body}`;\n });\n }\n\n /** Saves a PNG screenshot (to `filePath`, or a temp file if omitted) and returns its path. */\n async screenshot(botId: string, filePath?: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n const target = filePath ?? path.join(os.tmpdir(), `antbot-screenshot-${botId}-${Date.now()}.png`);\n try {\n fs.mkdirSync(path.dirname(target), { recursive: true });\n await page.screenshot({ path: target });\n } catch (err) {\n return `Screenshot failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Screenshot saved to ${target}\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n async currentUrl(botId: string): Promise<string> {\n return this.withScreen(botId, async (page) => this.orientLine(await this.orient(botId, page)));\n }\n\n async waitFor(botId: string, selector: string, timeoutMs: number): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.waitForSelector(selector, { timeout: timeoutMs });\n } catch (err) {\n return `Waiting for \"${selector}\" timed out: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Found \"${selector}\".\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n async scroll(botId: string, dy: number): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.mouse.wheel(0, dy);\n } catch (err) {\n return `Scroll failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n return `Scrolled by ${dy}px.\\n${this.orientLine(await this.orient(botId, page))}`;\n });\n }\n\n async goBack(botId: string): Promise<string> {\n return this.withScreen(botId, async (page) => {\n try {\n await page.goBack({ waitUntil: 'domcontentloaded', timeout: 15000 });\n } catch (err) {\n return `Go back failed: ${errMsg(err)}\\n${this.orientLine(await this.orient(botId, page))}`;\n }\n const block = await this.checkBlock(botId, page);\n const o = await this.orient(botId, page);\n return block ? `${block}\\n${this.orientLine(o)}` : `Went back.\\n${this.orientLine(o)}`;\n });\n }\n\n /* ---------------------------------------------------------------------------------------- *\n * Screencast \u2014 CDP-driven JPEG frames, throttled to ~4fps, reference-counted per bot so it\n * auto-stops once the last viewer detaches.\n * ---------------------------------------------------------------------------------------- */\n\n async startScreencast(botId: string, onFrame: FrameListener): Promise<() => void> {\n const page = await this.getPage(botId);\n\n let listeners = this.screencastListeners.get(botId);\n if (!listeners) {\n listeners = new Set();\n this.screencastListeners.set(botId, listeners);\n }\n listeners.add(onFrame);\n this.viewerCounts.set(botId, (this.viewerCounts.get(botId) ?? 0) + 1);\n\n if (!this.screencastSessions.has(botId)) {\n const ctx = await this.ensureContext();\n const session = await ctx.newCDPSession(page);\n const throttle = new FrameThrottle(4);\n this.screencastSessions.set(botId, session);\n this.screencastThrottles.set(botId, throttle);\n\n session.on('Page.screencastFrame', (evt) => {\n void session.send('Page.screencastFrameAck', { sessionId: evt.sessionId }).catch(() => {});\n if (!throttle.shouldEmit()) return;\n const w = evt.metadata?.deviceWidth ?? 0;\n const h = evt.metadata?.deviceHeight ?? 0;\n for (const l of listeners!) {\n try {\n l(evt.data, w, h);\n } catch (err) {\n log.warn('screencast frame listener threw', err);\n }\n }\n });\n\n await session.send('Page.startScreencast', { format: 'jpeg', quality: 60, maxWidth: 1280, everyNthFrame: 2 });\n }\n\n let stopped = false;\n return () => {\n if (stopped) return;\n stopped = true;\n listeners!.delete(onFrame);\n const remaining = Math.max(0, (this.viewerCounts.get(botId) ?? 1) - 1);\n this.viewerCounts.set(botId, remaining);\n if (remaining > 0) return;\n\n const session = this.screencastSessions.get(botId);\n this.screencastSessions.delete(botId);\n this.screencastThrottles.delete(botId);\n this.screencastListeners.delete(botId);\n if (session) {\n void session\n .send('Page.stopScreencast')\n .catch(() => {})\n .then(() => session.detach().catch(() => {}));\n }\n };\n }\n\n /* ---------------------------------------------------------------------------------------- *\n * Takeover \u2014 pauses agent-driven actions on a bot's screen for the human.\n * ---------------------------------------------------------------------------------------- */\n\n isTakenOver(botId: string): boolean {\n return this.takenOver.has(botId);\n }\n\n /**\n * Marks the screen as human-controlled. When headless, there is no window to bring forward\n * (and launching a second headed context on the same profile dir isn't possible \u2014 Chromium\n * locks the profile), so the human acts through the screencast view instead; interactive input\n * forwarding through that view is out of scope here. When headed, brings the real window to\n * the front for the human to drive directly.\n */\n async takeOver(botId: string): Promise<TakeOverResult> {\n this.takenOver.add(botId);\n if (this.headlessMode) {\n return {\n ok: true,\n mode: 'screencast-only',\n message:\n 'This computer is running headless, so there is no window to bring to the front. ' +\n 'Act through the screencast view for the blocked step (password, 2FA, CAPTCHA), then return control. ' +\n 'Never paste secrets into chat.',\n };\n }\n try {\n const page = await this.getPage(botId);\n await page.bringToFront();\n } catch (err) {\n log.warn('failed to bring page to front for takeover', err);\n }\n return {\n ok: true,\n mode: 'window',\n message: 'The browser window for this bot has been brought to the front. Complete the blocked step, then return control.',\n };\n }\n\n returnControl(botId: string): void {\n this.takenOver.delete(botId);\n }\n\n /* ---------------------------------------------------------------------------------------- */\n\n /** Never launches the browser. Reports optimistically until a launch attempt has failed. */\n status(): BrowserStatus {\n if (this.unavailableReason) {\n return { available: false, reason: this.unavailableReason, mode: 'host', headless: this.headlessMode, pages: [] };\n }\n const pages: ComputerPageStatus[] = [];\n if (this.context) {\n for (const [botId, page] of this.pages) {\n if (page.isClosed()) continue;\n pages.push({ botId, url: page.url(), title: this.titleCache.get(botId) ?? '' });\n }\n }\n return { available: true, mode: 'host', headless: this.headlessMode, pages };\n }\n\n async shutdown(): Promise<void> {\n for (const session of this.screencastSessions.values()) {\n await session\n .send('Page.stopScreencast')\n .catch(() => {})\n .then(() => session.detach().catch(() => {}));\n }\n this.screencastSessions.clear();\n this.screencastListeners.clear();\n this.screencastThrottles.clear();\n this.viewerCounts.clear();\n\n for (const page of this.pages.values()) {\n await page.close().catch(() => {});\n }\n this.pages.clear();\n this.titleCache.clear();\n this.takenOver.clear();\n\n if (this.context) {\n await this.context.close().catch(() => {});\n this.context = null;\n }\n this.launchPromise = null;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|