granclaw 0.0.1-beta.32 ā 0.0.1-beta.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/agent/process.js +14 -0
- package/dist/backend/agent/runner-pi.js +213 -1
- package/dist/backend/agent/telegram-adapter.js +36 -6
- package/dist/backend/assets/capmonster-extension/_locales/en/messages.json +226 -0
- package/dist/backend/assets/capmonster-extension/_locales/ru/messages.json +226 -0
- package/dist/backend/assets/capmonster-extension/_metadata/verified_contents.json +1 -0
- package/dist/backend/assets/capmonster-extension/background.js +2 -0
- package/dist/backend/assets/capmonster-extension/background.js.LICENSE.txt +29 -0
- package/dist/backend/assets/capmonster-extension/binanceInterceptor.js +1 -0
- package/dist/backend/assets/capmonster-extension/blsInterceptor.js +2 -0
- package/dist/backend/assets/capmonster-extension/blsInterceptor.js.LICENSE.txt +29 -0
- package/dist/backend/assets/capmonster-extension/content.js +2 -0
- package/dist/backend/assets/capmonster-extension/content.js.LICENSE.txt +29 -0
- package/dist/backend/assets/capmonster-extension/css/antd.variable.min.css +10 -0
- package/dist/backend/assets/capmonster-extension/css/content/solver.css +152 -0
- package/dist/backend/assets/capmonster-extension/css/popup/styles.css +110 -0
- package/dist/backend/assets/capmonster-extension/defaultSettings.json +62 -0
- package/dist/backend/assets/capmonster-extension/devtools/devtools.html +1 -0
- package/dist/backend/assets/capmonster-extension/devtools/devtools.js +1 -0
- package/dist/backend/assets/capmonster-extension/devtools/panel.html +11 -0
- package/dist/backend/assets/capmonster-extension/fonts/roboto/Roboto-Bold.ttf +0 -0
- package/dist/backend/assets/capmonster-extension/fonts/roboto/Roboto-Medium.ttf +0 -0
- package/dist/backend/assets/capmonster-extension/fonts/roboto/Roboto-Regular.ttf +0 -0
- package/dist/backend/assets/capmonster-extension/fonts/ubuntu/Ubuntu-B.ttf +0 -0
- package/dist/backend/assets/capmonster-extension/geetestInterceptor.js +1 -0
- package/dist/backend/assets/capmonster-extension/hcaptcha.js +2 -0
- package/dist/backend/assets/capmonster-extension/hcaptcha.js.LICENSE.txt +29 -0
- package/dist/backend/assets/capmonster-extension/hcaptchaInterceptor.js +1 -0
- package/dist/backend/assets/capmonster-extension/img/20x20_binance.svg +14 -0
- package/dist/backend/assets/capmonster-extension/img/20x20_bls.svg +9 -0
- package/dist/backend/assets/capmonster-extension/img/20x20_geetest.svg +6 -0
- package/dist/backend/assets/capmonster-extension/img/20x20_hcaptcha.svg +28 -0
- package/dist/backend/assets/capmonster-extension/img/20x20_recaptcha.svg +5 -0
- package/dist/backend/assets/capmonster-extension/img/20x20_text_captcha.svg +5 -0
- package/dist/backend/assets/capmonster-extension/img/20x20_turnstile.svg +11 -0
- package/dist/backend/assets/capmonster-extension/img/blue-cogs-animated.gif +0 -0
- package/dist/backend/assets/capmonster-extension/img/green-cogs.png +0 -0
- package/dist/backend/assets/capmonster-extension/img/icon.png +0 -0
- package/dist/backend/assets/capmonster-extension/img/logo.svg +20 -0
- package/dist/backend/assets/capmonster-extension/img/logo_icon.png +0 -0
- package/dist/backend/assets/capmonster-extension/img/red-cogs.png +0 -0
- package/dist/backend/assets/capmonster-extension/img/white-cogs.png +0 -0
- package/dist/backend/assets/capmonster-extension/manifest.json +107 -0
- package/dist/backend/assets/capmonster-extension/manifest_chrome.json +105 -0
- package/dist/backend/assets/capmonster-extension/manifest_firefox.json +120 -0
- package/dist/backend/assets/capmonster-extension/pageScript.js +1 -0
- package/dist/backend/assets/capmonster-extension/pageScriptHandler.js +2 -0
- package/dist/backend/assets/capmonster-extension/pageScriptHandler.js.LICENSE.txt +14 -0
- package/dist/backend/assets/capmonster-extension/panel.js +16 -0
- package/dist/backend/assets/capmonster-extension/panel.js.LICENSE.txt +90 -0
- package/dist/backend/assets/capmonster-extension/polyfills/browser-polyfill.js +1 -0
- package/dist/backend/assets/capmonster-extension/popup.html +13 -0
- package/dist/backend/assets/capmonster-extension/popup.js +222 -0
- package/dist/backend/assets/capmonster-extension/popup.js.LICENSE.txt +90 -0
- package/dist/backend/assets/capmonster-extension/recaptcha.js +2 -0
- package/dist/backend/assets/capmonster-extension/recaptcha.js.LICENSE.txt +29 -0
- package/dist/backend/assets/capmonster-extension/recaptcha2Interceptor.js +1 -0
- package/dist/backend/assets/capmonster-extension/recaptcha3Interceptor.js +1 -0
- package/dist/backend/assets/capmonster-extension/turnstileInterceptor.js +2 -0
- package/dist/backend/assets/capmonster-extension/turnstileInterceptor.js.LICENSE.txt +14 -0
- package/dist/backend/assets/stealth-extension/stealth.js +82 -37
- package/dist/backend/browser/stealth.js +82 -10
- package/dist/backend/lib/i18n-telegram.js +6 -0
- package/dist/frontend/assets/index-C89DY7ra.js +144 -0
- package/dist/frontend/assets/index-Cwyc7GKp.css +1 -0
- package/dist/frontend/index.html +2 -2
- package/package.json +1 -1
- package/templates/AGENT.onboarding.md +1 -1
- package/dist/frontend/assets/index-0VQ9YZWd.js +0 -144
- package/dist/frontend/assets/index-Dq3ELXQB.css +0 -1
|
@@ -169,6 +169,20 @@ function main() {
|
|
|
169
169
|
telegramAdapter.appendChunk(job.channelId, chunk.text);
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
if (chunk.type === 'takeover_requested') {
|
|
173
|
+
const takeoverUrl = chunk.takeoverUrl;
|
|
174
|
+
if (isTelegramJob) {
|
|
175
|
+
telegramAdapter.notifyTakeover(job.channelId, takeoverUrl);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// For UI/WebSocket channels inject the URL as a text chunk so it
|
|
179
|
+
// appears inline in the streaming response.
|
|
180
|
+
broadcastToChannel(job.channelId, {
|
|
181
|
+
type: 'chunk',
|
|
182
|
+
chunk: { type: 'text', text: `\n\nš **Takeover link:** ${takeoverUrl}` },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
172
186
|
if (chunk.type === 'tool_call') {
|
|
173
187
|
const tcString = `${chunk.tool}(${JSON.stringify(chunk.input)})`;
|
|
174
188
|
toolCallCount++;
|
|
@@ -26,6 +26,19 @@ const path_1 = __importDefault(require("path"));
|
|
|
26
26
|
const fs_1 = __importDefault(require("fs"));
|
|
27
27
|
const child_process_1 = require("child_process");
|
|
28
28
|
const util_1 = require("util");
|
|
29
|
+
const node_html_markdown_1 = require("node-html-markdown");
|
|
30
|
+
const undici_1 = require("undici");
|
|
31
|
+
// Cache ProxyAgent instances by URL so the same connection pool (and exit IP)
|
|
32
|
+
// is reused across all fetch_website calls for the same proxy.
|
|
33
|
+
const proxyAgentCache = new Map();
|
|
34
|
+
function getProxyAgent(proxyUrl) {
|
|
35
|
+
let agent = proxyAgentCache.get(proxyUrl);
|
|
36
|
+
if (!agent) {
|
|
37
|
+
agent = new undici_1.ProxyAgent(proxyUrl);
|
|
38
|
+
proxyAgentCache.set(proxyUrl, agent);
|
|
39
|
+
}
|
|
40
|
+
return agent;
|
|
41
|
+
}
|
|
29
42
|
const os_1 = require("os");
|
|
30
43
|
const crypto_1 = require("crypto");
|
|
31
44
|
const takeover_state_js_1 = require("../takeover-state.js");
|
|
@@ -255,6 +268,37 @@ function getLanIp() {
|
|
|
255
268
|
}
|
|
256
269
|
return 'localhost';
|
|
257
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Stable hash of an agent ID string ā non-negative integer.
|
|
273
|
+
* Used so proxy assignment is deterministic by agent ID, not config order.
|
|
274
|
+
*/
|
|
275
|
+
function hashAgentId(id) {
|
|
276
|
+
let h = 0;
|
|
277
|
+
for (const c of id)
|
|
278
|
+
h = (Math.imul(31, h) + c.charCodeAt(0)) | 0;
|
|
279
|
+
return Math.abs(h);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Resolve the proxy for an agent.
|
|
283
|
+
* Priority: agent.proxy config ā GRANCLAW_PROXY_LIST (stable hash by agent ID) ā undefined.
|
|
284
|
+
* Hash-based assignment means the same agent always gets the same proxy even if
|
|
285
|
+
* agents.config.json is reordered or new agents are added ā so the Chrome daemon
|
|
286
|
+
* always restarts on the same IP.
|
|
287
|
+
* Only applies when GRANCLAW_PROXY_ENABLE=true or agent.proxy is explicitly set.
|
|
288
|
+
*/
|
|
289
|
+
function resolveAgentProxy(agentId, configProxy) {
|
|
290
|
+
if (configProxy)
|
|
291
|
+
return configProxy;
|
|
292
|
+
if (process.env.GRANCLAW_PROXY_ENABLE !== 'true')
|
|
293
|
+
return undefined;
|
|
294
|
+
const proxyList = process.env.GRANCLAW_PROXY_LIST;
|
|
295
|
+
if (!proxyList)
|
|
296
|
+
return undefined;
|
|
297
|
+
const proxies = proxyList.split(',').map(p => p.trim()).filter(Boolean);
|
|
298
|
+
if (proxies.length === 0)
|
|
299
|
+
return undefined;
|
|
300
|
+
return proxies[hashAgentId(agentId) % proxies.length];
|
|
301
|
+
}
|
|
258
302
|
async function runAgent(agent, message, onChunk, options) {
|
|
259
303
|
const workspaceDir = path_1.default.resolve(config_js_1.REPO_ROOT, agent.workspaceDir);
|
|
260
304
|
const channelId = options?.channelId ?? 'ui';
|
|
@@ -638,6 +682,50 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
638
682
|
//
|
|
639
683
|
// Privileged commands (record, close, tab close for session 0,
|
|
640
684
|
// session management) are rejected ā the runtime owns those.
|
|
685
|
+
/**
|
|
686
|
+
* After a navigation command, check if a CAPTCHA is blocking the page.
|
|
687
|
+
* Waits up to 30s for the CapMonster extension to auto-solve it.
|
|
688
|
+
* Returns 'clear' (no captcha / solved), 'unsolved' (still blocked after timeout).
|
|
689
|
+
*/
|
|
690
|
+
async function waitForCaptchaResolution(bin, sessionId) {
|
|
691
|
+
const CAPTCHA_JS = `(function() {
|
|
692
|
+
var patterns = [
|
|
693
|
+
'iframe[src*="captcha-delivery"]',
|
|
694
|
+
'iframe[src*="geo.captcha"]',
|
|
695
|
+
'iframe[src*="recaptcha"]',
|
|
696
|
+
'iframe[src*="hcaptcha"]',
|
|
697
|
+
'iframe[src*="challenges.cloudflare"]',
|
|
698
|
+
'.g-recaptcha',
|
|
699
|
+
'.h-captcha',
|
|
700
|
+
'[class*="captcha"]',
|
|
701
|
+
];
|
|
702
|
+
return patterns.some(function(s) {
|
|
703
|
+
return !!document.querySelector(s);
|
|
704
|
+
}) ? 'captcha' : 'clear';
|
|
705
|
+
})()`;
|
|
706
|
+
const evalArgv = (id) => ['--session', id, 'eval', CAPTCHA_JS];
|
|
707
|
+
const check = async () => {
|
|
708
|
+
try {
|
|
709
|
+
const { stdout } = await execFileAsync(bin, evalArgv(sessionId), { timeout: 8_000 });
|
|
710
|
+
return stdout.includes('"captcha"') || stdout.includes("'captcha'") || stdout.trim() === 'captcha';
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
// Initial check ā short delay to let page settle
|
|
717
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
718
|
+
if (!await check())
|
|
719
|
+
return 'clear';
|
|
720
|
+
// CAPTCHA detected ā wait up to 30s for auto-solve
|
|
721
|
+
const deadline = Date.now() + 30_000;
|
|
722
|
+
while (Date.now() < deadline) {
|
|
723
|
+
await new Promise(r => setTimeout(r, 2_000));
|
|
724
|
+
if (!await check())
|
|
725
|
+
return 'clear';
|
|
726
|
+
}
|
|
727
|
+
return 'unsolved';
|
|
728
|
+
}
|
|
641
729
|
extensionFactories.push((pi) => {
|
|
642
730
|
const agentBrowserBin = process.env.AGENT_BROWSER_BIN ?? 'agent-browser';
|
|
643
731
|
const profileDir = path_1.default.join(workspaceDir, '.browser-profile');
|
|
@@ -660,12 +748,15 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
660
748
|
'or `session`, they are rejected. Every turn is recorded as a single WebM video visible in the dashboard Browser view.',
|
|
661
749
|
promptSnippet: 'Control a headless browser ā navigate, click, fill, snapshot, extract data',
|
|
662
750
|
promptGuidelines: [
|
|
751
|
+
'Use browser for: real-time navigation, login flows, write/post/update operations (LinkedIn, Reddit, social media), pages requiring JS interaction, and multi-step forms.',
|
|
752
|
+
'Do NOT use browser to just read a webpage ā use fetch_website instead (faster, lighter, no screenshot overhead).',
|
|
663
753
|
'Core loop: open ā snapshot ā interact ā re-snapshot. Refs from snapshot are invalidated by navigation.',
|
|
664
754
|
'For visual reasoning use `snapshot --annotate -i` (annotated screenshot with numbered refs).',
|
|
665
755
|
'For data extraction use `snapshot` (plain accessibility tree) or `text --ref <ref>`.',
|
|
666
756
|
'Do not call `record start`, `record stop`, `close`, or `session` ā the runtime manages those.',
|
|
667
757
|
'You do not need to screenshot for audit ā the whole session is recorded as video automatically.',
|
|
668
758
|
'Saved logins persist automatically when the user has set up a profile via the dashboard Browser view.',
|
|
759
|
+
'CAPTCHA handling: if a page returns a CAPTCHA, wait ā the browser has an automatic solver extension. If it is not resolved after ~30 seconds, use request_human_browser_takeover to let the user solve it.',
|
|
669
760
|
'Examples: {"command":"open","args":["https://example.com"]}, {"command":"click","args":["--ref","e12"]}, {"command":"fill","args":["--ref","e5","Alice"]}',
|
|
670
761
|
],
|
|
671
762
|
parameters: {
|
|
@@ -718,7 +809,11 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
718
809
|
if (fs_1.default.existsSync(profileDir)) {
|
|
719
810
|
argv.push('--profile', profileDir);
|
|
720
811
|
}
|
|
721
|
-
|
|
812
|
+
const stealthOpts = {
|
|
813
|
+
proxy: resolveAgentProxy(agent.id, agent.proxy),
|
|
814
|
+
capmonsterKey: agent.capmonsterKey,
|
|
815
|
+
};
|
|
816
|
+
argv.push(...(0, stealth_js_1.stealthArgv)(stealthOpts));
|
|
722
817
|
argv.push(command, ...args);
|
|
723
818
|
try {
|
|
724
819
|
const { stdout, stderr } = await execFileAsync(agentBrowserBin, argv, {
|
|
@@ -728,6 +823,20 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
728
823
|
});
|
|
729
824
|
(0, session_manager_js_1.appendCommand)(browserState.handle, `${command} ${args.join(' ')}`.trim());
|
|
730
825
|
const out = stdout.trim() || stderr.trim() || 'ok';
|
|
826
|
+
// After navigation, check for CAPTCHA and wait up to 30s for the
|
|
827
|
+
// CapMonster extension to auto-solve it before returning to the agent.
|
|
828
|
+
if (command === 'open' || command === 'reload') {
|
|
829
|
+
const captchaResult = await waitForCaptchaResolution(agentBrowserBin, agent.id);
|
|
830
|
+
if (captchaResult === 'unsolved') {
|
|
831
|
+
return {
|
|
832
|
+
content: [{
|
|
833
|
+
type: 'text',
|
|
834
|
+
text: out + '\n\nā ļø CAPTCHA detected and not auto-solved after 30s. ' +
|
|
835
|
+
'Use request_human_browser_takeover to let the user solve it, or try a different URL.',
|
|
836
|
+
}],
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
}
|
|
731
840
|
return { content: [{ type: 'text', text: out }] };
|
|
732
841
|
}
|
|
733
842
|
catch (err) {
|
|
@@ -826,6 +935,7 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
826
935
|
'Use for any question about recent events, current prices, live data, or information past your training cutoff.',
|
|
827
936
|
'Prefer specific, focused queries over vague ones.',
|
|
828
937
|
'You can call this tool multiple times to refine results.',
|
|
938
|
+
'After getting results, verify URLs with fetch_website before sharing with the user ā confirms the page loads and is not paywalled or broken.',
|
|
829
939
|
],
|
|
830
940
|
parameters: {
|
|
831
941
|
type: 'object',
|
|
@@ -862,6 +972,108 @@ async function runAgent(agent, message, onChunk, options) {
|
|
|
862
972
|
});
|
|
863
973
|
});
|
|
864
974
|
}
|
|
975
|
+
// fetch_website tool: fetches a URL and returns clean trimmed markdown.
|
|
976
|
+
// Normal mode: plain HTTP GET. Unblocker mode: Bright Data Web Unlocker API.
|
|
977
|
+
// Always registered ā no key required for basic usage.
|
|
978
|
+
extensionFactories.push((pi) => {
|
|
979
|
+
const nhm = new node_html_markdown_1.NodeHtmlMarkdown();
|
|
980
|
+
function htmlToMarkdown(html, maxChars = 4000) {
|
|
981
|
+
let md = nhm.translate(html).replace(/\n{3,}/g, '\n\n').trim();
|
|
982
|
+
if (md.length > maxChars) {
|
|
983
|
+
md = md.slice(0, maxChars) + `\n\n[...truncated at ${maxChars} chars]`;
|
|
984
|
+
}
|
|
985
|
+
return md;
|
|
986
|
+
}
|
|
987
|
+
pi.registerTool({
|
|
988
|
+
name: 'fetch_website',
|
|
989
|
+
label: 'Fetch Website',
|
|
990
|
+
description: 'Fetch a webpage and return its content as trimmed markdown. ' +
|
|
991
|
+
'Use to read web pages, verify URLs from search results, or scrape public content. ' +
|
|
992
|
+
'Set unblocker=true if the site blocks normal requests (bot-detection, Cloudflare, DataDome). ' +
|
|
993
|
+
'Prefer this over browser for read-only operations ā it is faster and uses less context.',
|
|
994
|
+
promptSnippet: 'Fetch and read a webpage as markdown',
|
|
995
|
+
promptGuidelines: [
|
|
996
|
+
'Use fetch_website (not browser) when you only need to read a page ā faster and no screenshot overhead.',
|
|
997
|
+
'Use fetch_website to verify URLs from web_search results before sharing with the user.',
|
|
998
|
+
'Set unblocker=true only after being blocked (captcha/403) on the same domain with unblocker=false.',
|
|
999
|
+
'Output is truncated at 4000 chars. For interactive or login-gated pages, use browser instead.',
|
|
1000
|
+
],
|
|
1001
|
+
parameters: {
|
|
1002
|
+
type: 'object',
|
|
1003
|
+
properties: {
|
|
1004
|
+
url: { type: 'string', description: 'The URL to fetch' },
|
|
1005
|
+
unblocker: {
|
|
1006
|
+
type: 'boolean',
|
|
1007
|
+
description: 'Route through Bright Data Web Unlocker to bypass bot protection (default: false)',
|
|
1008
|
+
},
|
|
1009
|
+
},
|
|
1010
|
+
required: ['url'],
|
|
1011
|
+
},
|
|
1012
|
+
async execute(_toolCallId, params) {
|
|
1013
|
+
const useUnblocker = !!params.unblocker;
|
|
1014
|
+
if (useUnblocker) {
|
|
1015
|
+
const unblockerKey = process.env.BRIGHTDATA_UNBLOCKER_KEY;
|
|
1016
|
+
const unblockerEnabled = process.env.BRIGHTDATA_UNBLOCKER_ENABLED !== 'false';
|
|
1017
|
+
if (!unblockerKey || !unblockerEnabled) {
|
|
1018
|
+
return {
|
|
1019
|
+
content: [{
|
|
1020
|
+
type: 'text',
|
|
1021
|
+
text: 'fetch_website: Bright Data unblocker not configured. Set BRIGHTDATA_UNBLOCKER_KEY and BRIGHTDATA_UNBLOCKER_ENABLED=true.',
|
|
1022
|
+
}],
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
try {
|
|
1026
|
+
const res = await fetch('https://api.brightdata.com/request', {
|
|
1027
|
+
method: 'POST',
|
|
1028
|
+
headers: {
|
|
1029
|
+
'Content-Type': 'application/json',
|
|
1030
|
+
'Authorization': `Bearer ${unblockerKey}`,
|
|
1031
|
+
},
|
|
1032
|
+
body: JSON.stringify({ zone: 'web_unlocker1', url: params.url, format: 'raw' }),
|
|
1033
|
+
signal: AbortSignal.timeout(30_000),
|
|
1034
|
+
});
|
|
1035
|
+
if (!res.ok) {
|
|
1036
|
+
return { content: [{ type: 'text', text: `fetch_website (unblocker): HTTP ${res.status} ${res.statusText}` }] };
|
|
1037
|
+
}
|
|
1038
|
+
const html = await res.text();
|
|
1039
|
+
const md = htmlToMarkdown(html);
|
|
1040
|
+
return { content: [{ type: 'text', text: md }] };
|
|
1041
|
+
}
|
|
1042
|
+
catch (err) {
|
|
1043
|
+
return { content: [{ type: 'text', text: `fetch_website (unblocker) error: ${err instanceof Error ? err.message : String(err)}` }] };
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
// Plain HTTP fetch ā route through proxy if configured
|
|
1047
|
+
try {
|
|
1048
|
+
const agentProxy = resolveAgentProxy(agent.id, agent.proxy);
|
|
1049
|
+
const fetchOpts = {
|
|
1050
|
+
headers: {
|
|
1051
|
+
'User-Agent': 'Mozilla/5.0 (compatible; GranClaw/1.0; +https://granclaw.com)',
|
|
1052
|
+
'Accept': 'text/html,application/xhtml+xml,*/*;q=0.9',
|
|
1053
|
+
},
|
|
1054
|
+
signal: AbortSignal.timeout(15_000),
|
|
1055
|
+
redirect: 'follow',
|
|
1056
|
+
...(agentProxy ? { dispatcher: getProxyAgent(agentProxy) } : {}),
|
|
1057
|
+
};
|
|
1058
|
+
const res = await (0, undici_1.fetch)(params.url, fetchOpts);
|
|
1059
|
+
if (!res.ok) {
|
|
1060
|
+
return { content: [{ type: 'text', text: `fetch_website: HTTP ${res.status} ${res.statusText}. Try unblocker=true if blocked.` }] };
|
|
1061
|
+
}
|
|
1062
|
+
const contentType = res.headers.get('content-type') ?? '';
|
|
1063
|
+
const body = await res.text();
|
|
1064
|
+
if (!contentType.includes('html')) {
|
|
1065
|
+
const out = body.length > 4000 ? body.slice(0, 4000) + '\n\n[...truncated]' : body;
|
|
1066
|
+
return { content: [{ type: 'text', text: out }] };
|
|
1067
|
+
}
|
|
1068
|
+
const md = htmlToMarkdown(body);
|
|
1069
|
+
return { content: [{ type: 'text', text: md }] };
|
|
1070
|
+
}
|
|
1071
|
+
catch (err) {
|
|
1072
|
+
return { content: [{ type: 'text', text: `fetch_website error: ${err instanceof Error ? err.message : String(err)}` }] };
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
865
1077
|
// Build resource loader. Must call reload() before passing to createAgentSession ā
|
|
866
1078
|
// when the sdk receives a pre-built resourceLoader it skips reload().
|
|
867
1079
|
const resourceLoader = new DefaultResourceLoader({
|
|
@@ -22,8 +22,12 @@
|
|
|
22
22
|
* stay under Telegram's per-chat rate limit. Periodic typing indicators are
|
|
23
23
|
* re-sent every 4 s while the job is running.
|
|
24
24
|
*/
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
25
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
29
|
exports.TelegramAdapter = void 0;
|
|
30
|
+
const telegramify_markdown_1 = __importDefault(require("telegramify-markdown"));
|
|
27
31
|
const telegram_http_client_js_1 = require("./telegram-http-client.js");
|
|
28
32
|
const agent_db_js_1 = require("../agent-db.js");
|
|
29
33
|
const i18n_telegram_js_1 = require("../lib/i18n-telegram.js");
|
|
@@ -181,6 +185,17 @@ class TelegramAdapter {
|
|
|
181
185
|
return;
|
|
182
186
|
state.responseBuffer += text;
|
|
183
187
|
}
|
|
188
|
+
/** Called from process.ts when the agent emits a takeover_requested chunk. */
|
|
189
|
+
notifyTakeover(channelId, takeoverUrl) {
|
|
190
|
+
const state = this.chats.get(channelId);
|
|
191
|
+
if (!state)
|
|
192
|
+
return;
|
|
193
|
+
this.bot
|
|
194
|
+
.sendMessage(state.chatId, `Takeover link: ${takeoverUrl}`)
|
|
195
|
+
.catch((err) => {
|
|
196
|
+
console.warn(`[agent:${this.agentId}] Telegram takeover notify failed:`, err.message);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
184
199
|
// āā Finalize + flush āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
185
200
|
/**
|
|
186
201
|
* Edit the live status message one last time with a "Done" footer, then
|
|
@@ -235,15 +250,30 @@ class TelegramAdapter {
|
|
|
235
250
|
}
|
|
236
251
|
async sendReply(chatId, text) {
|
|
237
252
|
const MAX = 4000;
|
|
238
|
-
|
|
239
|
-
|
|
253
|
+
// Convert standard markdown ā Telegram MarkdownV2. Fall back to plain
|
|
254
|
+
// text if conversion throws (e.g. deeply malformed LLM output).
|
|
255
|
+
let body;
|
|
256
|
+
let parseMode;
|
|
257
|
+
try {
|
|
258
|
+
body = (0, telegramify_markdown_1.default)(text, 'escape');
|
|
259
|
+
parseMode = 'MarkdownV2';
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
body = text;
|
|
263
|
+
parseMode = undefined;
|
|
264
|
+
}
|
|
265
|
+
const send = (chunk) => this.bot
|
|
266
|
+
.sendMessage(chatId, chunk, parseMode ? { parse_mode: parseMode } : undefined)
|
|
267
|
+
.catch(() => this.bot.sendMessage(chatId, chunk, {})); // plain-text fallback
|
|
268
|
+
if (body.length <= MAX) {
|
|
269
|
+
await send(body);
|
|
240
270
|
return;
|
|
241
271
|
}
|
|
242
|
-
let remaining =
|
|
272
|
+
let remaining = body;
|
|
243
273
|
while (remaining.length > 0) {
|
|
244
|
-
const
|
|
245
|
-
const splitAt =
|
|
246
|
-
await
|
|
274
|
+
const slice = remaining.slice(0, MAX);
|
|
275
|
+
const splitAt = slice.lastIndexOf('\n') > MAX / 2 ? slice.lastIndexOf('\n') : MAX;
|
|
276
|
+
await send(remaining.slice(0, splitAt));
|
|
247
277
|
remaining = remaining.slice(splitAt).trimStart();
|
|
248
278
|
}
|
|
249
279
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extName": {
|
|
3
|
+
"message": "CapMonster Cloud ā automated captcha solver",
|
|
4
|
+
"description": "The title of the application, displayed in the web store."
|
|
5
|
+
},
|
|
6
|
+
"extDesc": {
|
|
7
|
+
"message": "Online service for automated captcha solving",
|
|
8
|
+
"description": "The description of the application, displayed in the web store."
|
|
9
|
+
},
|
|
10
|
+
"extNameFirefox": {
|
|
11
|
+
"message": "CapMonster Cloud ā automated captcha solver",
|
|
12
|
+
"description": "The title of the application, displayed in the web store."
|
|
13
|
+
},
|
|
14
|
+
"extDescFirefox": {
|
|
15
|
+
"message": "Online service for automated captcha solving",
|
|
16
|
+
"description": "The description of the application, displayed in the web store."
|
|
17
|
+
},
|
|
18
|
+
"extShortName": {
|
|
19
|
+
"message": "CapMonster Cloud",
|
|
20
|
+
"description": ""
|
|
21
|
+
},
|
|
22
|
+
"on": {
|
|
23
|
+
"message": "On",
|
|
24
|
+
"description": ""
|
|
25
|
+
},
|
|
26
|
+
"off": {
|
|
27
|
+
"message": "Off",
|
|
28
|
+
"description": ""
|
|
29
|
+
},
|
|
30
|
+
"support": {
|
|
31
|
+
"message": "Support",
|
|
32
|
+
"description": ""
|
|
33
|
+
},
|
|
34
|
+
"apiKeyPlaceholder": {
|
|
35
|
+
"message": "Enter API key",
|
|
36
|
+
"description": ""
|
|
37
|
+
},
|
|
38
|
+
"getKey": {
|
|
39
|
+
"message": "Get a key",
|
|
40
|
+
"description": ""
|
|
41
|
+
},
|
|
42
|
+
"balance": {
|
|
43
|
+
"message": "Balance",
|
|
44
|
+
"description": ""
|
|
45
|
+
},
|
|
46
|
+
"topUp": {
|
|
47
|
+
"message": "Add funds",
|
|
48
|
+
"description": ""
|
|
49
|
+
},
|
|
50
|
+
"wrongKey": {
|
|
51
|
+
"message": "Wrong key",
|
|
52
|
+
"description": ""
|
|
53
|
+
},
|
|
54
|
+
"emptyKey": {
|
|
55
|
+
"message": "Empty key",
|
|
56
|
+
"description": ""
|
|
57
|
+
},
|
|
58
|
+
"automaticCaptchaSolving": {
|
|
59
|
+
"message": "Automated captcha solving",
|
|
60
|
+
"description": ""
|
|
61
|
+
},
|
|
62
|
+
"settings": {
|
|
63
|
+
"message": "Settings",
|
|
64
|
+
"description": ""
|
|
65
|
+
},
|
|
66
|
+
"repeatSolveAttempts": {
|
|
67
|
+
"message": "Repeat captcha solving in case of an error, attempts",
|
|
68
|
+
"description": ""
|
|
69
|
+
},
|
|
70
|
+
"proxy": {
|
|
71
|
+
"message": "Proxy",
|
|
72
|
+
"description": ""
|
|
73
|
+
},
|
|
74
|
+
"proxyType": {
|
|
75
|
+
"message": "Proxy type",
|
|
76
|
+
"description": ""
|
|
77
|
+
},
|
|
78
|
+
"port": {
|
|
79
|
+
"message": "Port",
|
|
80
|
+
"description": ""
|
|
81
|
+
},
|
|
82
|
+
"login": {
|
|
83
|
+
"message": "Login",
|
|
84
|
+
"description": ""
|
|
85
|
+
},
|
|
86
|
+
"password": {
|
|
87
|
+
"message": "Password",
|
|
88
|
+
"description": ""
|
|
89
|
+
},
|
|
90
|
+
"blacklistControl": {
|
|
91
|
+
"message": "Blacklist control",
|
|
92
|
+
"description": ""
|
|
93
|
+
},
|
|
94
|
+
"blacklistDescription": {
|
|
95
|
+
"message": "Captcha solving on added websites will be disabled",
|
|
96
|
+
"description": ""
|
|
97
|
+
},
|
|
98
|
+
"add": {
|
|
99
|
+
"message": "Add",
|
|
100
|
+
"description": ""
|
|
101
|
+
},
|
|
102
|
+
"manualRecognition": {
|
|
103
|
+
"message": "Manual recognition",
|
|
104
|
+
"description": ""
|
|
105
|
+
},
|
|
106
|
+
"textCaptcha": {
|
|
107
|
+
"message": "Text captcha",
|
|
108
|
+
"description": ""
|
|
109
|
+
},
|
|
110
|
+
"textCaptchaHelpLink": {
|
|
111
|
+
"message": "https://docs.capmonster.cloud/docs/captchas/image-to-text",
|
|
112
|
+
"description": ""
|
|
113
|
+
},
|
|
114
|
+
"markImageAsCaptcha": {
|
|
115
|
+
"message": "Mark image as captcha",
|
|
116
|
+
"description": ""
|
|
117
|
+
},
|
|
118
|
+
"selectForSubmit": {
|
|
119
|
+
"message": "Select element for submit",
|
|
120
|
+
"description": ""
|
|
121
|
+
},
|
|
122
|
+
"selectInputForCaptchaResult": {
|
|
123
|
+
"message": "Select an input for the captcha result",
|
|
124
|
+
"description": ""
|
|
125
|
+
},
|
|
126
|
+
"help": {
|
|
127
|
+
"message": "Help",
|
|
128
|
+
"description": ""
|
|
129
|
+
},
|
|
130
|
+
"rcInfoText": {
|
|
131
|
+
"message": "Only click",
|
|
132
|
+
"description": ""
|
|
133
|
+
},
|
|
134
|
+
"close_button": {
|
|
135
|
+
"message": "Close",
|
|
136
|
+
"description": ""
|
|
137
|
+
},
|
|
138
|
+
"delayStartCount": {
|
|
139
|
+
"message": "Delay between start solve captcha clicks",
|
|
140
|
+
"description": ""
|
|
141
|
+
},
|
|
142
|
+
"textCaptchaSaveOnSite": {
|
|
143
|
+
"message": "Save selected captcha position for sites",
|
|
144
|
+
"description": ""
|
|
145
|
+
},
|
|
146
|
+
"delayAfterLoadPage": {
|
|
147
|
+
"message": "Delay before start solve saved captcha",
|
|
148
|
+
"description": ""
|
|
149
|
+
},
|
|
150
|
+
"autoClick": {
|
|
151
|
+
"message": "Auto click",
|
|
152
|
+
"description": ""
|
|
153
|
+
},
|
|
154
|
+
"autoSolve": {
|
|
155
|
+
"message": "Auto solve",
|
|
156
|
+
"description": ""
|
|
157
|
+
},
|
|
158
|
+
"globalVariableSetting": {
|
|
159
|
+
"message": "Global variable",
|
|
160
|
+
"description": ""
|
|
161
|
+
},
|
|
162
|
+
"manualRecognitionDescription": {
|
|
163
|
+
"message": "This feature allows starting captcha solving using the \"Token\" method not automatically but by clicking the \"Solve\" button.",
|
|
164
|
+
"description": ""
|
|
165
|
+
},
|
|
166
|
+
"textCaptchaModule": {
|
|
167
|
+
"message": "Text module",
|
|
168
|
+
"description": ""
|
|
169
|
+
},
|
|
170
|
+
"enterModuleName": {
|
|
171
|
+
"message": "Enter text module",
|
|
172
|
+
"description": ""
|
|
173
|
+
},
|
|
174
|
+
"addModuleText": {
|
|
175
|
+
"message": "Add module",
|
|
176
|
+
"description": ""
|
|
177
|
+
},
|
|
178
|
+
"autoModule": {
|
|
179
|
+
"message": "Auto",
|
|
180
|
+
"description": ""
|
|
181
|
+
},
|
|
182
|
+
"delayBetweenClickValue": {
|
|
183
|
+
"message": "Delay between click (ms)",
|
|
184
|
+
"description": ""
|
|
185
|
+
},
|
|
186
|
+
"delayBetweenClickEnabled": {
|
|
187
|
+
"message": "Delay between click",
|
|
188
|
+
"description": ""
|
|
189
|
+
},
|
|
190
|
+
"apiKeyTitle": {
|
|
191
|
+
"message": "API-key",
|
|
192
|
+
"description": ""
|
|
193
|
+
},
|
|
194
|
+
"globalObjectDescription": {
|
|
195
|
+
"message": "Name of the field for interaction with the extension via a global object. More information about the extension settings can be found",
|
|
196
|
+
"description": ""
|
|
197
|
+
},
|
|
198
|
+
"globalObjectDescriptionLink": {
|
|
199
|
+
"message": "in the article",
|
|
200
|
+
"description": ""
|
|
201
|
+
},
|
|
202
|
+
"autoSubmitText": {
|
|
203
|
+
"message": "Element for auto submit",
|
|
204
|
+
"description": ""
|
|
205
|
+
},
|
|
206
|
+
"removeAutoSubmit": {
|
|
207
|
+
"message": "Remove",
|
|
208
|
+
"description": ""
|
|
209
|
+
},
|
|
210
|
+
"emptyDetectedCaptchaMessage": {
|
|
211
|
+
"message": "CAPTCHA may require activation - simply refreshing the page might not be enough. Please complete the CAPTCHA manually to load all necessary parameters.",
|
|
212
|
+
"description": ""
|
|
213
|
+
},
|
|
214
|
+
"detectedCaptchas": {
|
|
215
|
+
"message": "Captchas detected",
|
|
216
|
+
"description": ""
|
|
217
|
+
},
|
|
218
|
+
"copySuccessMessage": {
|
|
219
|
+
"message": "Copied to clipboard!",
|
|
220
|
+
"description": ""
|
|
221
|
+
},
|
|
222
|
+
"copyFailedMessage": {
|
|
223
|
+
"message": "Failed to copy",
|
|
224
|
+
"description": ""
|
|
225
|
+
}
|
|
226
|
+
}
|