smooth-operator-mcp 3.0.2 → 3.0.5
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/smooth-operator.mjs +186 -33
- package/dist/smooth-operator.mjs.map +2 -2
- package/docs/harnesses.md +7 -6
- package/docs/mcp-server.md +3 -2
- package/package.json +1 -1
package/dist/smooth-operator.mjs
CHANGED
|
@@ -354,7 +354,7 @@ function truncateUtf8(value, maxBytes) {
|
|
|
354
354
|
while (low < high) {
|
|
355
355
|
const midpoint = Math.ceil((low + high) / 2);
|
|
356
356
|
const candidate = decoder.decode(bytes.slice(0, midpoint));
|
|
357
|
-
if (UTF8_ENCODER.encode(candidate).byteLength <=
|
|
357
|
+
if (UTF8_ENCODER.encode(candidate).byteLength <= boundedMaxBytes) {
|
|
358
358
|
low = midpoint;
|
|
359
359
|
} else {
|
|
360
360
|
high = midpoint - 1;
|
|
@@ -397,7 +397,7 @@ var SERVER_VERSION;
|
|
|
397
397
|
var init_version = __esm({
|
|
398
398
|
"src/server/version.ts"() {
|
|
399
399
|
"use strict";
|
|
400
|
-
SERVER_VERSION = "3.0.
|
|
400
|
+
SERVER_VERSION = "3.0.5";
|
|
401
401
|
}
|
|
402
402
|
});
|
|
403
403
|
|
|
@@ -639,9 +639,9 @@ function resolveConfigPath(target, homeDirectory = homedir3(), environment = pro
|
|
|
639
639
|
return join6(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
640
640
|
}
|
|
641
641
|
if (platform2() === "win32") {
|
|
642
|
-
return join6(environment.APPDATA
|
|
642
|
+
return join6(resolveConfigDirectory(environment.APPDATA, join6(home, "AppData", "Roaming")), "Claude", "claude_desktop_config.json");
|
|
643
643
|
}
|
|
644
|
-
return join6(environment.XDG_CONFIG_HOME
|
|
644
|
+
return join6(resolveConfigDirectory(environment.XDG_CONFIG_HOME, join6(home, ".config")), "Claude", "claude_desktop_config.json");
|
|
645
645
|
}
|
|
646
646
|
function resolveOpenCodeConfigPath(homeDirectory = homedir3(), environment = process.env, override) {
|
|
647
647
|
if (override) {
|
|
@@ -650,9 +650,17 @@ function resolveOpenCodeConfigPath(homeDirectory = homedir3(), environment = pro
|
|
|
650
650
|
if (environment.OPENCODE_CONFIG) {
|
|
651
651
|
return resolve5(environment.OPENCODE_CONFIG);
|
|
652
652
|
}
|
|
653
|
-
const configDirectory = environment.OPENCODE_CONFIG_DIR
|
|
653
|
+
const configDirectory = resolveConfigDirectory(environment.OPENCODE_CONFIG_DIR, join6(homeDirectory || homedir3(), ".config", "opencode"));
|
|
654
654
|
return join6(configDirectory, "opencode.json");
|
|
655
655
|
}
|
|
656
|
+
function resolveConfigDirectory(value, fallback) {
|
|
657
|
+
const candidate = value?.trim() || fallback;
|
|
658
|
+
try {
|
|
659
|
+
return resolve5(candidate);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
throw new AppError("INSTALL_CONFIG_INVALID", "Configuration directory paths must be valid filesystem paths.", { cause: error });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
656
664
|
async function installJsonConfig(target, plannedPath, options, allowOpenCodeJsoncFallback = false) {
|
|
657
665
|
const path = target === "opencode" && allowOpenCodeJsoncFallback ? await chooseExistingOpenCodePath(plannedPath) : plannedPath;
|
|
658
666
|
await ensureSecureDirectory(dirname4(path));
|
|
@@ -898,6 +906,9 @@ function sameOpenCodeEntry(value, desired) {
|
|
|
898
906
|
}
|
|
899
907
|
async function ensureSecureDirectory(path) {
|
|
900
908
|
const absolute = resolve5(path);
|
|
909
|
+
if (parse3(absolute).root === absolute) {
|
|
910
|
+
throw new AppError("INSTALL_CONFIG_FAILED", "Configuration directories must not be filesystem roots.");
|
|
911
|
+
}
|
|
901
912
|
await assertNoSymlinkComponents2(absolute, "configuration directory");
|
|
902
913
|
await mkdir3(absolute, { recursive: true, mode: 448 });
|
|
903
914
|
await assertNoSymlinkComponents2(absolute, "configuration directory");
|
|
@@ -1227,7 +1238,7 @@ __export(installer_wizard_exports, {
|
|
|
1227
1238
|
runWizard: () => runWizard
|
|
1228
1239
|
});
|
|
1229
1240
|
import { dirname as dirname5, isAbsolute as isAbsolute4, join as join7, parse as parse4, resolve as resolve6, win32 as win323 } from "node:path";
|
|
1230
|
-
import {
|
|
1241
|
+
import { accessSync as accessSync2, constants as constants3, statSync } from "node:fs";
|
|
1231
1242
|
import { chmod as chmod3, lstat as lstat4, rename as rename4, unlink as unlink4, writeFile as writeFile2 } from "node:fs/promises";
|
|
1232
1243
|
import { homedir as homedir4 } from "node:os";
|
|
1233
1244
|
import { isIP as isIP3 } from "node:net";
|
|
@@ -1244,6 +1255,19 @@ function isAbsolutePath(value) {
|
|
|
1244
1255
|
function isFilesystemRoot(value) {
|
|
1245
1256
|
return isAbsolute4(value) && parse4(value).root === value || win323.isAbsolute(value) && win323.parse(value).root === value;
|
|
1246
1257
|
}
|
|
1258
|
+
function isExecutableFile(path) {
|
|
1259
|
+
try {
|
|
1260
|
+
if (!statSync(path).isFile()) {
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
if (process.platform !== "win32") {
|
|
1264
|
+
accessSync2(path, constants3.X_OK);
|
|
1265
|
+
}
|
|
1266
|
+
return true;
|
|
1267
|
+
} catch {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1247
1271
|
function isInteractive() {
|
|
1248
1272
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
1249
1273
|
}
|
|
@@ -1313,8 +1337,8 @@ async function askBrowser(session, ui) {
|
|
|
1313
1337
|
return detected[numeric - 1].path;
|
|
1314
1338
|
}
|
|
1315
1339
|
if (/^\d+$/.test(answer)) continue;
|
|
1316
|
-
if (isAbsolutePath(answer) &&
|
|
1317
|
-
ui.failure("Enter a listed number or an existing
|
|
1340
|
+
if (isAbsolutePath(answer) && isExecutableFile(answer)) return answer;
|
|
1341
|
+
ui.failure("Enter a listed number or an existing executable file path.");
|
|
1318
1342
|
}
|
|
1319
1343
|
}
|
|
1320
1344
|
while (true) {
|
|
@@ -1324,8 +1348,8 @@ async function askBrowser(session, ui) {
|
|
|
1324
1348
|
ui.failure("Enter an absolute path.");
|
|
1325
1349
|
continue;
|
|
1326
1350
|
}
|
|
1327
|
-
if (!
|
|
1328
|
-
ui.failure("That path
|
|
1351
|
+
if (!isExecutableFile(answer)) {
|
|
1352
|
+
ui.failure("That path is not an executable file.");
|
|
1329
1353
|
continue;
|
|
1330
1354
|
}
|
|
1331
1355
|
return answer;
|
|
@@ -1560,16 +1584,65 @@ function writeSummary(ui, harness, choices) {
|
|
|
1560
1584
|
]);
|
|
1561
1585
|
}
|
|
1562
1586
|
async function defaultProbe(url, timeoutMs) {
|
|
1587
|
+
const controller = new AbortController();
|
|
1588
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1563
1589
|
try {
|
|
1564
|
-
const controller = new AbortController();
|
|
1565
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1566
1590
|
const response = await fetch(url, { signal: controller.signal });
|
|
1567
|
-
clearTimeout(timer);
|
|
1568
1591
|
if (!response.ok) return { state: "no-file" };
|
|
1569
|
-
const version = await response.
|
|
1570
|
-
return { state: "live", version };
|
|
1592
|
+
const version = await readProbeJson(response, controller.signal);
|
|
1593
|
+
return isDevToolsVersion(version) ? { state: "live", version } : { state: "no-file" };
|
|
1571
1594
|
} catch {
|
|
1572
1595
|
return { state: "no-file" };
|
|
1596
|
+
} finally {
|
|
1597
|
+
clearTimeout(timer);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
function isDevToolsVersion(value) {
|
|
1601
|
+
return isRecord4(value) && typeof value.Browser === "string" && typeof value.webSocketDebuggerUrl === "string" && /^wss?:\/\//i.test(value.webSocketDebuggerUrl);
|
|
1602
|
+
}
|
|
1603
|
+
async function readProbeJson(response, signal) {
|
|
1604
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
1605
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_PROBE_RESPONSE_BYTES) {
|
|
1606
|
+
return void 0;
|
|
1607
|
+
}
|
|
1608
|
+
if (!response.body) {
|
|
1609
|
+
return void 0;
|
|
1610
|
+
}
|
|
1611
|
+
const reader = response.body.getReader();
|
|
1612
|
+
const chunks = [];
|
|
1613
|
+
let total = 0;
|
|
1614
|
+
try {
|
|
1615
|
+
while (true) {
|
|
1616
|
+
if (signal.aborted) {
|
|
1617
|
+
return void 0;
|
|
1618
|
+
}
|
|
1619
|
+
const next = await reader.read();
|
|
1620
|
+
if (next.done) {
|
|
1621
|
+
break;
|
|
1622
|
+
}
|
|
1623
|
+
if (!(next.value instanceof Uint8Array)) {
|
|
1624
|
+
return void 0;
|
|
1625
|
+
}
|
|
1626
|
+
total += next.value.byteLength;
|
|
1627
|
+
if (total > MAX_PROBE_RESPONSE_BYTES) {
|
|
1628
|
+
return void 0;
|
|
1629
|
+
}
|
|
1630
|
+
chunks.push(next.value);
|
|
1631
|
+
}
|
|
1632
|
+
} finally {
|
|
1633
|
+
await reader.cancel().catch(() => void 0);
|
|
1634
|
+
reader.releaseLock();
|
|
1635
|
+
}
|
|
1636
|
+
const bytes = new Uint8Array(total);
|
|
1637
|
+
let offset = 0;
|
|
1638
|
+
for (const chunk of chunks) {
|
|
1639
|
+
bytes.set(chunk, offset);
|
|
1640
|
+
offset += chunk.byteLength;
|
|
1641
|
+
}
|
|
1642
|
+
try {
|
|
1643
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
1644
|
+
} catch {
|
|
1645
|
+
return void 0;
|
|
1573
1646
|
}
|
|
1574
1647
|
}
|
|
1575
1648
|
async function assertPrivateWizardConfig(handle) {
|
|
@@ -1671,16 +1744,21 @@ async function launchPersonalChrome(opts) {
|
|
|
1671
1744
|
if (!safeDataDir || !isAbsolutePath(rawDataDir) || isFilesystemRoot(safeDataDir) || /[\u0000-\u001f\u007f]/.test(rawDataDir)) {
|
|
1672
1745
|
throw new AppError("INSTALL_CONFIG_INVALID", "The personal Chrome data directory must be an absolute non-root path without control characters.");
|
|
1673
1746
|
}
|
|
1747
|
+
if (opts.probeAttempts !== void 0 && (!Number.isSafeInteger(opts.probeAttempts) || opts.probeAttempts < 1 || opts.probeAttempts > MAX_PROBE_ATTEMPTS)) {
|
|
1748
|
+
throw new AppError("INSTALL_CONFIG_INVALID", `The personal Chrome probe attempt count must be an integer between 1 and ${MAX_PROBE_ATTEMPTS}.`);
|
|
1749
|
+
}
|
|
1750
|
+
await ensureSecureDirectory(safeDataDir);
|
|
1751
|
+
const personalProfileDir = join7(safeDataDir, "personal-chrome");
|
|
1752
|
+
await ensureSecureDirectory(personalProfileDir);
|
|
1674
1753
|
const { findChromeExecutable: findChromeExecutable2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
1675
1754
|
const executable = opts.executablePath ?? findChromeExecutable2()?.path;
|
|
1676
|
-
if (!executable) {
|
|
1755
|
+
if (!executable || !isExecutableFile(executable)) {
|
|
1677
1756
|
throw new AppError("BROWSER_NOT_CONFIGURED", "Install Chrome or set SMOOTH_OPERATOR_BROWSER_EXECUTABLE");
|
|
1678
1757
|
}
|
|
1679
|
-
await ensureSecureDirectory(safeDataDir);
|
|
1680
1758
|
const spawnFn = opts.spawn ?? (await import("node:child_process")).spawn;
|
|
1681
1759
|
const args = [
|
|
1682
1760
|
`--remote-debugging-port=${port}`,
|
|
1683
|
-
`--user-data-dir=${
|
|
1761
|
+
`--user-data-dir=${personalProfileDir}`,
|
|
1684
1762
|
"--no-first-run",
|
|
1685
1763
|
"--no-default-browser-check",
|
|
1686
1764
|
...opts.headless ? ["--headless=new"] : []
|
|
@@ -1689,19 +1767,44 @@ async function launchPersonalChrome(opts) {
|
|
|
1689
1767
|
child.unref();
|
|
1690
1768
|
const probe = opts.probe;
|
|
1691
1769
|
const attempts = opts.probeAttempts ?? DEFAULT_PROBE_ATTEMPTS;
|
|
1770
|
+
const deadline = opts.probeAttempts === void 0 ? Date.now() + DEFAULT_PROBE_DEADLINE_MS : void 0;
|
|
1771
|
+
let attemptsMade = 0;
|
|
1692
1772
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1773
|
+
if (deadline !== void 0 && Date.now() >= deadline) {
|
|
1774
|
+
break;
|
|
1775
|
+
}
|
|
1693
1776
|
if (attempt > 0) {
|
|
1694
|
-
|
|
1777
|
+
const remaining2 = deadline === void 0 ? PROBE_INTERVAL_MS : deadline - Date.now();
|
|
1778
|
+
if (remaining2 <= 0) break;
|
|
1779
|
+
await new Promise((resolveTimeout) => setTimeout(resolveTimeout, Math.min(PROBE_INTERVAL_MS, remaining2)));
|
|
1695
1780
|
}
|
|
1781
|
+
const remaining = deadline === void 0 ? PROBE_TIMEOUT_MS : Math.min(PROBE_TIMEOUT_MS, deadline - Date.now());
|
|
1782
|
+
if (remaining <= 0) break;
|
|
1783
|
+
attemptsMade += 1;
|
|
1696
1784
|
try {
|
|
1697
|
-
const res = await probe
|
|
1785
|
+
const res = await boundedProbe(probe, `http://127.0.0.1:${port}/json/version`, remaining);
|
|
1698
1786
|
if (res.state === "live") return { url: `http://127.0.0.1:${port}` };
|
|
1699
1787
|
} catch {
|
|
1700
1788
|
}
|
|
1701
1789
|
}
|
|
1702
|
-
throw new AppError("BROWSER_CONNECT_TIMEOUT", `Chrome DevTools endpoint on port ${port} did not become ready after ${
|
|
1790
|
+
throw new AppError("BROWSER_CONNECT_TIMEOUT", `Chrome DevTools endpoint on port ${port} did not become ready after ${attemptsMade} probes. Close Chrome or choose another port.`);
|
|
1791
|
+
}
|
|
1792
|
+
async function boundedProbe(probe, url, timeoutMs) {
|
|
1793
|
+
let timer;
|
|
1794
|
+
try {
|
|
1795
|
+
return await Promise.race([
|
|
1796
|
+
Promise.resolve().then(() => probe(url, timeoutMs)),
|
|
1797
|
+
new Promise((resolveProbe) => {
|
|
1798
|
+
timer = setTimeout(() => resolveProbe({ state: "timeout" }), timeoutMs);
|
|
1799
|
+
})
|
|
1800
|
+
]);
|
|
1801
|
+
} finally {
|
|
1802
|
+
if (timer) {
|
|
1803
|
+
clearTimeout(timer);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1703
1806
|
}
|
|
1704
|
-
var PROBE_INTERVAL_MS, DEFAULT_PROBE_ATTEMPTS, MAX_WIZARD_CONFIG_BYTES, HARNESS_MENU, WIZARD_STEP_TOTAL;
|
|
1807
|
+
var PROBE_INTERVAL_MS, PROBE_TIMEOUT_MS, DEFAULT_PROBE_ATTEMPTS, DEFAULT_PROBE_DEADLINE_MS, MAX_PROBE_ATTEMPTS, MAX_PROBE_RESPONSE_BYTES, MAX_WIZARD_CONFIG_BYTES, HARNESS_MENU, WIZARD_STEP_TOTAL;
|
|
1705
1808
|
var init_installer_wizard = __esm({
|
|
1706
1809
|
"src/server/installer-wizard.ts"() {
|
|
1707
1810
|
"use strict";
|
|
@@ -1710,7 +1813,11 @@ var init_installer_wizard = __esm({
|
|
|
1710
1813
|
init_ui();
|
|
1711
1814
|
init_version();
|
|
1712
1815
|
PROBE_INTERVAL_MS = 300;
|
|
1816
|
+
PROBE_TIMEOUT_MS = 1e3;
|
|
1713
1817
|
DEFAULT_PROBE_ATTEMPTS = 33;
|
|
1818
|
+
DEFAULT_PROBE_DEADLINE_MS = 1e4;
|
|
1819
|
+
MAX_PROBE_ATTEMPTS = 100;
|
|
1820
|
+
MAX_PROBE_RESPONSE_BYTES = 64 * 1024;
|
|
1714
1821
|
MAX_WIZARD_CONFIG_BYTES = 2e6;
|
|
1715
1822
|
HARNESS_MENU = [
|
|
1716
1823
|
{ id: "opencode", label: "OpenCode", description: "Configures ~/.config/opencode/opencode.json" },
|
|
@@ -1907,7 +2014,7 @@ function isValidDomainPattern(pattern) {
|
|
|
1907
2014
|
}
|
|
1908
2015
|
const bracketless = base.replace(/^\[|\]$/g, "");
|
|
1909
2016
|
if (isIP(bracketless) !== 0) {
|
|
1910
|
-
return
|
|
2017
|
+
return !wildcard;
|
|
1911
2018
|
}
|
|
1912
2019
|
const ascii = domainToASCII(base);
|
|
1913
2020
|
return Boolean(ascii) && ascii.split(".").every((label) => label.length > 0 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label));
|
|
@@ -1999,6 +2106,18 @@ function canonicalizeAllowedFileRoots(rawRoots) {
|
|
|
1999
2106
|
if (parse(canonicalRoot).root === canonicalRoot) {
|
|
2000
2107
|
throw new AppError("CONFIG_INVALID", "Configured file roots must not be filesystem roots.");
|
|
2001
2108
|
}
|
|
2109
|
+
try {
|
|
2110
|
+
if (!lstatSync(canonicalRoot).isDirectory()) {
|
|
2111
|
+
throw new AppError("CONFIG_INVALID", "Configured file roots must be directories.");
|
|
2112
|
+
}
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
if (error instanceof AppError) {
|
|
2115
|
+
throw error;
|
|
2116
|
+
}
|
|
2117
|
+
if (!isMissingPathError(error)) {
|
|
2118
|
+
throw new AppError("CONFIG_INSECURE", "Configured file roots could not be inspected safely.", { cause: error });
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2002
2121
|
if (!roots.includes(canonicalRoot)) {
|
|
2003
2122
|
roots.push(canonicalRoot);
|
|
2004
2123
|
}
|
|
@@ -2312,11 +2431,21 @@ function normalizeHostPattern(value) {
|
|
|
2312
2431
|
throw new AppError("CONFIG_INVALID", "Configuration failed validation: configured HTTP host allowlists must contain hostnames or bracketed IPv6 addresses without ports.");
|
|
2313
2432
|
}
|
|
2314
2433
|
try {
|
|
2315
|
-
|
|
2434
|
+
const hostname = new URL(`http://${trimmed}`).hostname.toLowerCase();
|
|
2435
|
+
return hostname.endsWith(".") ? hostname.slice(0, -1) : hostname;
|
|
2316
2436
|
} catch (error) {
|
|
2317
2437
|
throw new AppError("CONFIG_INVALID", "Configuration failed validation: configured HTTP host allowlists contain an invalid hostname.", { cause: error });
|
|
2318
2438
|
}
|
|
2319
2439
|
}
|
|
2440
|
+
function normalizeListenHost(value) {
|
|
2441
|
+
const trimmed = value.trim();
|
|
2442
|
+
try {
|
|
2443
|
+
const hostname = new URL(`http://${trimmed}`).hostname.toLowerCase();
|
|
2444
|
+
return hostname.endsWith(".") ? hostname.slice(0, -1) : hostname;
|
|
2445
|
+
} catch {
|
|
2446
|
+
return trimmed;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2320
2449
|
function normalizeHostList(values) {
|
|
2321
2450
|
return normalizeList(values).map(normalizeHostPattern);
|
|
2322
2451
|
}
|
|
@@ -2329,7 +2458,7 @@ function isValidDomainPattern2(value) {
|
|
|
2329
2458
|
}
|
|
2330
2459
|
const bracketless = base.replace(/^\[|\]$/g, "");
|
|
2331
2460
|
if (isIP2(bracketless) !== 0) {
|
|
2332
|
-
return
|
|
2461
|
+
return !wildcard;
|
|
2333
2462
|
}
|
|
2334
2463
|
let ascii;
|
|
2335
2464
|
try {
|
|
@@ -2575,7 +2704,7 @@ function loadServerConfig(args = [], environment = env, homeDirectory = homedir(
|
|
|
2575
2704
|
const config = {
|
|
2576
2705
|
transport: argValue("--transport") ?? environment.SMOOTH_OPERATOR_TRANSPORT ?? fileConfig.transport ?? "stdio",
|
|
2577
2706
|
http: {
|
|
2578
|
-
host: (argValue("--host") ?? environment.SMOOTH_OPERATOR_HTTP_HOST ?? nestedHttp.host ?? "127.0.0.1")
|
|
2707
|
+
host: normalizeListenHost(argValue("--host") ?? environment.SMOOTH_OPERATOR_HTTP_HOST ?? nestedHttp.host ?? "127.0.0.1"),
|
|
2579
2708
|
port: parseInteger(argValue("--port") ?? environment.SMOOTH_OPERATOR_HTTP_PORT, nestedHttp.port ?? 3344),
|
|
2580
2709
|
path: (environment.SMOOTH_OPERATOR_HTTP_PATH ?? nestedHttp.path ?? "/mcp").trim(),
|
|
2581
2710
|
token: environment.SMOOTH_OPERATOR_HTTP_TOKEN ?? nestedHttp.token,
|
|
@@ -4554,11 +4683,31 @@ var DEFAULT_TYPE = {
|
|
|
4554
4683
|
rng: Math.random
|
|
4555
4684
|
};
|
|
4556
4685
|
function randomRange(min, max, rand = Math.random) {
|
|
4557
|
-
|
|
4686
|
+
const sample = rand();
|
|
4687
|
+
const boundedSample = Number.isFinite(sample) ? Math.min(1, Math.max(0, sample)) : 0;
|
|
4688
|
+
return min + boundedSample * (max - min);
|
|
4558
4689
|
}
|
|
4559
|
-
function sleep(ms) {
|
|
4560
|
-
|
|
4561
|
-
|
|
4690
|
+
function sleep(ms, signal) {
|
|
4691
|
+
if (signal?.aborted) {
|
|
4692
|
+
return Promise.reject(new Error("Operation aborted"));
|
|
4693
|
+
}
|
|
4694
|
+
return new Promise((resolve7, reject) => {
|
|
4695
|
+
let settled = false;
|
|
4696
|
+
const timer = setTimeout(() => finish(resolve7), Math.max(0, ms));
|
|
4697
|
+
const onAbort = () => finish(() => reject(new Error("Operation aborted")));
|
|
4698
|
+
const finish = (callback) => {
|
|
4699
|
+
if (settled) {
|
|
4700
|
+
return;
|
|
4701
|
+
}
|
|
4702
|
+
settled = true;
|
|
4703
|
+
clearTimeout(timer);
|
|
4704
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4705
|
+
callback();
|
|
4706
|
+
};
|
|
4707
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4708
|
+
if (signal?.aborted) {
|
|
4709
|
+
onAbort();
|
|
4710
|
+
}
|
|
4562
4711
|
});
|
|
4563
4712
|
}
|
|
4564
4713
|
async function humanMouseMove(page, x1, y1, x2, y2, durationMs = 80, options = {}) {
|
|
@@ -4577,15 +4726,18 @@ async function humanType(page, text, options = {}) {
|
|
|
4577
4726
|
const cfg = { ...DEFAULT_TYPE, ...options, rng };
|
|
4578
4727
|
const keyboard = page.keyboard;
|
|
4579
4728
|
for (const char of text) {
|
|
4729
|
+
if (cfg.signal?.aborted) {
|
|
4730
|
+
throw new Error("Operation aborted");
|
|
4731
|
+
}
|
|
4580
4732
|
if (char === " ") {
|
|
4581
4733
|
await keyboard.down("Space");
|
|
4582
4734
|
await keyboard.up("Space");
|
|
4583
4735
|
} else {
|
|
4584
4736
|
await keyboard.type(char);
|
|
4585
4737
|
}
|
|
4586
|
-
await sleep(randomRange(cfg.minDelayMs, cfg.maxDelayMs, cfg.rng));
|
|
4738
|
+
await sleep(randomRange(cfg.minDelayMs, cfg.maxDelayMs, cfg.rng), cfg.signal);
|
|
4587
4739
|
if (cfg.rng() < cfg.thinkPauseChance) {
|
|
4588
|
-
await sleep(randomRange(cfg.thinkPauseMinMs, cfg.thinkPauseMaxMs, cfg.rng));
|
|
4740
|
+
await sleep(randomRange(cfg.thinkPauseMinMs, cfg.thinkPauseMaxMs, cfg.rng), cfg.signal);
|
|
4589
4741
|
}
|
|
4590
4742
|
}
|
|
4591
4743
|
}
|
|
@@ -9307,6 +9459,7 @@ var BrowserService = class {
|
|
|
9307
9459
|
try {
|
|
9308
9460
|
await humanMouseMove(state.page, 0, 0, centerX, centerY, 80);
|
|
9309
9461
|
} catch {
|
|
9462
|
+
throwIfAborted(signal);
|
|
9310
9463
|
}
|
|
9311
9464
|
}
|
|
9312
9465
|
async clickTarget(state, target, button, clickCount, signal, frame = state.page.mainFrame(), pointerType = "mouse") {
|
|
@@ -9820,7 +9973,7 @@ var BrowserService = class {
|
|
|
9820
9973
|
if (!nativeControlValueSet) {
|
|
9821
9974
|
throwIfAborted(signal);
|
|
9822
9975
|
if (this.stealthSettings().behaviorEnabled) {
|
|
9823
|
-
await humanType(state.page, text);
|
|
9976
|
+
await humanType(state.page, text, { signal });
|
|
9824
9977
|
} else {
|
|
9825
9978
|
await state.page.keyboard.type(text);
|
|
9826
9979
|
}
|