opensteer 0.6.0 → 0.6.2
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 +16 -1
- package/README.md +63 -0
- package/bin/opensteer.mjs +214 -3
- package/dist/browser-profile-client-CaL-mwqs.d.ts +168 -0
- package/dist/browser-profile-client-DK9qa_Dj.d.cts +168 -0
- package/dist/chunk-7RMY26CM.js +1130 -0
- package/dist/{chunk-SGZYTGY3.js → chunk-F2VDVOJO.js} +1890 -1520
- package/dist/chunk-WDRMHPWL.js +1320 -0
- package/dist/chunk-WJI7TGBQ.js +77 -0
- package/dist/cli/auth.cjs +1834 -0
- package/dist/cli/auth.d.cts +114 -0
- package/dist/cli/auth.d.ts +114 -0
- package/dist/cli/auth.js +15 -0
- package/dist/cli/profile.cjs +16222 -0
- package/dist/cli/profile.d.cts +79 -0
- package/dist/cli/profile.d.ts +79 -0
- package/dist/cli/profile.js +866 -0
- package/dist/cli/server.cjs +1816 -422
- package/dist/cli/server.js +310 -10
- package/dist/index.cjs +1772 -414
- package/dist/index.d.cts +141 -395
- package/dist/index.d.ts +141 -395
- package/dist/index.js +203 -8
- package/dist/types-BxiRblC7.d.cts +327 -0
- package/dist/types-BxiRblC7.d.ts +327 -0
- package/package.json +3 -2
- package/skills/opensteer/SKILL.md +34 -5
- package/skills/opensteer/references/cli-reference.md +10 -8
- package/skills/opensteer/references/sdk-reference.md +14 -3
package/dist/cli/server.cjs
CHANGED
|
@@ -424,7 +424,7 @@ var import_fs4 = require("fs");
|
|
|
424
424
|
var import_crypto = require("crypto");
|
|
425
425
|
|
|
426
426
|
// src/browser/pool.ts
|
|
427
|
-
var
|
|
427
|
+
var import_playwright2 = require("playwright");
|
|
428
428
|
|
|
429
429
|
// src/browser/cdp-proxy.ts
|
|
430
430
|
var import_ws = __toESM(require("ws"), 1);
|
|
@@ -763,6 +763,19 @@ function errorMessage(error) {
|
|
|
763
763
|
return error instanceof Error ? error.message : String(error);
|
|
764
764
|
}
|
|
765
765
|
|
|
766
|
+
// src/browser/chromium-profile.ts
|
|
767
|
+
var import_node_util = require("util");
|
|
768
|
+
var import_node_child_process2 = require("child_process");
|
|
769
|
+
var import_node_crypto = require("crypto");
|
|
770
|
+
var import_promises = require("fs/promises");
|
|
771
|
+
var import_node_fs = require("fs");
|
|
772
|
+
var import_node_path = require("path");
|
|
773
|
+
var import_node_os = require("os");
|
|
774
|
+
var import_playwright = require("playwright");
|
|
775
|
+
|
|
776
|
+
// src/auth/keychain-store.ts
|
|
777
|
+
var import_node_child_process = require("child_process");
|
|
778
|
+
|
|
766
779
|
// src/browser/chrome.ts
|
|
767
780
|
var import_os = require("os");
|
|
768
781
|
var import_path = require("path");
|
|
@@ -773,9 +786,61 @@ function expandHome(p) {
|
|
|
773
786
|
return p;
|
|
774
787
|
}
|
|
775
788
|
|
|
789
|
+
// src/browser/chromium-profile.ts
|
|
790
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
791
|
+
function directoryExists(filePath) {
|
|
792
|
+
try {
|
|
793
|
+
return (0, import_node_fs.statSync)(filePath).isDirectory();
|
|
794
|
+
} catch {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function fileExists(filePath) {
|
|
799
|
+
try {
|
|
800
|
+
return (0, import_node_fs.statSync)(filePath).isFile();
|
|
801
|
+
} catch {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function resolveCookieDbPath(profileDir) {
|
|
806
|
+
const candidates = [(0, import_node_path.join)(profileDir, "Network", "Cookies"), (0, import_node_path.join)(profileDir, "Cookies")];
|
|
807
|
+
for (const candidate of candidates) {
|
|
808
|
+
if (fileExists(candidate)) {
|
|
809
|
+
return candidate;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
function resolvePersistentChromiumLaunchProfile(inputPath) {
|
|
815
|
+
const expandedPath = expandHome(inputPath.trim());
|
|
816
|
+
if (!expandedPath) {
|
|
817
|
+
return {
|
|
818
|
+
userDataDir: inputPath
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
if (fileExists(expandedPath) && (0, import_node_path.basename)(expandedPath) === "Cookies") {
|
|
822
|
+
const directParent = (0, import_node_path.dirname)(expandedPath);
|
|
823
|
+
const profileDir = (0, import_node_path.basename)(directParent) === "Network" ? (0, import_node_path.dirname)(directParent) : directParent;
|
|
824
|
+
return {
|
|
825
|
+
userDataDir: (0, import_node_path.dirname)(profileDir),
|
|
826
|
+
profileDirectory: (0, import_node_path.basename)(profileDir)
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
if (directoryExists(expandedPath) && resolveCookieDbPath(expandedPath) && fileExists((0, import_node_path.join)((0, import_node_path.dirname)(expandedPath), "Local State"))) {
|
|
830
|
+
return {
|
|
831
|
+
userDataDir: (0, import_node_path.dirname)(expandedPath),
|
|
832
|
+
profileDirectory: (0, import_node_path.basename)(expandedPath)
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
userDataDir: expandedPath
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
776
840
|
// src/browser/pool.ts
|
|
777
841
|
var BrowserPool = class {
|
|
778
842
|
browser = null;
|
|
843
|
+
persistentContext = null;
|
|
779
844
|
cdpProxy = null;
|
|
780
845
|
defaults;
|
|
781
846
|
constructor(defaults = {}) {
|
|
@@ -791,16 +856,23 @@ var BrowserPool = class {
|
|
|
791
856
|
if (connectUrl) {
|
|
792
857
|
return this.connectToRunning(connectUrl, options.timeout);
|
|
793
858
|
}
|
|
794
|
-
if (
|
|
795
|
-
return this.
|
|
859
|
+
if (profileDir) {
|
|
860
|
+
return this.launchPersistentProfile(options, channel, profileDir);
|
|
861
|
+
}
|
|
862
|
+
if (channel) {
|
|
863
|
+
return this.launchWithChannel(options, channel);
|
|
796
864
|
}
|
|
797
865
|
return this.launchSandbox(options);
|
|
798
866
|
}
|
|
799
867
|
async close() {
|
|
800
868
|
const browser = this.browser;
|
|
869
|
+
const persistentContext = this.persistentContext;
|
|
801
870
|
this.browser = null;
|
|
871
|
+
this.persistentContext = null;
|
|
802
872
|
try {
|
|
803
|
-
if (
|
|
873
|
+
if (persistentContext) {
|
|
874
|
+
await persistentContext.close();
|
|
875
|
+
} else if (browser) {
|
|
804
876
|
await browser.close();
|
|
805
877
|
}
|
|
806
878
|
} finally {
|
|
@@ -822,10 +894,11 @@ var BrowserPool = class {
|
|
|
822
894
|
const target = targets[0];
|
|
823
895
|
this.cdpProxy = new CDPProxy(browserWsUrl, target.id);
|
|
824
896
|
const proxyWsUrl = await this.cdpProxy.start();
|
|
825
|
-
browser = await
|
|
897
|
+
browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
|
|
826
898
|
timeout: timeout ?? 3e4
|
|
827
899
|
});
|
|
828
900
|
this.browser = browser;
|
|
901
|
+
this.persistentContext = null;
|
|
829
902
|
const contexts = browser.contexts();
|
|
830
903
|
if (contexts.length === 0) {
|
|
831
904
|
throw new Error(
|
|
@@ -841,46 +914,66 @@ var BrowserPool = class {
|
|
|
841
914
|
await browser.close().catch(() => void 0);
|
|
842
915
|
}
|
|
843
916
|
this.browser = null;
|
|
917
|
+
this.persistentContext = null;
|
|
844
918
|
this.cdpProxy?.close();
|
|
845
919
|
this.cdpProxy = null;
|
|
846
920
|
throw error;
|
|
847
921
|
}
|
|
848
922
|
}
|
|
849
|
-
async
|
|
923
|
+
async launchPersistentProfile(options, channel, profileDir) {
|
|
850
924
|
const args = [];
|
|
851
|
-
|
|
852
|
-
|
|
925
|
+
const launchProfile = resolvePersistentChromiumLaunchProfile(profileDir);
|
|
926
|
+
if (launchProfile.profileDirectory) {
|
|
927
|
+
args.push(`--profile-directory=${launchProfile.profileDirectory}`);
|
|
928
|
+
}
|
|
929
|
+
const context = await import_playwright2.chromium.launchPersistentContext(
|
|
930
|
+
launchProfile.userDataDir,
|
|
931
|
+
{
|
|
932
|
+
channel,
|
|
933
|
+
headless: options.headless ?? this.defaults.headless,
|
|
934
|
+
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
935
|
+
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
936
|
+
timeout: options.timeout,
|
|
937
|
+
...options.context || {},
|
|
938
|
+
args
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
const browser = context.browser();
|
|
942
|
+
if (!browser) {
|
|
943
|
+
await context.close().catch(() => void 0);
|
|
944
|
+
throw new Error("Persistent browser launch did not expose a browser instance.");
|
|
853
945
|
}
|
|
854
|
-
|
|
946
|
+
this.browser = browser;
|
|
947
|
+
this.persistentContext = context;
|
|
948
|
+
const pages = context.pages();
|
|
949
|
+
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
950
|
+
return { browser, context, page, isExternal: false };
|
|
951
|
+
}
|
|
952
|
+
async launchWithChannel(options, channel) {
|
|
953
|
+
const browser = await import_playwright2.chromium.launch({
|
|
855
954
|
channel,
|
|
856
955
|
headless: options.headless ?? this.defaults.headless,
|
|
857
956
|
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
858
957
|
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
859
|
-
|
|
958
|
+
timeout: options.timeout
|
|
860
959
|
});
|
|
861
960
|
this.browser = browser;
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if (contexts.length > 0) {
|
|
866
|
-
context = contexts[0];
|
|
867
|
-
const pages = context.pages();
|
|
868
|
-
page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
869
|
-
} else {
|
|
870
|
-
context = await browser.newContext(options.context || {});
|
|
871
|
-
page = await context.newPage();
|
|
872
|
-
}
|
|
961
|
+
this.persistentContext = null;
|
|
962
|
+
const context = await browser.newContext(options.context || {});
|
|
963
|
+
const page = await context.newPage();
|
|
873
964
|
return { browser, context, page, isExternal: false };
|
|
874
965
|
}
|
|
875
966
|
async launchSandbox(options) {
|
|
876
|
-
const browser = await
|
|
967
|
+
const browser = await import_playwright2.chromium.launch({
|
|
877
968
|
headless: options.headless ?? this.defaults.headless,
|
|
878
969
|
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
879
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0
|
|
970
|
+
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
971
|
+
timeout: options.timeout
|
|
880
972
|
});
|
|
881
973
|
const context = await browser.newContext(options.context || {});
|
|
882
974
|
const page = await context.newPage();
|
|
883
975
|
this.browser = browser;
|
|
976
|
+
this.persistentContext = null;
|
|
884
977
|
return { browser, context, page, isExternal: false };
|
|
885
978
|
}
|
|
886
979
|
};
|
|
@@ -1162,6 +1255,10 @@ var DEFAULT_CONFIG = {
|
|
|
1162
1255
|
storage: {
|
|
1163
1256
|
rootDir: process.cwd()
|
|
1164
1257
|
},
|
|
1258
|
+
cursor: {
|
|
1259
|
+
enabled: false,
|
|
1260
|
+
profile: "snappy"
|
|
1261
|
+
},
|
|
1165
1262
|
model: "gpt-5.1",
|
|
1166
1263
|
debug: false
|
|
1167
1264
|
};
|
|
@@ -1215,7 +1312,7 @@ function loadDotenvValues(rootDir, baseEnv, options = {}) {
|
|
|
1215
1312
|
return values;
|
|
1216
1313
|
}
|
|
1217
1314
|
function resolveEnv(rootDir, options = {}) {
|
|
1218
|
-
const baseEnv = process.env;
|
|
1315
|
+
const baseEnv = options.baseEnv ?? process.env;
|
|
1219
1316
|
const dotenvValues = loadDotenvValues(rootDir, baseEnv, options);
|
|
1220
1317
|
return {
|
|
1221
1318
|
...dotenvValues,
|
|
@@ -1364,6 +1461,11 @@ function resolveOpensteerApiKey(env) {
|
|
|
1364
1461
|
if (!value) return void 0;
|
|
1365
1462
|
return value;
|
|
1366
1463
|
}
|
|
1464
|
+
function resolveOpensteerAccessToken(env) {
|
|
1465
|
+
const value = env.OPENSTEER_ACCESS_TOKEN?.trim();
|
|
1466
|
+
if (!value) return void 0;
|
|
1467
|
+
return value;
|
|
1468
|
+
}
|
|
1367
1469
|
function resolveOpensteerBaseUrl(env) {
|
|
1368
1470
|
const value = env.OPENSTEER_BASE_URL?.trim();
|
|
1369
1471
|
if (!value) return void 0;
|
|
@@ -1372,12 +1474,71 @@ function resolveOpensteerBaseUrl(env) {
|
|
|
1372
1474
|
function resolveOpensteerAuthScheme(env) {
|
|
1373
1475
|
return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
|
|
1374
1476
|
}
|
|
1477
|
+
function resolveOpensteerCloudProfileId(env) {
|
|
1478
|
+
const value = env.OPENSTEER_CLOUD_PROFILE_ID?.trim();
|
|
1479
|
+
if (!value) return void 0;
|
|
1480
|
+
return value;
|
|
1481
|
+
}
|
|
1482
|
+
function resolveOpensteerCloudProfileReuseIfActive(env) {
|
|
1483
|
+
return parseBool(env.OPENSTEER_CLOUD_PROFILE_REUSE_IF_ACTIVE);
|
|
1484
|
+
}
|
|
1485
|
+
function parseCloudBrowserProfileReuseIfActive(value) {
|
|
1486
|
+
if (value == null) return void 0;
|
|
1487
|
+
if (typeof value !== "boolean") {
|
|
1488
|
+
throw new Error(
|
|
1489
|
+
`Invalid cloud.browserProfile.reuseIfActive value "${String(
|
|
1490
|
+
value
|
|
1491
|
+
)}". Use true or false.`
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
return value;
|
|
1495
|
+
}
|
|
1496
|
+
function normalizeCloudBrowserProfileOptions(value, source) {
|
|
1497
|
+
if (value == null) {
|
|
1498
|
+
return void 0;
|
|
1499
|
+
}
|
|
1500
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
1501
|
+
throw new Error(
|
|
1502
|
+
`Invalid ${source} value "${String(value)}". Use an object with profileId and optional reuseIfActive.`
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
const record = value;
|
|
1506
|
+
const rawProfileId = record.profileId;
|
|
1507
|
+
if (typeof rawProfileId !== "string" || !rawProfileId.trim()) {
|
|
1508
|
+
throw new Error(
|
|
1509
|
+
`${source}.profileId must be a non-empty string when browserProfile is provided.`
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
profileId: rawProfileId.trim(),
|
|
1514
|
+
reuseIfActive: parseCloudBrowserProfileReuseIfActive(record.reuseIfActive)
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
function resolveEnvCloudBrowserProfile(profileId, reuseIfActive) {
|
|
1518
|
+
if (reuseIfActive !== void 0 && !profileId) {
|
|
1519
|
+
throw new Error(
|
|
1520
|
+
"OPENSTEER_CLOUD_PROFILE_REUSE_IF_ACTIVE requires OPENSTEER_CLOUD_PROFILE_ID."
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
if (!profileId) {
|
|
1524
|
+
return void 0;
|
|
1525
|
+
}
|
|
1526
|
+
return {
|
|
1527
|
+
profileId,
|
|
1528
|
+
reuseIfActive
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1375
1531
|
function normalizeCloudOptions(value) {
|
|
1376
1532
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1377
1533
|
return void 0;
|
|
1378
1534
|
}
|
|
1379
1535
|
return value;
|
|
1380
1536
|
}
|
|
1537
|
+
function normalizeNonEmptyString(value) {
|
|
1538
|
+
if (typeof value !== "string") return void 0;
|
|
1539
|
+
const normalized = value.trim();
|
|
1540
|
+
return normalized.length ? normalized : void 0;
|
|
1541
|
+
}
|
|
1381
1542
|
function parseCloudEnabled(value, source) {
|
|
1382
1543
|
if (value == null) return void 0;
|
|
1383
1544
|
if (typeof value === "boolean") return value;
|
|
@@ -1406,8 +1567,8 @@ function resolveCloudSelection(config, env = process.env) {
|
|
|
1406
1567
|
source: "default"
|
|
1407
1568
|
};
|
|
1408
1569
|
}
|
|
1409
|
-
function resolveConfigWithEnv(input = {}) {
|
|
1410
|
-
const processEnv = process.env;
|
|
1570
|
+
function resolveConfigWithEnv(input = {}, options = {}) {
|
|
1571
|
+
const processEnv = options.env ?? process.env;
|
|
1411
1572
|
const debugHint = typeof input.debug === "boolean" ? input.debug : parseBool(processEnv.OPENSTEER_DEBUG) === true;
|
|
1412
1573
|
const initialRootDir = input.storage?.rootDir ?? process.cwd();
|
|
1413
1574
|
const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
|
|
@@ -1425,7 +1586,8 @@ function resolveConfigWithEnv(input = {}) {
|
|
|
1425
1586
|
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
1426
1587
|
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
1427
1588
|
const env = resolveEnv(envRootDir, {
|
|
1428
|
-
debug: debugHint
|
|
1589
|
+
debug: debugHint,
|
|
1590
|
+
baseEnv: processEnv
|
|
1429
1591
|
});
|
|
1430
1592
|
if (env.OPENSTEER_AI_MODEL) {
|
|
1431
1593
|
throw new Error(
|
|
@@ -1446,6 +1608,9 @@ function resolveConfigWithEnv(input = {}) {
|
|
|
1446
1608
|
channel: env.OPENSTEER_CHANNEL || void 0,
|
|
1447
1609
|
profileDir: env.OPENSTEER_PROFILE_DIR || void 0
|
|
1448
1610
|
},
|
|
1611
|
+
cursor: {
|
|
1612
|
+
enabled: parseBool(env.OPENSTEER_CURSOR)
|
|
1613
|
+
},
|
|
1449
1614
|
model: env.OPENSTEER_MODEL || void 0,
|
|
1450
1615
|
debug: parseBool(env.OPENSTEER_DEBUG)
|
|
1451
1616
|
};
|
|
@@ -1453,13 +1618,27 @@ function resolveConfigWithEnv(input = {}) {
|
|
|
1453
1618
|
const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
|
|
1454
1619
|
const resolved = mergeDeep(mergedWithEnv, input);
|
|
1455
1620
|
const envApiKey = resolveOpensteerApiKey(env);
|
|
1621
|
+
const envAccessTokenRaw = resolveOpensteerAccessToken(env);
|
|
1456
1622
|
const envBaseUrl = resolveOpensteerBaseUrl(env);
|
|
1457
1623
|
const envAuthScheme = resolveOpensteerAuthScheme(env);
|
|
1624
|
+
if (envApiKey && envAccessTokenRaw) {
|
|
1625
|
+
throw new Error(
|
|
1626
|
+
"OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
const envAccessToken = envAccessTokenRaw || (envAuthScheme === "bearer" ? envApiKey : void 0);
|
|
1630
|
+
const envApiCredential = envAuthScheme === "bearer" && !envAccessTokenRaw ? void 0 : envApiKey;
|
|
1631
|
+
const envCloudProfileId = resolveOpensteerCloudProfileId(env);
|
|
1632
|
+
const envCloudProfileReuseIfActive = resolveOpensteerCloudProfileReuseIfActive(env);
|
|
1458
1633
|
const envCloudAnnounce = parseCloudAnnounce(
|
|
1459
1634
|
env.OPENSTEER_REMOTE_ANNOUNCE,
|
|
1460
1635
|
"OPENSTEER_REMOTE_ANNOUNCE"
|
|
1461
1636
|
);
|
|
1462
1637
|
const inputCloudOptions = normalizeCloudOptions(input.cloud);
|
|
1638
|
+
const inputCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
|
|
1639
|
+
inputCloudOptions?.browserProfile,
|
|
1640
|
+
"cloud.browserProfile"
|
|
1641
|
+
);
|
|
1463
1642
|
const inputAuthScheme = parseAuthScheme(
|
|
1464
1643
|
inputCloudOptions?.authScheme,
|
|
1465
1644
|
"cloud.authScheme"
|
|
@@ -1471,26 +1650,65 @@ function resolveConfigWithEnv(input = {}) {
|
|
|
1471
1650
|
const inputHasCloudApiKey = Boolean(
|
|
1472
1651
|
inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
|
|
1473
1652
|
);
|
|
1653
|
+
const inputHasCloudAccessToken = Boolean(
|
|
1654
|
+
inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "accessToken")
|
|
1655
|
+
);
|
|
1474
1656
|
const inputHasCloudBaseUrl = Boolean(
|
|
1475
1657
|
inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
|
|
1476
1658
|
);
|
|
1659
|
+
if (normalizeNonEmptyString(inputCloudOptions?.apiKey) && normalizeNonEmptyString(inputCloudOptions?.accessToken)) {
|
|
1660
|
+
throw new Error(
|
|
1661
|
+
"cloud.apiKey and cloud.accessToken are mutually exclusive. Set only one."
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1477
1664
|
const cloudSelection = resolveCloudSelection({
|
|
1478
1665
|
cloud: resolved.cloud
|
|
1479
1666
|
}, env);
|
|
1480
1667
|
if (cloudSelection.cloud) {
|
|
1481
1668
|
const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
|
|
1482
|
-
const
|
|
1669
|
+
const {
|
|
1670
|
+
apiKey: resolvedCloudApiKeyRaw,
|
|
1671
|
+
accessToken: resolvedCloudAccessTokenRaw,
|
|
1672
|
+
...resolvedCloudRest
|
|
1673
|
+
} = resolvedCloud;
|
|
1674
|
+
if (normalizeNonEmptyString(resolvedCloudApiKeyRaw) && normalizeNonEmptyString(resolvedCloudAccessTokenRaw)) {
|
|
1675
|
+
throw new Error(
|
|
1676
|
+
"Cloud config cannot include both apiKey and accessToken at the same time."
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
const resolvedCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
|
|
1680
|
+
resolvedCloud.browserProfile,
|
|
1681
|
+
"resolved.cloud.browserProfile"
|
|
1682
|
+
);
|
|
1683
|
+
const envCloudBrowserProfile = resolveEnvCloudBrowserProfile(
|
|
1684
|
+
envCloudProfileId,
|
|
1685
|
+
envCloudProfileReuseIfActive
|
|
1686
|
+
);
|
|
1687
|
+
const browserProfile = inputCloudBrowserProfile ?? envCloudBrowserProfile ?? resolvedCloudBrowserProfile;
|
|
1688
|
+
let authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
|
|
1483
1689
|
const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
|
|
1690
|
+
const credentialOverriddenByInput = inputHasCloudApiKey || inputHasCloudAccessToken;
|
|
1691
|
+
let apiKey = normalizeNonEmptyString(resolvedCloudApiKeyRaw);
|
|
1692
|
+
let accessToken = normalizeNonEmptyString(resolvedCloudAccessTokenRaw);
|
|
1693
|
+
if (!credentialOverriddenByInput) {
|
|
1694
|
+
if (envAccessToken) {
|
|
1695
|
+
accessToken = envAccessToken;
|
|
1696
|
+
apiKey = void 0;
|
|
1697
|
+
} else if (envApiCredential) {
|
|
1698
|
+
apiKey = envApiCredential;
|
|
1699
|
+
accessToken = void 0;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (accessToken) {
|
|
1703
|
+
authScheme = "bearer";
|
|
1704
|
+
}
|
|
1484
1705
|
resolved.cloud = {
|
|
1485
|
-
...
|
|
1706
|
+
...resolvedCloudRest,
|
|
1707
|
+
...inputHasCloudApiKey ? { apiKey: resolvedCloudApiKeyRaw } : apiKey ? { apiKey } : {},
|
|
1708
|
+
...inputHasCloudAccessToken ? { accessToken: resolvedCloudAccessTokenRaw } : accessToken ? { accessToken } : {},
|
|
1486
1709
|
authScheme,
|
|
1487
|
-
announce
|
|
1488
|
-
|
|
1489
|
-
}
|
|
1490
|
-
if (envApiKey && cloudSelection.cloud && !inputHasCloudApiKey) {
|
|
1491
|
-
resolved.cloud = {
|
|
1492
|
-
...normalizeCloudOptions(resolved.cloud) ?? {},
|
|
1493
|
-
apiKey: envApiKey
|
|
1710
|
+
announce,
|
|
1711
|
+
...browserProfile ? { browserProfile } : {}
|
|
1494
1712
|
};
|
|
1495
1713
|
}
|
|
1496
1714
|
if (envBaseUrl && cloudSelection.cloud && !inputHasCloudBaseUrl) {
|
|
@@ -4883,6 +5101,16 @@ async function probeActionabilityState(element) {
|
|
|
4883
5101
|
|
|
4884
5102
|
// src/extract-value-normalization.ts
|
|
4885
5103
|
var URL_LIST_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset", "ping"]);
|
|
5104
|
+
var IFRAME_URL_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
5105
|
+
"href",
|
|
5106
|
+
"src",
|
|
5107
|
+
"srcset",
|
|
5108
|
+
"imagesrcset",
|
|
5109
|
+
"action",
|
|
5110
|
+
"formaction",
|
|
5111
|
+
"poster",
|
|
5112
|
+
"ping"
|
|
5113
|
+
]);
|
|
4886
5114
|
function normalizeExtractedValue(raw, attribute) {
|
|
4887
5115
|
if (raw == null) return null;
|
|
4888
5116
|
const rawText = String(raw);
|
|
@@ -4898,6 +5126,19 @@ function normalizeExtractedValue(raw, attribute) {
|
|
|
4898
5126
|
const text = rawText.replace(/\s+/g, " ").trim();
|
|
4899
5127
|
return text || null;
|
|
4900
5128
|
}
|
|
5129
|
+
function resolveExtractedValueInContext(normalizedValue, options) {
|
|
5130
|
+
if (normalizedValue == null) return null;
|
|
5131
|
+
const normalizedAttribute = String(options.attribute || "").trim().toLowerCase();
|
|
5132
|
+
if (!options.insideIframe) return normalizedValue;
|
|
5133
|
+
if (!IFRAME_URL_ATTRIBUTES.has(normalizedAttribute)) return normalizedValue;
|
|
5134
|
+
const baseURI = String(options.baseURI || "").trim();
|
|
5135
|
+
if (!baseURI) return normalizedValue;
|
|
5136
|
+
try {
|
|
5137
|
+
return new URL(normalizedValue, baseURI).href;
|
|
5138
|
+
} catch {
|
|
5139
|
+
return normalizedValue;
|
|
5140
|
+
}
|
|
5141
|
+
}
|
|
4901
5142
|
function pickSingleListAttributeValue(attribute, raw) {
|
|
4902
5143
|
if (attribute === "ping") {
|
|
4903
5144
|
const firstUrl = raw.trim().split(/\s+/)[0] || "";
|
|
@@ -5053,6 +5294,36 @@ function readDescriptorToken(value, index) {
|
|
|
5053
5294
|
};
|
|
5054
5295
|
}
|
|
5055
5296
|
|
|
5297
|
+
// src/extract-value-reader.ts
|
|
5298
|
+
async function readExtractedValueFromHandle(element, options) {
|
|
5299
|
+
const insideIframe = await isElementInsideIframe(element);
|
|
5300
|
+
const payload = await element.evaluate(
|
|
5301
|
+
(target, browserOptions) => {
|
|
5302
|
+
const ownerDocument = target.ownerDocument;
|
|
5303
|
+
return {
|
|
5304
|
+
raw: browserOptions.attribute ? target.getAttribute(browserOptions.attribute) : target.textContent,
|
|
5305
|
+
baseURI: ownerDocument?.baseURI || null
|
|
5306
|
+
};
|
|
5307
|
+
},
|
|
5308
|
+
{
|
|
5309
|
+
attribute: options.attribute
|
|
5310
|
+
}
|
|
5311
|
+
);
|
|
5312
|
+
const normalizedValue = normalizeExtractedValue(
|
|
5313
|
+
payload.raw,
|
|
5314
|
+
options.attribute
|
|
5315
|
+
);
|
|
5316
|
+
return resolveExtractedValueInContext(normalizedValue, {
|
|
5317
|
+
attribute: options.attribute,
|
|
5318
|
+
baseURI: payload.baseURI,
|
|
5319
|
+
insideIframe
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
async function isElementInsideIframe(element) {
|
|
5323
|
+
const ownerFrame = await element.ownerFrame();
|
|
5324
|
+
return !!ownerFrame?.parentFrame();
|
|
5325
|
+
}
|
|
5326
|
+
|
|
5056
5327
|
// src/html/counter-runtime.ts
|
|
5057
5328
|
var CounterResolutionError = class extends Error {
|
|
5058
5329
|
code;
|
|
@@ -5075,13 +5346,14 @@ async function resolveCounterElement(page, counter) {
|
|
|
5075
5346
|
if (entry.count > 1) {
|
|
5076
5347
|
throw buildCounterAmbiguousError(counter);
|
|
5077
5348
|
}
|
|
5078
|
-
const
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5349
|
+
const resolution = await resolveCounterElementInFrame(entry.frame, normalized);
|
|
5350
|
+
if (resolution.status === "ambiguous") {
|
|
5351
|
+
throw buildCounterAmbiguousError(counter);
|
|
5352
|
+
}
|
|
5353
|
+
if (resolution.status === "missing") {
|
|
5082
5354
|
throw buildCounterNotFoundError(counter);
|
|
5083
5355
|
}
|
|
5084
|
-
return element;
|
|
5356
|
+
return resolution.element;
|
|
5085
5357
|
}
|
|
5086
5358
|
async function resolveCountersBatch(page, requests) {
|
|
5087
5359
|
const out = {};
|
|
@@ -5095,41 +5367,57 @@ async function resolveCountersBatch(page, requests) {
|
|
|
5095
5367
|
}
|
|
5096
5368
|
}
|
|
5097
5369
|
const valueCache = /* @__PURE__ */ new Map();
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
normalized
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5370
|
+
const elementCache = /* @__PURE__ */ new Map();
|
|
5371
|
+
try {
|
|
5372
|
+
for (const request of requests) {
|
|
5373
|
+
const normalized = normalizeCounter(request.counter);
|
|
5374
|
+
if (normalized == null) {
|
|
5375
|
+
out[request.key] = null;
|
|
5376
|
+
continue;
|
|
5377
|
+
}
|
|
5378
|
+
const entry = scan.get(normalized);
|
|
5379
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
5380
|
+
out[request.key] = null;
|
|
5381
|
+
continue;
|
|
5382
|
+
}
|
|
5383
|
+
const cacheKey = `${normalized}:${request.attribute || ""}`;
|
|
5384
|
+
if (valueCache.has(cacheKey)) {
|
|
5385
|
+
out[request.key] = valueCache.get(cacheKey);
|
|
5386
|
+
continue;
|
|
5387
|
+
}
|
|
5388
|
+
if (!elementCache.has(normalized)) {
|
|
5389
|
+
elementCache.set(
|
|
5390
|
+
normalized,
|
|
5391
|
+
await resolveCounterElementInFrame(entry.frame, normalized)
|
|
5392
|
+
);
|
|
5393
|
+
}
|
|
5394
|
+
const resolution = elementCache.get(normalized);
|
|
5395
|
+
if (resolution.status === "ambiguous") {
|
|
5396
|
+
throw buildCounterAmbiguousError(normalized);
|
|
5397
|
+
}
|
|
5398
|
+
if (resolution.status === "missing") {
|
|
5399
|
+
valueCache.set(cacheKey, null);
|
|
5400
|
+
out[request.key] = null;
|
|
5401
|
+
continue;
|
|
5402
|
+
}
|
|
5403
|
+
const value = await readCounterValueFromElement(
|
|
5404
|
+
resolution.element,
|
|
5405
|
+
request.attribute
|
|
5406
|
+
);
|
|
5407
|
+
if (value.status === "missing") {
|
|
5408
|
+
await resolution.element.dispose();
|
|
5409
|
+
elementCache.set(normalized, {
|
|
5410
|
+
status: "missing"
|
|
5411
|
+
});
|
|
5412
|
+
valueCache.set(cacheKey, null);
|
|
5413
|
+
out[request.key] = null;
|
|
5414
|
+
continue;
|
|
5415
|
+
}
|
|
5416
|
+
valueCache.set(cacheKey, value.value);
|
|
5417
|
+
out[request.key] = value.value;
|
|
5126
5418
|
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
request.attribute
|
|
5130
|
-
);
|
|
5131
|
-
valueCache.set(cacheKey, normalizedValue);
|
|
5132
|
-
out[request.key] = normalizedValue;
|
|
5419
|
+
} finally {
|
|
5420
|
+
await disposeResolvedCounterElements(elementCache.values());
|
|
5133
5421
|
}
|
|
5134
5422
|
return out;
|
|
5135
5423
|
}
|
|
@@ -5201,73 +5489,79 @@ async function scanCounterOccurrences(page, counters) {
|
|
|
5201
5489
|
}
|
|
5202
5490
|
return out;
|
|
5203
5491
|
}
|
|
5204
|
-
async function
|
|
5205
|
-
|
|
5206
|
-
const
|
|
5207
|
-
|
|
5208
|
-
const
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5492
|
+
async function resolveCounterElementInFrame(frame, counter) {
|
|
5493
|
+
try {
|
|
5494
|
+
const handle = await frame.evaluateHandle((targetCounter) => {
|
|
5495
|
+
const matches = [];
|
|
5496
|
+
const walk = (root) => {
|
|
5497
|
+
const children = Array.from(root.children);
|
|
5498
|
+
for (const child of children) {
|
|
5499
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
5500
|
+
matches.push(child);
|
|
5501
|
+
}
|
|
5502
|
+
walk(child);
|
|
5503
|
+
if (child.shadowRoot) {
|
|
5504
|
+
walk(child.shadowRoot);
|
|
5505
|
+
}
|
|
5216
5506
|
}
|
|
5507
|
+
};
|
|
5508
|
+
walk(document);
|
|
5509
|
+
if (!matches.length) {
|
|
5510
|
+
return "missing";
|
|
5217
5511
|
}
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
return
|
|
5512
|
+
if (matches.length > 1) {
|
|
5513
|
+
return "ambiguous";
|
|
5514
|
+
}
|
|
5515
|
+
return matches[0];
|
|
5516
|
+
}, String(counter));
|
|
5517
|
+
const element = handle.asElement();
|
|
5518
|
+
if (element) {
|
|
5519
|
+
return {
|
|
5520
|
+
status: "resolved",
|
|
5521
|
+
element
|
|
5522
|
+
};
|
|
5523
|
+
}
|
|
5524
|
+
const status = await handle.jsonValue();
|
|
5525
|
+
await handle.dispose();
|
|
5526
|
+
return status === "ambiguous" ? { status: "ambiguous" } : { status: "missing" };
|
|
5527
|
+
} catch (error) {
|
|
5528
|
+
if (isRecoverableCounterReadRace(error)) {
|
|
5529
|
+
return {
|
|
5530
|
+
status: "missing"
|
|
5531
|
+
};
|
|
5222
5532
|
}
|
|
5223
|
-
|
|
5224
|
-
}
|
|
5533
|
+
throw error;
|
|
5534
|
+
}
|
|
5225
5535
|
}
|
|
5226
|
-
async function
|
|
5536
|
+
async function readCounterValueFromElement(element, attribute) {
|
|
5227
5537
|
try {
|
|
5228
|
-
return await frame.evaluate(
|
|
5229
|
-
({ targetCounter, attribute: attribute2 }) => {
|
|
5230
|
-
const matches = [];
|
|
5231
|
-
const walk = (root) => {
|
|
5232
|
-
const children = Array.from(root.children);
|
|
5233
|
-
for (const child of children) {
|
|
5234
|
-
if (child.getAttribute("c") === targetCounter) {
|
|
5235
|
-
matches.push(child);
|
|
5236
|
-
}
|
|
5237
|
-
walk(child);
|
|
5238
|
-
if (child.shadowRoot) {
|
|
5239
|
-
walk(child.shadowRoot);
|
|
5240
|
-
}
|
|
5241
|
-
}
|
|
5242
|
-
};
|
|
5243
|
-
walk(document);
|
|
5244
|
-
if (!matches.length) {
|
|
5245
|
-
return {
|
|
5246
|
-
status: "missing"
|
|
5247
|
-
};
|
|
5248
|
-
}
|
|
5249
|
-
if (matches.length > 1) {
|
|
5250
|
-
return {
|
|
5251
|
-
status: "ambiguous"
|
|
5252
|
-
};
|
|
5253
|
-
}
|
|
5254
|
-
const target = matches[0];
|
|
5255
|
-
const value = attribute2 ? target.getAttribute(attribute2) : target.textContent;
|
|
5256
|
-
return {
|
|
5257
|
-
status: "ok",
|
|
5258
|
-
value
|
|
5259
|
-
};
|
|
5260
|
-
},
|
|
5261
|
-
{
|
|
5262
|
-
targetCounter: String(counter),
|
|
5263
|
-
attribute
|
|
5264
|
-
}
|
|
5265
|
-
);
|
|
5266
|
-
} catch {
|
|
5267
5538
|
return {
|
|
5268
|
-
status: "
|
|
5539
|
+
status: "ok",
|
|
5540
|
+
value: await readExtractedValueFromHandle(element, {
|
|
5541
|
+
attribute
|
|
5542
|
+
})
|
|
5269
5543
|
};
|
|
5544
|
+
} catch (error) {
|
|
5545
|
+
if (isRecoverableCounterReadRace(error)) {
|
|
5546
|
+
return {
|
|
5547
|
+
status: "missing"
|
|
5548
|
+
};
|
|
5549
|
+
}
|
|
5550
|
+
throw error;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
async function disposeResolvedCounterElements(resolutions) {
|
|
5554
|
+
const disposals = [];
|
|
5555
|
+
for (const resolution of resolutions) {
|
|
5556
|
+
if (resolution.status !== "resolved") continue;
|
|
5557
|
+
disposals.push(resolution.element.dispose());
|
|
5270
5558
|
}
|
|
5559
|
+
await Promise.all(disposals);
|
|
5560
|
+
}
|
|
5561
|
+
function isRecoverableCounterReadRace(error) {
|
|
5562
|
+
if (!(error instanceof Error)) return false;
|
|
5563
|
+
const message = error.message;
|
|
5564
|
+
return message.includes("Execution context was destroyed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context") || message.includes("Frame was detached") || message.includes("Element is not attached to the DOM") || message.includes("Element is detached");
|
|
5271
5565
|
}
|
|
5272
5566
|
function buildCounterNotFoundError(counter) {
|
|
5273
5567
|
return new CounterResolutionError(
|
|
@@ -5883,17 +6177,6 @@ async function performSelect(page, path5, options) {
|
|
|
5883
6177
|
}
|
|
5884
6178
|
|
|
5885
6179
|
// src/actions/extract.ts
|
|
5886
|
-
async function readFieldValueFromHandle(element, options) {
|
|
5887
|
-
const raw = await element.evaluate(
|
|
5888
|
-
(target, payload) => {
|
|
5889
|
-
return payload.attribute ? target.getAttribute(payload.attribute) : target.textContent;
|
|
5890
|
-
},
|
|
5891
|
-
{
|
|
5892
|
-
attribute: options.attribute
|
|
5893
|
-
}
|
|
5894
|
-
);
|
|
5895
|
-
return normalizeExtractedValue(raw, options.attribute);
|
|
5896
|
-
}
|
|
5897
6180
|
async function extractWithPaths(page, fields) {
|
|
5898
6181
|
const result = {};
|
|
5899
6182
|
for (const field of fields) {
|
|
@@ -5905,7 +6188,7 @@ async function extractWithPaths(page, fields) {
|
|
|
5905
6188
|
continue;
|
|
5906
6189
|
}
|
|
5907
6190
|
try {
|
|
5908
|
-
result[field.key] = await
|
|
6191
|
+
result[field.key] = await readExtractedValueFromHandle(
|
|
5909
6192
|
resolved.element,
|
|
5910
6193
|
{
|
|
5911
6194
|
attribute: field.attribute
|
|
@@ -5941,7 +6224,7 @@ async function extractArrayRowsWithPaths(page, array) {
|
|
|
5941
6224
|
field.candidates
|
|
5942
6225
|
) : item;
|
|
5943
6226
|
try {
|
|
5944
|
-
const value = target ? await
|
|
6227
|
+
const value = target ? await readExtractedValueFromHandle(target, {
|
|
5945
6228
|
attribute: field.attribute
|
|
5946
6229
|
}) : null;
|
|
5947
6230
|
if (key) {
|
|
@@ -6253,7 +6536,7 @@ async function closeTab(context, activePage, index) {
|
|
|
6253
6536
|
}
|
|
6254
6537
|
|
|
6255
6538
|
// src/actions/cookies.ts
|
|
6256
|
-
var
|
|
6539
|
+
var import_promises2 = require("fs/promises");
|
|
6257
6540
|
async function getCookies(context, url) {
|
|
6258
6541
|
return context.cookies(url ? [url] : void 0);
|
|
6259
6542
|
}
|
|
@@ -6265,10 +6548,10 @@ async function clearCookies(context) {
|
|
|
6265
6548
|
}
|
|
6266
6549
|
async function exportCookies(context, filePath, url) {
|
|
6267
6550
|
const cookies = await context.cookies(url ? [url] : void 0);
|
|
6268
|
-
await (0,
|
|
6551
|
+
await (0, import_promises2.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
6269
6552
|
}
|
|
6270
6553
|
async function importCookies(context, filePath) {
|
|
6271
|
-
const raw = await (0,
|
|
6554
|
+
const raw = await (0, import_promises2.readFile)(filePath, "utf-8");
|
|
6272
6555
|
const cookies = JSON.parse(raw);
|
|
6273
6556
|
await context.addCookies(cookies);
|
|
6274
6557
|
}
|
|
@@ -8235,13 +8518,13 @@ function dedupeNewest(entries) {
|
|
|
8235
8518
|
}
|
|
8236
8519
|
|
|
8237
8520
|
// src/cloud/cdp-client.ts
|
|
8238
|
-
var
|
|
8521
|
+
var import_playwright3 = require("playwright");
|
|
8239
8522
|
var CloudCdpClient = class {
|
|
8240
8523
|
async connect(args) {
|
|
8241
8524
|
const endpoint = withTokenQuery2(args.wsUrl, args.token);
|
|
8242
8525
|
let browser;
|
|
8243
8526
|
try {
|
|
8244
|
-
browser = await
|
|
8527
|
+
browser = await import_playwright3.chromium.connectOverCDP(endpoint);
|
|
8245
8528
|
} catch (error) {
|
|
8246
8529
|
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
8247
8530
|
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
@@ -8296,31 +8579,72 @@ function withTokenQuery2(wsUrl, token) {
|
|
|
8296
8579
|
return url.toString();
|
|
8297
8580
|
}
|
|
8298
8581
|
|
|
8299
|
-
// src/
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
authScheme;
|
|
8305
|
-
constructor(baseUrl, key, authScheme = "api-key") {
|
|
8306
|
-
this.baseUrl = normalizeBaseUrl(baseUrl);
|
|
8307
|
-
this.key = key;
|
|
8308
|
-
this.authScheme = authScheme;
|
|
8582
|
+
// src/utils/strip-trailing-slashes.ts
|
|
8583
|
+
function stripTrailingSlashes(value) {
|
|
8584
|
+
let end = value.length;
|
|
8585
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
8586
|
+
end -= 1;
|
|
8309
8587
|
}
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
}
|
|
8322
|
-
|
|
8323
|
-
|
|
8588
|
+
return end === value.length ? value : value.slice(0, end);
|
|
8589
|
+
}
|
|
8590
|
+
|
|
8591
|
+
// src/cloud/http-client.ts
|
|
8592
|
+
function normalizeCloudBaseUrl(baseUrl) {
|
|
8593
|
+
return stripTrailingSlashes(baseUrl);
|
|
8594
|
+
}
|
|
8595
|
+
function cloudAuthHeaders(key, authScheme) {
|
|
8596
|
+
if (authScheme === "bearer") {
|
|
8597
|
+
return {
|
|
8598
|
+
authorization: `Bearer ${key}`
|
|
8599
|
+
};
|
|
8600
|
+
}
|
|
8601
|
+
return {
|
|
8602
|
+
"x-api-key": key
|
|
8603
|
+
};
|
|
8604
|
+
}
|
|
8605
|
+
async function parseCloudHttpError(response) {
|
|
8606
|
+
let body = null;
|
|
8607
|
+
try {
|
|
8608
|
+
body = await response.json();
|
|
8609
|
+
} catch {
|
|
8610
|
+
body = null;
|
|
8611
|
+
}
|
|
8612
|
+
const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
|
|
8613
|
+
const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
|
|
8614
|
+
return new OpensteerCloudError(code, message, response.status, body?.details);
|
|
8615
|
+
}
|
|
8616
|
+
function toCloudErrorCode(code) {
|
|
8617
|
+
if (code === "CLOUD_AUTH_FAILED" || code === "CLOUD_SESSION_NOT_FOUND" || code === "CLOUD_SESSION_CLOSED" || code === "CLOUD_UNSUPPORTED_METHOD" || code === "CLOUD_INVALID_REQUEST" || code === "CLOUD_MODEL_NOT_ALLOWED" || code === "CLOUD_ACTION_FAILED" || code === "CLOUD_INTERNAL" || code === "CLOUD_CAPACITY_EXHAUSTED" || code === "CLOUD_RUNTIME_UNAVAILABLE" || code === "CLOUD_RUNTIME_MISMATCH" || code === "CLOUD_SESSION_STALE" || code === "CLOUD_CONTRACT_MISMATCH" || code === "CLOUD_CONTROL_PLANE_ERROR" || code === "CLOUD_PROXY_UNAVAILABLE" || code === "CLOUD_PROXY_REQUIRED" || code === "CLOUD_BILLING_LIMIT_REACHED" || code === "CLOUD_RATE_LIMITED" || code === "CLOUD_BROWSER_PROFILE_NOT_FOUND" || code === "CLOUD_BROWSER_PROFILE_BUSY" || code === "CLOUD_BROWSER_PROFILE_DISABLED" || code === "CLOUD_BROWSER_PROFILE_PROXY_UNAVAILABLE" || code === "CLOUD_BROWSER_PROFILE_SYNC_FAILED") {
|
|
8618
|
+
return code;
|
|
8619
|
+
}
|
|
8620
|
+
return "CLOUD_TRANSPORT_ERROR";
|
|
8621
|
+
}
|
|
8622
|
+
|
|
8623
|
+
// src/cloud/session-client.ts
|
|
8624
|
+
var CACHE_IMPORT_BATCH_SIZE = 200;
|
|
8625
|
+
var CloudSessionClient = class {
|
|
8626
|
+
baseUrl;
|
|
8627
|
+
key;
|
|
8628
|
+
authScheme;
|
|
8629
|
+
constructor(baseUrl, key, authScheme = "api-key") {
|
|
8630
|
+
this.baseUrl = normalizeCloudBaseUrl(baseUrl);
|
|
8631
|
+
this.key = key;
|
|
8632
|
+
this.authScheme = authScheme;
|
|
8633
|
+
}
|
|
8634
|
+
async create(request) {
|
|
8635
|
+
const response = await fetch(`${this.baseUrl}/sessions`, {
|
|
8636
|
+
method: "POST",
|
|
8637
|
+
headers: {
|
|
8638
|
+
"content-type": "application/json",
|
|
8639
|
+
...cloudAuthHeaders(this.key, this.authScheme)
|
|
8640
|
+
},
|
|
8641
|
+
body: JSON.stringify(request)
|
|
8642
|
+
});
|
|
8643
|
+
if (!response.ok) {
|
|
8644
|
+
throw await parseCloudHttpError(response);
|
|
8645
|
+
}
|
|
8646
|
+
let body;
|
|
8647
|
+
try {
|
|
8324
8648
|
body = await response.json();
|
|
8325
8649
|
} catch {
|
|
8326
8650
|
throw new OpensteerCloudError(
|
|
@@ -8335,14 +8659,14 @@ var CloudSessionClient = class {
|
|
|
8335
8659
|
const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
|
|
8336
8660
|
method: "DELETE",
|
|
8337
8661
|
headers: {
|
|
8338
|
-
...this.
|
|
8662
|
+
...cloudAuthHeaders(this.key, this.authScheme)
|
|
8339
8663
|
}
|
|
8340
8664
|
});
|
|
8341
8665
|
if (response.status === 204) {
|
|
8342
8666
|
return;
|
|
8343
8667
|
}
|
|
8344
8668
|
if (!response.ok) {
|
|
8345
|
-
throw await
|
|
8669
|
+
throw await parseCloudHttpError(response);
|
|
8346
8670
|
}
|
|
8347
8671
|
}
|
|
8348
8672
|
async importSelectorCache(request) {
|
|
@@ -8365,29 +8689,16 @@ var CloudSessionClient = class {
|
|
|
8365
8689
|
method: "POST",
|
|
8366
8690
|
headers: {
|
|
8367
8691
|
"content-type": "application/json",
|
|
8368
|
-
...this.
|
|
8692
|
+
...cloudAuthHeaders(this.key, this.authScheme)
|
|
8369
8693
|
},
|
|
8370
8694
|
body: JSON.stringify({ entries })
|
|
8371
8695
|
});
|
|
8372
8696
|
if (!response.ok) {
|
|
8373
|
-
throw await
|
|
8697
|
+
throw await parseCloudHttpError(response);
|
|
8374
8698
|
}
|
|
8375
8699
|
return await response.json();
|
|
8376
8700
|
}
|
|
8377
|
-
authHeaders() {
|
|
8378
|
-
if (this.authScheme === "bearer") {
|
|
8379
|
-
return {
|
|
8380
|
-
authorization: `Bearer ${this.key}`
|
|
8381
|
-
};
|
|
8382
|
-
}
|
|
8383
|
-
return {
|
|
8384
|
-
"x-api-key": this.key
|
|
8385
|
-
};
|
|
8386
|
-
}
|
|
8387
8701
|
};
|
|
8388
|
-
function normalizeBaseUrl(baseUrl) {
|
|
8389
|
-
return baseUrl.replace(/\/+$/, "");
|
|
8390
|
-
}
|
|
8391
8702
|
function parseCreateResponse(body, status) {
|
|
8392
8703
|
const root = requireObject(
|
|
8393
8704
|
body,
|
|
@@ -8532,23 +8843,6 @@ function mergeImportResponse(first, second) {
|
|
|
8532
8843
|
skipped: first.skipped + second.skipped
|
|
8533
8844
|
};
|
|
8534
8845
|
}
|
|
8535
|
-
async function parseHttpError(response) {
|
|
8536
|
-
let body = null;
|
|
8537
|
-
try {
|
|
8538
|
-
body = await response.json();
|
|
8539
|
-
} catch {
|
|
8540
|
-
body = null;
|
|
8541
|
-
}
|
|
8542
|
-
const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
|
|
8543
|
-
const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
|
|
8544
|
-
return new OpensteerCloudError(code, message, response.status, body?.details);
|
|
8545
|
-
}
|
|
8546
|
-
function toCloudErrorCode(code) {
|
|
8547
|
-
if (code === "CLOUD_AUTH_FAILED" || code === "CLOUD_SESSION_NOT_FOUND" || code === "CLOUD_SESSION_CLOSED" || code === "CLOUD_UNSUPPORTED_METHOD" || code === "CLOUD_INVALID_REQUEST" || code === "CLOUD_MODEL_NOT_ALLOWED" || code === "CLOUD_ACTION_FAILED" || code === "CLOUD_INTERNAL" || code === "CLOUD_CAPACITY_EXHAUSTED" || code === "CLOUD_RUNTIME_UNAVAILABLE" || code === "CLOUD_RUNTIME_MISMATCH" || code === "CLOUD_SESSION_STALE" || code === "CLOUD_CONTRACT_MISMATCH" || code === "CLOUD_CONTROL_PLANE_ERROR") {
|
|
8548
|
-
return code;
|
|
8549
|
-
}
|
|
8550
|
-
return "CLOUD_TRANSPORT_ERROR";
|
|
8551
|
-
}
|
|
8552
8846
|
|
|
8553
8847
|
// src/cloud/runtime.ts
|
|
8554
8848
|
var DEFAULT_CLOUD_BASE_URL = "https://api.opensteer.com";
|
|
@@ -8573,9 +8867,6 @@ function resolveCloudBaseUrl() {
|
|
|
8573
8867
|
if (!value) return DEFAULT_CLOUD_BASE_URL;
|
|
8574
8868
|
return normalizeCloudBaseUrl(value);
|
|
8575
8869
|
}
|
|
8576
|
-
function normalizeCloudBaseUrl(value) {
|
|
8577
|
-
return value.replace(/\/+$/, "");
|
|
8578
|
-
}
|
|
8579
8870
|
function readCloudActionDescription(payload) {
|
|
8580
8871
|
const description = payload.description;
|
|
8581
8872
|
if (typeof description !== "string") return void 0;
|
|
@@ -9906,7 +10197,7 @@ function resolveAgentConfig(args) {
|
|
|
9906
10197
|
});
|
|
9907
10198
|
return {
|
|
9908
10199
|
mode: "cua",
|
|
9909
|
-
systemPrompt:
|
|
10200
|
+
systemPrompt: normalizeNonEmptyString2(agentConfig.systemPrompt) || DEFAULT_SYSTEM_PROMPT,
|
|
9910
10201
|
waitBetweenActionsMs: normalizeWaitBetween(agentConfig.waitBetweenActionsMs),
|
|
9911
10202
|
model
|
|
9912
10203
|
};
|
|
@@ -9925,7 +10216,7 @@ function createCuaClient(config) {
|
|
|
9925
10216
|
);
|
|
9926
10217
|
}
|
|
9927
10218
|
}
|
|
9928
|
-
function
|
|
10219
|
+
function normalizeNonEmptyString2(value) {
|
|
9929
10220
|
if (typeof value !== "string") return void 0;
|
|
9930
10221
|
const normalized = value.trim();
|
|
9931
10222
|
return normalized.length ? normalized : void 0;
|
|
@@ -10200,24 +10491,22 @@ var OpensteerCuaAgentHandler = class {
|
|
|
10200
10491
|
page;
|
|
10201
10492
|
config;
|
|
10202
10493
|
client;
|
|
10203
|
-
|
|
10494
|
+
cursorController;
|
|
10204
10495
|
onMutatingAction;
|
|
10205
|
-
cursorOverlayInjected = false;
|
|
10206
10496
|
constructor(options) {
|
|
10207
10497
|
this.page = options.page;
|
|
10208
10498
|
this.config = options.config;
|
|
10209
10499
|
this.client = options.client;
|
|
10210
|
-
this.
|
|
10500
|
+
this.cursorController = options.cursorController;
|
|
10211
10501
|
this.onMutatingAction = options.onMutatingAction;
|
|
10212
10502
|
}
|
|
10213
10503
|
async execute(options) {
|
|
10214
10504
|
const instruction = options.instruction;
|
|
10215
10505
|
const maxSteps = options.maxSteps ?? 20;
|
|
10216
10506
|
await this.initializeClient();
|
|
10217
|
-
const highlightCursor = options.highlightCursor === true;
|
|
10218
10507
|
this.client.setActionHandler(async (action) => {
|
|
10219
|
-
if (
|
|
10220
|
-
await this.
|
|
10508
|
+
if (this.cursorController.isEnabled()) {
|
|
10509
|
+
await this.maybePreviewCursor(action);
|
|
10221
10510
|
}
|
|
10222
10511
|
await executeAgentAction(this.page, action);
|
|
10223
10512
|
this.client.setCurrentUrl(this.page.url());
|
|
@@ -10248,6 +10537,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
10248
10537
|
const viewport = await this.resolveViewport();
|
|
10249
10538
|
this.client.setViewport(viewport.width, viewport.height);
|
|
10250
10539
|
this.client.setCurrentUrl(this.page.url());
|
|
10540
|
+
await this.cursorController.attachPage(this.page);
|
|
10251
10541
|
this.client.setScreenshotProvider(async () => {
|
|
10252
10542
|
const buffer = await this.page.screenshot({
|
|
10253
10543
|
fullPage: false,
|
|
@@ -10276,51 +10566,611 @@ var OpensteerCuaAgentHandler = class {
|
|
|
10276
10566
|
}
|
|
10277
10567
|
return DEFAULT_CUA_VIEWPORT;
|
|
10278
10568
|
}
|
|
10279
|
-
async
|
|
10569
|
+
async maybePreviewCursor(action) {
|
|
10280
10570
|
const x = typeof action.x === "number" ? action.x : null;
|
|
10281
10571
|
const y = typeof action.y === "number" ? action.y : null;
|
|
10282
10572
|
if (x == null || y == null) {
|
|
10283
10573
|
return;
|
|
10284
10574
|
}
|
|
10575
|
+
await this.cursorController.preview({ x, y }, "agent");
|
|
10576
|
+
}
|
|
10577
|
+
};
|
|
10578
|
+
function sleep4(ms) {
|
|
10579
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
10580
|
+
}
|
|
10581
|
+
|
|
10582
|
+
// src/cursor/motion.ts
|
|
10583
|
+
var DEFAULT_SNAPPY_OPTIONS = {
|
|
10584
|
+
minDurationMs: 46,
|
|
10585
|
+
maxDurationMs: 170,
|
|
10586
|
+
maxPoints: 14
|
|
10587
|
+
};
|
|
10588
|
+
function planSnappyCursorMotion(from, to, options = {}) {
|
|
10589
|
+
const resolved = {
|
|
10590
|
+
...DEFAULT_SNAPPY_OPTIONS,
|
|
10591
|
+
...options
|
|
10592
|
+
};
|
|
10593
|
+
const dx = to.x - from.x;
|
|
10594
|
+
const dy = to.y - from.y;
|
|
10595
|
+
const distance = Math.hypot(dx, dy);
|
|
10596
|
+
if (distance < 5) {
|
|
10597
|
+
return {
|
|
10598
|
+
points: [roundPoint(to)],
|
|
10599
|
+
stepDelayMs: 0
|
|
10600
|
+
};
|
|
10601
|
+
}
|
|
10602
|
+
const durationMs = clamp(
|
|
10603
|
+
44 + distance * 0.26,
|
|
10604
|
+
resolved.minDurationMs,
|
|
10605
|
+
resolved.maxDurationMs
|
|
10606
|
+
);
|
|
10607
|
+
const rawPoints = clamp(
|
|
10608
|
+
Math.round(durationMs / 13),
|
|
10609
|
+
4,
|
|
10610
|
+
resolved.maxPoints
|
|
10611
|
+
);
|
|
10612
|
+
const sign = deterministicBendSign(from, to);
|
|
10613
|
+
const nx = -dy / distance;
|
|
10614
|
+
const ny = dx / distance;
|
|
10615
|
+
const bend = clamp(distance * 0.12, 8, 28) * sign;
|
|
10616
|
+
const c1 = {
|
|
10617
|
+
x: from.x + dx * 0.28 + nx * bend,
|
|
10618
|
+
y: from.y + dy * 0.28 + ny * bend
|
|
10619
|
+
};
|
|
10620
|
+
const c2 = {
|
|
10621
|
+
x: from.x + dx * 0.74 + nx * bend * 0.58,
|
|
10622
|
+
y: from.y + dy * 0.74 + ny * bend * 0.58
|
|
10623
|
+
};
|
|
10624
|
+
const points = [];
|
|
10625
|
+
for (let i = 1; i <= rawPoints; i += 1) {
|
|
10626
|
+
const t = i / rawPoints;
|
|
10627
|
+
const sampled = cubicBezier(from, c1, c2, to, t);
|
|
10628
|
+
if (i !== rawPoints) {
|
|
10629
|
+
const previous = points[points.length - 1];
|
|
10630
|
+
if (previous && distanceBetween(previous, sampled) < 1.4) {
|
|
10631
|
+
continue;
|
|
10632
|
+
}
|
|
10633
|
+
}
|
|
10634
|
+
points.push(roundPoint(sampled));
|
|
10635
|
+
}
|
|
10636
|
+
if (distance > 220) {
|
|
10637
|
+
const settle = {
|
|
10638
|
+
x: to.x - dx / distance,
|
|
10639
|
+
y: to.y - dy / distance
|
|
10640
|
+
};
|
|
10641
|
+
const last = points[points.length - 1];
|
|
10642
|
+
if (!last || distanceBetween(last, settle) > 1.2) {
|
|
10643
|
+
points.splice(Math.max(0, points.length - 1), 0, roundPoint(settle));
|
|
10644
|
+
}
|
|
10645
|
+
}
|
|
10646
|
+
const deduped = dedupeAdjacent(points);
|
|
10647
|
+
const stepDelayMs = deduped.length > 1 ? Math.round(durationMs / deduped.length) : 0;
|
|
10648
|
+
return {
|
|
10649
|
+
points: deduped.length ? deduped : [roundPoint(to)],
|
|
10650
|
+
stepDelayMs
|
|
10651
|
+
};
|
|
10652
|
+
}
|
|
10653
|
+
function cubicBezier(p0, p1, p2, p3, t) {
|
|
10654
|
+
const inv = 1 - t;
|
|
10655
|
+
const inv2 = inv * inv;
|
|
10656
|
+
const inv3 = inv2 * inv;
|
|
10657
|
+
const t2 = t * t;
|
|
10658
|
+
const t3 = t2 * t;
|
|
10659
|
+
return {
|
|
10660
|
+
x: inv3 * p0.x + 3 * inv2 * t * p1.x + 3 * inv * t2 * p2.x + t3 * p3.x,
|
|
10661
|
+
y: inv3 * p0.y + 3 * inv2 * t * p1.y + 3 * inv * t2 * p2.y + t3 * p3.y
|
|
10662
|
+
};
|
|
10663
|
+
}
|
|
10664
|
+
function deterministicBendSign(from, to) {
|
|
10665
|
+
const seed = Math.sin(
|
|
10666
|
+
from.x * 12.9898 + from.y * 78.233 + to.x * 37.719 + to.y * 19.113
|
|
10667
|
+
) * 43758.5453;
|
|
10668
|
+
const fractional = seed - Math.floor(seed);
|
|
10669
|
+
return fractional >= 0.5 ? 1 : -1;
|
|
10670
|
+
}
|
|
10671
|
+
function roundPoint(point) {
|
|
10672
|
+
return {
|
|
10673
|
+
x: Math.round(point.x * 100) / 100,
|
|
10674
|
+
y: Math.round(point.y * 100) / 100
|
|
10675
|
+
};
|
|
10676
|
+
}
|
|
10677
|
+
function distanceBetween(a, b) {
|
|
10678
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
10679
|
+
}
|
|
10680
|
+
function dedupeAdjacent(points) {
|
|
10681
|
+
const out = [];
|
|
10682
|
+
for (const point of points) {
|
|
10683
|
+
const last = out[out.length - 1];
|
|
10684
|
+
if (last && last.x === point.x && last.y === point.y) {
|
|
10685
|
+
continue;
|
|
10686
|
+
}
|
|
10687
|
+
out.push(point);
|
|
10688
|
+
}
|
|
10689
|
+
return out;
|
|
10690
|
+
}
|
|
10691
|
+
function clamp(value, min, max) {
|
|
10692
|
+
return Math.min(max, Math.max(min, value));
|
|
10693
|
+
}
|
|
10694
|
+
|
|
10695
|
+
// src/cursor/renderers/svg-overlay.ts
|
|
10696
|
+
var PULSE_DURATION_MS = 220;
|
|
10697
|
+
var HOST_ELEMENT_ID = "__os_cr";
|
|
10698
|
+
var SvgCursorRenderer = class {
|
|
10699
|
+
page = null;
|
|
10700
|
+
active = false;
|
|
10701
|
+
reason = "disabled";
|
|
10702
|
+
lastMessage;
|
|
10703
|
+
async initialize(page) {
|
|
10704
|
+
this.page = page;
|
|
10705
|
+
if (page.isClosed()) {
|
|
10706
|
+
this.markInactive("page_closed");
|
|
10707
|
+
return;
|
|
10708
|
+
}
|
|
10709
|
+
try {
|
|
10710
|
+
await page.evaluate(injectCursor, HOST_ELEMENT_ID);
|
|
10711
|
+
this.active = true;
|
|
10712
|
+
this.reason = void 0;
|
|
10713
|
+
this.lastMessage = void 0;
|
|
10714
|
+
} catch (error) {
|
|
10715
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10716
|
+
this.markInactive("renderer_error", message);
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
isActive() {
|
|
10720
|
+
return this.active;
|
|
10721
|
+
}
|
|
10722
|
+
status() {
|
|
10723
|
+
return {
|
|
10724
|
+
enabled: true,
|
|
10725
|
+
active: this.active,
|
|
10726
|
+
reason: this.reason ? this.lastMessage ? `${this.reason}: ${this.lastMessage}` : this.reason : void 0
|
|
10727
|
+
};
|
|
10728
|
+
}
|
|
10729
|
+
async move(point, style) {
|
|
10730
|
+
if (!this.active || !this.page || this.page.isClosed()) return;
|
|
10285
10731
|
try {
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10732
|
+
const ok = await this.page.evaluate(moveCursor, {
|
|
10733
|
+
id: HOST_ELEMENT_ID,
|
|
10734
|
+
x: point.x,
|
|
10735
|
+
y: point.y,
|
|
10736
|
+
size: style.size,
|
|
10737
|
+
fill: colorToRgba(style.fillColor),
|
|
10738
|
+
outline: colorToRgba(style.outlineColor)
|
|
10739
|
+
});
|
|
10740
|
+
if (!ok) {
|
|
10741
|
+
await this.reinject();
|
|
10742
|
+
await this.page.evaluate(moveCursor, {
|
|
10743
|
+
id: HOST_ELEMENT_ID,
|
|
10744
|
+
x: point.x,
|
|
10745
|
+
y: point.y,
|
|
10746
|
+
size: style.size,
|
|
10747
|
+
fill: colorToRgba(style.fillColor),
|
|
10748
|
+
outline: colorToRgba(style.outlineColor)
|
|
10303
10749
|
});
|
|
10304
|
-
this.cursorOverlayInjected = true;
|
|
10305
10750
|
}
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10751
|
+
} catch (error) {
|
|
10752
|
+
this.handleError(error);
|
|
10753
|
+
}
|
|
10754
|
+
}
|
|
10755
|
+
async pulse(point, style) {
|
|
10756
|
+
if (!this.active || !this.page || this.page.isClosed()) return;
|
|
10757
|
+
try {
|
|
10758
|
+
const ok = await this.page.evaluate(pulseCursor, {
|
|
10759
|
+
id: HOST_ELEMENT_ID,
|
|
10760
|
+
x: point.x,
|
|
10761
|
+
y: point.y,
|
|
10762
|
+
size: style.size,
|
|
10763
|
+
fill: colorToRgba(style.fillColor),
|
|
10764
|
+
outline: colorToRgba(style.outlineColor),
|
|
10765
|
+
halo: colorToRgba(style.haloColor),
|
|
10766
|
+
pulseMs: PULSE_DURATION_MS
|
|
10767
|
+
});
|
|
10768
|
+
if (!ok) {
|
|
10769
|
+
await this.reinject();
|
|
10770
|
+
await this.page.evaluate(pulseCursor, {
|
|
10771
|
+
id: HOST_ELEMENT_ID,
|
|
10772
|
+
x: point.x,
|
|
10773
|
+
y: point.y,
|
|
10774
|
+
size: style.size,
|
|
10775
|
+
fill: colorToRgba(style.fillColor),
|
|
10776
|
+
outline: colorToRgba(style.outlineColor),
|
|
10777
|
+
halo: colorToRgba(style.haloColor),
|
|
10778
|
+
pulseMs: PULSE_DURATION_MS
|
|
10779
|
+
});
|
|
10780
|
+
}
|
|
10781
|
+
} catch (error) {
|
|
10782
|
+
this.handleError(error);
|
|
10783
|
+
}
|
|
10784
|
+
}
|
|
10785
|
+
async clear() {
|
|
10786
|
+
if (!this.page || this.page.isClosed()) return;
|
|
10787
|
+
try {
|
|
10788
|
+
await this.page.evaluate(removeCursor, HOST_ELEMENT_ID);
|
|
10789
|
+
} catch {
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
async dispose() {
|
|
10793
|
+
if (this.page && !this.page.isClosed()) {
|
|
10794
|
+
try {
|
|
10795
|
+
await this.page.evaluate(removeCursor, HOST_ELEMENT_ID);
|
|
10796
|
+
} catch {
|
|
10797
|
+
}
|
|
10798
|
+
}
|
|
10799
|
+
this.active = false;
|
|
10800
|
+
this.reason = "disabled";
|
|
10801
|
+
this.lastMessage = void 0;
|
|
10802
|
+
this.page = null;
|
|
10803
|
+
}
|
|
10804
|
+
async reinject() {
|
|
10805
|
+
if (!this.page || this.page.isClosed()) return;
|
|
10806
|
+
try {
|
|
10807
|
+
await this.page.evaluate(injectCursor, HOST_ELEMENT_ID);
|
|
10808
|
+
} catch {
|
|
10809
|
+
}
|
|
10810
|
+
}
|
|
10811
|
+
markInactive(reason, message) {
|
|
10812
|
+
this.active = false;
|
|
10813
|
+
this.reason = reason;
|
|
10814
|
+
this.lastMessage = message;
|
|
10815
|
+
}
|
|
10816
|
+
handleError(error) {
|
|
10817
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10818
|
+
if (isPageGone(message)) {
|
|
10819
|
+
this.markInactive("page_closed", message);
|
|
10820
|
+
}
|
|
10821
|
+
}
|
|
10822
|
+
};
|
|
10823
|
+
function injectCursor(hostId) {
|
|
10824
|
+
const win = window;
|
|
10825
|
+
if (win[hostId]) return;
|
|
10826
|
+
const host = document.createElement("div");
|
|
10827
|
+
host.style.cssText = "position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;";
|
|
10828
|
+
const shadow = host.attachShadow({ mode: "closed" });
|
|
10829
|
+
const wrapper = document.createElement("div");
|
|
10830
|
+
wrapper.style.cssText = "position:fixed;top:0;left:0;pointer-events:none;will-change:transform;display:none;";
|
|
10831
|
+
wrapper.innerHTML = `
|
|
10832
|
+
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" style="filter:drop-shadow(0 1px 2px rgba(0,0,0,0.3));display:block;">
|
|
10833
|
+
<path d="M3 2L3 23L8.5 17.5L13 26L17 24L12.5 15.5L20 15.5L3 2Z"
|
|
10834
|
+
fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
|
|
10835
|
+
</svg>
|
|
10836
|
+
<div data-role="pulse" style="position:absolute;top:0;left:0;width:24px;height:24px;border-radius:50%;pointer-events:none;opacity:0;transform:translate(-8px,-8px);"></div>
|
|
10837
|
+
`;
|
|
10838
|
+
shadow.appendChild(wrapper);
|
|
10839
|
+
document.documentElement.appendChild(host);
|
|
10840
|
+
Object.defineProperty(window, hostId, {
|
|
10841
|
+
value: {
|
|
10842
|
+
host,
|
|
10843
|
+
wrapper,
|
|
10844
|
+
path: wrapper.querySelector("path"),
|
|
10845
|
+
pulse: wrapper.querySelector('[data-role="pulse"]')
|
|
10846
|
+
},
|
|
10847
|
+
configurable: true,
|
|
10848
|
+
enumerable: false
|
|
10849
|
+
});
|
|
10850
|
+
}
|
|
10851
|
+
function moveCursor(args) {
|
|
10852
|
+
const refs = window[args.id];
|
|
10853
|
+
if (!refs) return false;
|
|
10854
|
+
const scale = args.size / 20;
|
|
10855
|
+
refs.wrapper.style.transform = `translate(${args.x}px, ${args.y}px) scale(${scale})`;
|
|
10856
|
+
refs.wrapper.style.display = "block";
|
|
10857
|
+
if (refs.path) {
|
|
10858
|
+
refs.path.setAttribute("fill", args.fill);
|
|
10859
|
+
refs.path.setAttribute("stroke", args.outline);
|
|
10860
|
+
}
|
|
10861
|
+
return true;
|
|
10862
|
+
}
|
|
10863
|
+
function pulseCursor(args) {
|
|
10864
|
+
const refs = window[args.id];
|
|
10865
|
+
if (!refs) return false;
|
|
10866
|
+
const scale = args.size / 20;
|
|
10867
|
+
refs.wrapper.style.transform = `translate(${args.x}px, ${args.y}px) scale(${scale})`;
|
|
10868
|
+
refs.wrapper.style.display = "block";
|
|
10869
|
+
if (refs.path) {
|
|
10870
|
+
refs.path.setAttribute("fill", args.fill);
|
|
10871
|
+
refs.path.setAttribute("stroke", args.outline);
|
|
10872
|
+
}
|
|
10873
|
+
const ring = refs.pulse;
|
|
10874
|
+
if (!ring) return true;
|
|
10875
|
+
ring.style.background = args.halo;
|
|
10876
|
+
ring.style.opacity = "0.7";
|
|
10877
|
+
ring.style.width = "24px";
|
|
10878
|
+
ring.style.height = "24px";
|
|
10879
|
+
ring.style.transition = `all ${args.pulseMs}ms ease-out`;
|
|
10880
|
+
ring.offsetHeight;
|
|
10881
|
+
ring.style.width = "48px";
|
|
10882
|
+
ring.style.height = "48px";
|
|
10883
|
+
ring.style.opacity = "0";
|
|
10884
|
+
ring.style.transform = "translate(-20px, -20px)";
|
|
10885
|
+
setTimeout(() => {
|
|
10886
|
+
ring.style.transition = "none";
|
|
10887
|
+
ring.style.width = "24px";
|
|
10888
|
+
ring.style.height = "24px";
|
|
10889
|
+
ring.style.transform = "translate(-8px, -8px)";
|
|
10890
|
+
ring.style.opacity = "0";
|
|
10891
|
+
}, args.pulseMs);
|
|
10892
|
+
return true;
|
|
10893
|
+
}
|
|
10894
|
+
function removeCursor(hostId) {
|
|
10895
|
+
const refs = window[hostId];
|
|
10896
|
+
if (refs) {
|
|
10897
|
+
refs.host.remove();
|
|
10898
|
+
delete window[hostId];
|
|
10899
|
+
}
|
|
10900
|
+
}
|
|
10901
|
+
function colorToRgba(c) {
|
|
10902
|
+
return `rgba(${Math.round(c.r)},${Math.round(c.g)},${Math.round(c.b)},${c.a})`;
|
|
10903
|
+
}
|
|
10904
|
+
function isPageGone(message) {
|
|
10905
|
+
const m = message.toLowerCase();
|
|
10906
|
+
return m.includes("closed") || m.includes("detached") || m.includes("destroyed") || m.includes("target");
|
|
10907
|
+
}
|
|
10908
|
+
|
|
10909
|
+
// src/cursor/controller.ts
|
|
10910
|
+
var DEFAULT_STYLE = {
|
|
10911
|
+
size: 20,
|
|
10912
|
+
fillColor: {
|
|
10913
|
+
r: 255,
|
|
10914
|
+
g: 255,
|
|
10915
|
+
b: 255,
|
|
10916
|
+
a: 0.96
|
|
10917
|
+
},
|
|
10918
|
+
outlineColor: {
|
|
10919
|
+
r: 0,
|
|
10920
|
+
g: 0,
|
|
10921
|
+
b: 0,
|
|
10922
|
+
a: 1
|
|
10923
|
+
},
|
|
10924
|
+
haloColor: {
|
|
10925
|
+
r: 35,
|
|
10926
|
+
g: 162,
|
|
10927
|
+
b: 255,
|
|
10928
|
+
a: 0.38
|
|
10929
|
+
},
|
|
10930
|
+
pulseScale: 2.15
|
|
10931
|
+
};
|
|
10932
|
+
var REINITIALIZE_BACKOFF_MS = 1e3;
|
|
10933
|
+
var FIRST_MOVE_CENTER_DISTANCE_THRESHOLD = 16;
|
|
10934
|
+
var FIRST_MOVE_MAX_TRAVEL = 220;
|
|
10935
|
+
var FIRST_MOVE_NEAR_TARGET_X_OFFSET = 28;
|
|
10936
|
+
var FIRST_MOVE_NEAR_TARGET_Y_OFFSET = 18;
|
|
10937
|
+
var MOTION_PLANNERS = {
|
|
10938
|
+
snappy: planSnappyCursorMotion
|
|
10939
|
+
};
|
|
10940
|
+
var CursorController = class {
|
|
10941
|
+
debug;
|
|
10942
|
+
renderer;
|
|
10943
|
+
page = null;
|
|
10944
|
+
listenerPage = null;
|
|
10945
|
+
lastPoint = null;
|
|
10946
|
+
initializedForPage = false;
|
|
10947
|
+
lastInitializeAttemptAt = 0;
|
|
10948
|
+
enabled;
|
|
10949
|
+
profile;
|
|
10950
|
+
style;
|
|
10951
|
+
onDomContentLoaded = () => {
|
|
10952
|
+
void this.restoreCursorAfterNavigation();
|
|
10953
|
+
};
|
|
10954
|
+
constructor(options = {}) {
|
|
10955
|
+
const config = options.config || {};
|
|
10956
|
+
this.debug = Boolean(options.debug);
|
|
10957
|
+
this.enabled = config.enabled === true;
|
|
10958
|
+
this.profile = config.profile ?? "snappy";
|
|
10959
|
+
this.style = mergeStyle(config.style);
|
|
10960
|
+
this.renderer = options.renderer ?? new SvgCursorRenderer();
|
|
10961
|
+
}
|
|
10962
|
+
setEnabled(enabled) {
|
|
10963
|
+
if (this.enabled && !enabled) {
|
|
10964
|
+
this.lastPoint = null;
|
|
10965
|
+
void this.clear();
|
|
10966
|
+
}
|
|
10967
|
+
this.enabled = enabled;
|
|
10968
|
+
}
|
|
10969
|
+
isEnabled() {
|
|
10970
|
+
return this.enabled;
|
|
10971
|
+
}
|
|
10972
|
+
getStatus() {
|
|
10973
|
+
if (!this.enabled) {
|
|
10974
|
+
return {
|
|
10975
|
+
enabled: false,
|
|
10976
|
+
active: false,
|
|
10977
|
+
reason: "disabled"
|
|
10978
|
+
};
|
|
10979
|
+
}
|
|
10980
|
+
const status = this.renderer.status();
|
|
10981
|
+
if (!this.initializedForPage && !status.active) {
|
|
10982
|
+
return {
|
|
10983
|
+
enabled: true,
|
|
10984
|
+
active: false,
|
|
10985
|
+
reason: "not_initialized"
|
|
10986
|
+
};
|
|
10987
|
+
}
|
|
10988
|
+
return status;
|
|
10989
|
+
}
|
|
10990
|
+
async attachPage(page) {
|
|
10991
|
+
if (this.page !== page) {
|
|
10992
|
+
this.detachPageListeners();
|
|
10993
|
+
this.page = page;
|
|
10994
|
+
this.lastPoint = null;
|
|
10995
|
+
this.initializedForPage = false;
|
|
10996
|
+
this.lastInitializeAttemptAt = 0;
|
|
10997
|
+
}
|
|
10998
|
+
this.attachPageListeners(page);
|
|
10999
|
+
}
|
|
11000
|
+
async preview(point, intent) {
|
|
11001
|
+
if (!this.enabled || !point) return;
|
|
11002
|
+
if (!this.page || this.page.isClosed()) return;
|
|
11003
|
+
try {
|
|
11004
|
+
await this.ensureInitialized();
|
|
11005
|
+
if (!this.renderer.isActive()) {
|
|
11006
|
+
await this.reinitializeIfEligible();
|
|
11007
|
+
}
|
|
11008
|
+
if (!this.renderer.isActive()) return;
|
|
11009
|
+
const start = this.resolveMotionStart(point);
|
|
11010
|
+
const motion = this.planMotion(start, point);
|
|
11011
|
+
for (const step of motion.points) {
|
|
11012
|
+
await this.renderer.move(step, this.style);
|
|
11013
|
+
if (motion.stepDelayMs > 0) {
|
|
11014
|
+
await sleep5(motion.stepDelayMs);
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
if (shouldPulse(intent)) {
|
|
11018
|
+
await this.renderer.pulse(point, this.style);
|
|
11019
|
+
}
|
|
11020
|
+
this.lastPoint = point;
|
|
11021
|
+
} catch (error) {
|
|
11022
|
+
if (this.debug) {
|
|
11023
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11024
|
+
console.warn(`[opensteer] cursor preview failed: ${message}`);
|
|
11025
|
+
}
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
async clear() {
|
|
11029
|
+
try {
|
|
11030
|
+
await this.renderer.clear();
|
|
11031
|
+
} catch (error) {
|
|
11032
|
+
if (this.debug) {
|
|
11033
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11034
|
+
console.warn(`[opensteer] cursor clear failed: ${message}`);
|
|
11035
|
+
}
|
|
11036
|
+
}
|
|
11037
|
+
}
|
|
11038
|
+
async dispose() {
|
|
11039
|
+
this.detachPageListeners();
|
|
11040
|
+
this.lastPoint = null;
|
|
11041
|
+
this.initializedForPage = false;
|
|
11042
|
+
this.lastInitializeAttemptAt = 0;
|
|
11043
|
+
this.page = null;
|
|
11044
|
+
await this.renderer.dispose();
|
|
11045
|
+
}
|
|
11046
|
+
async ensureInitialized() {
|
|
11047
|
+
if (!this.page || this.page.isClosed()) return;
|
|
11048
|
+
if (this.initializedForPage) return;
|
|
11049
|
+
await this.initializeRenderer();
|
|
11050
|
+
}
|
|
11051
|
+
attachPageListeners(page) {
|
|
11052
|
+
if (this.listenerPage === page) {
|
|
11053
|
+
return;
|
|
11054
|
+
}
|
|
11055
|
+
this.detachPageListeners();
|
|
11056
|
+
page.on("domcontentloaded", this.onDomContentLoaded);
|
|
11057
|
+
this.listenerPage = page;
|
|
11058
|
+
}
|
|
11059
|
+
detachPageListeners() {
|
|
11060
|
+
if (!this.listenerPage) {
|
|
11061
|
+
return;
|
|
11062
|
+
}
|
|
11063
|
+
this.listenerPage.off("domcontentloaded", this.onDomContentLoaded);
|
|
11064
|
+
this.listenerPage = null;
|
|
11065
|
+
}
|
|
11066
|
+
planMotion(from, to) {
|
|
11067
|
+
return MOTION_PLANNERS[this.profile](from, to);
|
|
11068
|
+
}
|
|
11069
|
+
async reinitializeIfEligible() {
|
|
11070
|
+
if (!this.page || this.page.isClosed()) return;
|
|
11071
|
+
const elapsed = Date.now() - this.lastInitializeAttemptAt;
|
|
11072
|
+
if (elapsed < REINITIALIZE_BACKOFF_MS) return;
|
|
11073
|
+
await this.initializeRenderer();
|
|
11074
|
+
}
|
|
11075
|
+
async initializeRenderer() {
|
|
11076
|
+
if (!this.page || this.page.isClosed()) return;
|
|
11077
|
+
this.lastInitializeAttemptAt = Date.now();
|
|
11078
|
+
await this.renderer.initialize(this.page);
|
|
11079
|
+
this.initializedForPage = true;
|
|
11080
|
+
}
|
|
11081
|
+
async restoreCursorAfterNavigation() {
|
|
11082
|
+
if (!this.enabled || !this.lastPoint) return;
|
|
11083
|
+
if (!this.page || this.page.isClosed()) return;
|
|
11084
|
+
try {
|
|
11085
|
+
if (!this.renderer.isActive()) {
|
|
11086
|
+
await this.reinitializeIfEligible();
|
|
11087
|
+
}
|
|
11088
|
+
if (!this.renderer.isActive()) {
|
|
11089
|
+
return;
|
|
11090
|
+
}
|
|
11091
|
+
await this.renderer.move(this.lastPoint, this.style);
|
|
10314
11092
|
} catch (error) {
|
|
10315
11093
|
if (this.debug) {
|
|
10316
11094
|
const message = error instanceof Error ? error.message : String(error);
|
|
10317
|
-
console.warn(
|
|
11095
|
+
console.warn(
|
|
11096
|
+
`[opensteer] cursor restore after navigation failed: ${message}`
|
|
11097
|
+
);
|
|
11098
|
+
}
|
|
11099
|
+
}
|
|
11100
|
+
}
|
|
11101
|
+
resolveMotionStart(target) {
|
|
11102
|
+
if (this.lastPoint) {
|
|
11103
|
+
return this.lastPoint;
|
|
11104
|
+
}
|
|
11105
|
+
const viewport = this.page?.viewportSize();
|
|
11106
|
+
if (!viewport?.width || !viewport?.height) {
|
|
11107
|
+
return target;
|
|
11108
|
+
}
|
|
11109
|
+
const centerPoint = {
|
|
11110
|
+
x: viewport.width / 2,
|
|
11111
|
+
y: viewport.height / 2
|
|
11112
|
+
};
|
|
11113
|
+
if (distanceBetween2(centerPoint, target) > FIRST_MOVE_CENTER_DISTANCE_THRESHOLD) {
|
|
11114
|
+
const dx = target.x - centerPoint.x;
|
|
11115
|
+
const dy = target.y - centerPoint.y;
|
|
11116
|
+
const distance = Math.hypot(dx, dy);
|
|
11117
|
+
if (distance > FIRST_MOVE_MAX_TRAVEL) {
|
|
11118
|
+
const ux = dx / distance;
|
|
11119
|
+
const uy = dy / distance;
|
|
11120
|
+
return {
|
|
11121
|
+
x: target.x - ux * FIRST_MOVE_MAX_TRAVEL,
|
|
11122
|
+
y: target.y - uy * FIRST_MOVE_MAX_TRAVEL
|
|
11123
|
+
};
|
|
10318
11124
|
}
|
|
11125
|
+
return centerPoint;
|
|
10319
11126
|
}
|
|
11127
|
+
return {
|
|
11128
|
+
x: clamp2(target.x - FIRST_MOVE_NEAR_TARGET_X_OFFSET, 0, viewport.width),
|
|
11129
|
+
y: clamp2(target.y - FIRST_MOVE_NEAR_TARGET_Y_OFFSET, 0, viewport.height)
|
|
11130
|
+
};
|
|
10320
11131
|
}
|
|
10321
11132
|
};
|
|
10322
|
-
function
|
|
10323
|
-
return
|
|
11133
|
+
function mergeStyle(style) {
|
|
11134
|
+
return {
|
|
11135
|
+
size: normalizeFinite(style?.size, DEFAULT_STYLE.size, 4, 48),
|
|
11136
|
+
pulseScale: normalizeFinite(
|
|
11137
|
+
style?.pulseScale,
|
|
11138
|
+
DEFAULT_STYLE.pulseScale,
|
|
11139
|
+
1,
|
|
11140
|
+
3
|
|
11141
|
+
),
|
|
11142
|
+
fillColor: normalizeColor(style?.fillColor, DEFAULT_STYLE.fillColor),
|
|
11143
|
+
outlineColor: normalizeColor(
|
|
11144
|
+
style?.outlineColor,
|
|
11145
|
+
DEFAULT_STYLE.outlineColor
|
|
11146
|
+
),
|
|
11147
|
+
haloColor: normalizeColor(style?.haloColor, DEFAULT_STYLE.haloColor)
|
|
11148
|
+
};
|
|
11149
|
+
}
|
|
11150
|
+
function normalizeColor(color, fallback) {
|
|
11151
|
+
if (!color) return { ...fallback };
|
|
11152
|
+
return {
|
|
11153
|
+
r: normalizeFinite(color.r, fallback.r, 0, 255),
|
|
11154
|
+
g: normalizeFinite(color.g, fallback.g, 0, 255),
|
|
11155
|
+
b: normalizeFinite(color.b, fallback.b, 0, 255),
|
|
11156
|
+
a: normalizeFinite(color.a, fallback.a, 0, 1)
|
|
11157
|
+
};
|
|
11158
|
+
}
|
|
11159
|
+
function normalizeFinite(value, fallback, min, max) {
|
|
11160
|
+
const numeric = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
11161
|
+
return Math.min(max, Math.max(min, numeric));
|
|
11162
|
+
}
|
|
11163
|
+
function distanceBetween2(a, b) {
|
|
11164
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
11165
|
+
}
|
|
11166
|
+
function clamp2(value, min, max) {
|
|
11167
|
+
return Math.min(max, Math.max(min, value));
|
|
11168
|
+
}
|
|
11169
|
+
function shouldPulse(intent) {
|
|
11170
|
+
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
11171
|
+
}
|
|
11172
|
+
function sleep5(ms) {
|
|
11173
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10324
11174
|
}
|
|
10325
11175
|
|
|
10326
11176
|
// src/opensteer.ts
|
|
@@ -10349,6 +11199,7 @@ var Opensteer = class _Opensteer {
|
|
|
10349
11199
|
ownsBrowser = false;
|
|
10350
11200
|
snapshotCache = null;
|
|
10351
11201
|
agentExecutionInFlight = false;
|
|
11202
|
+
cursorController = null;
|
|
10352
11203
|
constructor(config = {}) {
|
|
10353
11204
|
const resolvedRuntime = resolveConfigWithEnv(config);
|
|
10354
11205
|
const resolved = resolvedRuntime.config;
|
|
@@ -10370,15 +11221,29 @@ var Opensteer = class _Opensteer {
|
|
|
10370
11221
|
if (cloudSelection.cloud) {
|
|
10371
11222
|
const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
|
|
10372
11223
|
const apiKey = cloudConfig?.apiKey?.trim();
|
|
10373
|
-
|
|
11224
|
+
const accessToken = cloudConfig?.accessToken?.trim();
|
|
11225
|
+
if (apiKey && accessToken) {
|
|
11226
|
+
throw new Error(
|
|
11227
|
+
"Cloud mode cannot use both cloud.apiKey and cloud.accessToken. Set only one credential."
|
|
11228
|
+
);
|
|
11229
|
+
}
|
|
11230
|
+
let credential = "";
|
|
11231
|
+
let authScheme = cloudConfig?.authScheme ?? "api-key";
|
|
11232
|
+
if (accessToken) {
|
|
11233
|
+
credential = accessToken;
|
|
11234
|
+
authScheme = "bearer";
|
|
11235
|
+
} else if (apiKey) {
|
|
11236
|
+
credential = apiKey;
|
|
11237
|
+
}
|
|
11238
|
+
if (!credential) {
|
|
10374
11239
|
throw new Error(
|
|
10375
|
-
"Cloud mode requires
|
|
11240
|
+
"Cloud mode requires credentials via cloud.apiKey/cloud.accessToken or OPENSTEER_API_KEY/OPENSTEER_ACCESS_TOKEN."
|
|
10376
11241
|
);
|
|
10377
11242
|
}
|
|
10378
11243
|
this.cloud = createCloudRuntimeState(
|
|
10379
|
-
|
|
11244
|
+
credential,
|
|
10380
11245
|
cloudConfig?.baseUrl,
|
|
10381
|
-
|
|
11246
|
+
authScheme
|
|
10382
11247
|
);
|
|
10383
11248
|
} else {
|
|
10384
11249
|
this.cloud = null;
|
|
@@ -10603,6 +11468,19 @@ var Opensteer = class _Opensteer {
|
|
|
10603
11468
|
}
|
|
10604
11469
|
return true;
|
|
10605
11470
|
}
|
|
11471
|
+
buildCloudSessionLaunchConfig(options) {
|
|
11472
|
+
const cloudConfig = this.config.cloud && typeof this.config.cloud === "object" ? this.config.cloud : void 0;
|
|
11473
|
+
const browserProfile = normalizeCloudBrowserProfilePreference(
|
|
11474
|
+
options.cloudBrowserProfile ?? cloudConfig?.browserProfile,
|
|
11475
|
+
options.cloudBrowserProfile ? "launch options" : "Opensteer config"
|
|
11476
|
+
);
|
|
11477
|
+
if (!browserProfile) {
|
|
11478
|
+
return void 0;
|
|
11479
|
+
}
|
|
11480
|
+
return {
|
|
11481
|
+
browserProfile
|
|
11482
|
+
};
|
|
11483
|
+
}
|
|
10606
11484
|
async launch(options = {}) {
|
|
10607
11485
|
if (this.pageRef && !this.ownsBrowser) {
|
|
10608
11486
|
throw new Error(
|
|
@@ -10625,6 +11503,7 @@ var Opensteer = class _Opensteer {
|
|
|
10625
11503
|
}
|
|
10626
11504
|
localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
|
|
10627
11505
|
this.cloud.localRunId = localRunId;
|
|
11506
|
+
const launchConfig = this.buildCloudSessionLaunchConfig(options);
|
|
10628
11507
|
const session3 = await this.cloud.sessionClient.create({
|
|
10629
11508
|
cloudSessionContractVersion,
|
|
10630
11509
|
sourceType: "local-cloud",
|
|
@@ -10632,7 +11511,8 @@ var Opensteer = class _Opensteer {
|
|
|
10632
11511
|
localRunId,
|
|
10633
11512
|
name: this.namespace,
|
|
10634
11513
|
model: this.config.model,
|
|
10635
|
-
launchContext: options.context || void 0
|
|
11514
|
+
launchContext: options.context || void 0,
|
|
11515
|
+
launchConfig
|
|
10636
11516
|
});
|
|
10637
11517
|
sessionId = session3.sessionId;
|
|
10638
11518
|
actionClient = await ActionWsClient.connect({
|
|
@@ -10650,6 +11530,9 @@ var Opensteer = class _Opensteer {
|
|
|
10650
11530
|
this.pageRef = cdpConnection.page;
|
|
10651
11531
|
this.ownsBrowser = true;
|
|
10652
11532
|
this.snapshotCache = null;
|
|
11533
|
+
if (this.cursorController) {
|
|
11534
|
+
await this.cursorController.attachPage(this.pageRef);
|
|
11535
|
+
}
|
|
10653
11536
|
this.cloud.actionClient = actionClient;
|
|
10654
11537
|
this.cloud.sessionId = sessionId;
|
|
10655
11538
|
this.cloud.cloudSessionUrl = session3.cloudSessionUrl;
|
|
@@ -10690,6 +11573,9 @@ var Opensteer = class _Opensteer {
|
|
|
10690
11573
|
this.pageRef = session2.page;
|
|
10691
11574
|
this.ownsBrowser = true;
|
|
10692
11575
|
this.snapshotCache = null;
|
|
11576
|
+
if (this.cursorController) {
|
|
11577
|
+
await this.cursorController.attachPage(this.pageRef);
|
|
11578
|
+
}
|
|
10693
11579
|
}
|
|
10694
11580
|
static from(page, config = {}) {
|
|
10695
11581
|
const resolvedRuntime = resolveConfigWithEnv(config);
|
|
@@ -10734,6 +11620,9 @@ var Opensteer = class _Opensteer {
|
|
|
10734
11620
|
if (sessionId) {
|
|
10735
11621
|
await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
|
|
10736
11622
|
}
|
|
11623
|
+
if (this.cursorController) {
|
|
11624
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
11625
|
+
}
|
|
10737
11626
|
return;
|
|
10738
11627
|
}
|
|
10739
11628
|
if (this.ownsBrowser) {
|
|
@@ -10743,6 +11632,9 @@ var Opensteer = class _Opensteer {
|
|
|
10743
11632
|
this.pageRef = null;
|
|
10744
11633
|
this.contextRef = null;
|
|
10745
11634
|
this.ownsBrowser = false;
|
|
11635
|
+
if (this.cursorController) {
|
|
11636
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
11637
|
+
}
|
|
10746
11638
|
}
|
|
10747
11639
|
async syncLocalSelectorCacheToCloud() {
|
|
10748
11640
|
if (!this.cloud) return;
|
|
@@ -10869,12 +11761,22 @@ var Opensteer = class _Opensteer {
|
|
|
10869
11761
|
resolution.counter
|
|
10870
11762
|
);
|
|
10871
11763
|
}
|
|
10872
|
-
await this.
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
11764
|
+
await this.runWithCursorPreview(
|
|
11765
|
+
() => this.resolveHandleTargetPoint(handle, options.position),
|
|
11766
|
+
"hover",
|
|
11767
|
+
async () => {
|
|
11768
|
+
await this.runWithPostActionWait(
|
|
11769
|
+
"hover",
|
|
11770
|
+
options.wait,
|
|
11771
|
+
async () => {
|
|
11772
|
+
await handle.hover({
|
|
11773
|
+
force: options.force,
|
|
11774
|
+
position: options.position
|
|
11775
|
+
});
|
|
11776
|
+
}
|
|
11777
|
+
);
|
|
11778
|
+
}
|
|
11779
|
+
);
|
|
10878
11780
|
} catch (err) {
|
|
10879
11781
|
const failure = classifyActionFailure({
|
|
10880
11782
|
action: "hover",
|
|
@@ -10909,25 +11811,31 @@ var Opensteer = class _Opensteer {
|
|
|
10909
11811
|
throw new Error("Unable to resolve element path for hover action.");
|
|
10910
11812
|
}
|
|
10911
11813
|
const path5 = resolution.path;
|
|
10912
|
-
const result = await this.
|
|
11814
|
+
const result = await this.runWithCursorPreview(
|
|
11815
|
+
() => this.resolvePathTargetPoint(path5, options.position),
|
|
10913
11816
|
"hover",
|
|
10914
|
-
options.wait,
|
|
10915
11817
|
async () => {
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
11818
|
+
return await this.runWithPostActionWait(
|
|
11819
|
+
"hover",
|
|
11820
|
+
options.wait,
|
|
11821
|
+
async () => {
|
|
11822
|
+
const actionResult = await performHover(this.page, path5, options);
|
|
11823
|
+
if (!actionResult.ok) {
|
|
11824
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
11825
|
+
action: "hover",
|
|
11826
|
+
error: actionResult.error || defaultActionFailureMessage("hover"),
|
|
11827
|
+
fallbackMessage: defaultActionFailureMessage("hover")
|
|
11828
|
+
});
|
|
11829
|
+
throw this.buildActionError(
|
|
11830
|
+
"hover",
|
|
11831
|
+
options.description,
|
|
11832
|
+
failure,
|
|
11833
|
+
actionResult.usedSelector || null
|
|
11834
|
+
);
|
|
11835
|
+
}
|
|
11836
|
+
return actionResult;
|
|
11837
|
+
}
|
|
11838
|
+
);
|
|
10931
11839
|
}
|
|
10932
11840
|
);
|
|
10933
11841
|
this.snapshotCache = null;
|
|
@@ -10968,16 +11876,26 @@ var Opensteer = class _Opensteer {
|
|
|
10968
11876
|
resolution.counter
|
|
10969
11877
|
);
|
|
10970
11878
|
}
|
|
10971
|
-
await this.
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
await
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
11879
|
+
await this.runWithCursorPreview(
|
|
11880
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
11881
|
+
"input",
|
|
11882
|
+
async () => {
|
|
11883
|
+
await this.runWithPostActionWait(
|
|
11884
|
+
"input",
|
|
11885
|
+
options.wait,
|
|
11886
|
+
async () => {
|
|
11887
|
+
if (options.clear !== false) {
|
|
11888
|
+
await handle.fill(options.text);
|
|
11889
|
+
} else {
|
|
11890
|
+
await handle.type(options.text);
|
|
11891
|
+
}
|
|
11892
|
+
if (options.pressEnter) {
|
|
11893
|
+
await handle.press("Enter", { noWaitAfter: true });
|
|
11894
|
+
}
|
|
11895
|
+
}
|
|
11896
|
+
);
|
|
10979
11897
|
}
|
|
10980
|
-
|
|
11898
|
+
);
|
|
10981
11899
|
} catch (err) {
|
|
10982
11900
|
const failure = classifyActionFailure({
|
|
10983
11901
|
action: "input",
|
|
@@ -11012,25 +11930,31 @@ var Opensteer = class _Opensteer {
|
|
|
11012
11930
|
throw new Error("Unable to resolve element path for input action.");
|
|
11013
11931
|
}
|
|
11014
11932
|
const path5 = resolution.path;
|
|
11015
|
-
const result = await this.
|
|
11933
|
+
const result = await this.runWithCursorPreview(
|
|
11934
|
+
() => this.resolvePathTargetPoint(path5),
|
|
11016
11935
|
"input",
|
|
11017
|
-
options.wait,
|
|
11018
11936
|
async () => {
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11937
|
+
return await this.runWithPostActionWait(
|
|
11938
|
+
"input",
|
|
11939
|
+
options.wait,
|
|
11940
|
+
async () => {
|
|
11941
|
+
const actionResult = await performInput(this.page, path5, options);
|
|
11942
|
+
if (!actionResult.ok) {
|
|
11943
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
11944
|
+
action: "input",
|
|
11945
|
+
error: actionResult.error || defaultActionFailureMessage("input"),
|
|
11946
|
+
fallbackMessage: defaultActionFailureMessage("input")
|
|
11947
|
+
});
|
|
11948
|
+
throw this.buildActionError(
|
|
11949
|
+
"input",
|
|
11950
|
+
options.description,
|
|
11951
|
+
failure,
|
|
11952
|
+
actionResult.usedSelector || null
|
|
11953
|
+
);
|
|
11954
|
+
}
|
|
11955
|
+
return actionResult;
|
|
11956
|
+
}
|
|
11957
|
+
);
|
|
11034
11958
|
}
|
|
11035
11959
|
);
|
|
11036
11960
|
this.snapshotCache = null;
|
|
@@ -11071,21 +11995,27 @@ var Opensteer = class _Opensteer {
|
|
|
11071
11995
|
resolution.counter
|
|
11072
11996
|
);
|
|
11073
11997
|
}
|
|
11074
|
-
await this.
|
|
11998
|
+
await this.runWithCursorPreview(
|
|
11999
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
11075
12000
|
"select",
|
|
11076
|
-
options.wait,
|
|
11077
12001
|
async () => {
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
12002
|
+
await this.runWithPostActionWait(
|
|
12003
|
+
"select",
|
|
12004
|
+
options.wait,
|
|
12005
|
+
async () => {
|
|
12006
|
+
if (options.value != null) {
|
|
12007
|
+
await handle.selectOption(options.value);
|
|
12008
|
+
} else if (options.label != null) {
|
|
12009
|
+
await handle.selectOption({ label: options.label });
|
|
12010
|
+
} else if (options.index != null) {
|
|
12011
|
+
await handle.selectOption({ index: options.index });
|
|
12012
|
+
} else {
|
|
12013
|
+
throw new Error(
|
|
12014
|
+
"Select requires value, label, or index."
|
|
12015
|
+
);
|
|
12016
|
+
}
|
|
12017
|
+
}
|
|
12018
|
+
);
|
|
11089
12019
|
}
|
|
11090
12020
|
);
|
|
11091
12021
|
} catch (err) {
|
|
@@ -11122,25 +12052,31 @@ var Opensteer = class _Opensteer {
|
|
|
11122
12052
|
throw new Error("Unable to resolve element path for select action.");
|
|
11123
12053
|
}
|
|
11124
12054
|
const path5 = resolution.path;
|
|
11125
|
-
const result = await this.
|
|
12055
|
+
const result = await this.runWithCursorPreview(
|
|
12056
|
+
() => this.resolvePathTargetPoint(path5),
|
|
11126
12057
|
"select",
|
|
11127
|
-
options.wait,
|
|
11128
12058
|
async () => {
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
12059
|
+
return await this.runWithPostActionWait(
|
|
12060
|
+
"select",
|
|
12061
|
+
options.wait,
|
|
12062
|
+
async () => {
|
|
12063
|
+
const actionResult = await performSelect(this.page, path5, options);
|
|
12064
|
+
if (!actionResult.ok) {
|
|
12065
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
12066
|
+
action: "select",
|
|
12067
|
+
error: actionResult.error || defaultActionFailureMessage("select"),
|
|
12068
|
+
fallbackMessage: defaultActionFailureMessage("select")
|
|
12069
|
+
});
|
|
12070
|
+
throw this.buildActionError(
|
|
12071
|
+
"select",
|
|
12072
|
+
options.description,
|
|
12073
|
+
failure,
|
|
12074
|
+
actionResult.usedSelector || null
|
|
12075
|
+
);
|
|
12076
|
+
}
|
|
12077
|
+
return actionResult;
|
|
12078
|
+
}
|
|
12079
|
+
);
|
|
11144
12080
|
}
|
|
11145
12081
|
);
|
|
11146
12082
|
this.snapshotCache = null;
|
|
@@ -11182,13 +12118,23 @@ var Opensteer = class _Opensteer {
|
|
|
11182
12118
|
);
|
|
11183
12119
|
}
|
|
11184
12120
|
const delta = getScrollDelta2(options);
|
|
11185
|
-
await this.
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
12121
|
+
await this.runWithCursorPreview(
|
|
12122
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
12123
|
+
"scroll",
|
|
12124
|
+
async () => {
|
|
12125
|
+
await this.runWithPostActionWait(
|
|
12126
|
+
"scroll",
|
|
12127
|
+
options.wait,
|
|
12128
|
+
async () => {
|
|
12129
|
+
await handle.evaluate((el, value) => {
|
|
12130
|
+
if (el instanceof HTMLElement) {
|
|
12131
|
+
el.scrollBy(value.x, value.y);
|
|
12132
|
+
}
|
|
12133
|
+
}, delta);
|
|
12134
|
+
}
|
|
12135
|
+
);
|
|
12136
|
+
}
|
|
12137
|
+
);
|
|
11192
12138
|
} catch (err) {
|
|
11193
12139
|
const failure = classifyActionFailure({
|
|
11194
12140
|
action: "scroll",
|
|
@@ -11219,29 +12165,35 @@ var Opensteer = class _Opensteer {
|
|
|
11219
12165
|
`[c="${resolution.counter}"]`
|
|
11220
12166
|
);
|
|
11221
12167
|
}
|
|
11222
|
-
const result = await this.
|
|
12168
|
+
const result = await this.runWithCursorPreview(
|
|
12169
|
+
() => resolution.path ? this.resolvePathTargetPoint(resolution.path) : this.resolveViewportAnchorPoint(),
|
|
11223
12170
|
"scroll",
|
|
11224
|
-
options.wait,
|
|
11225
12171
|
async () => {
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
12172
|
+
return await this.runWithPostActionWait(
|
|
12173
|
+
"scroll",
|
|
12174
|
+
options.wait,
|
|
12175
|
+
async () => {
|
|
12176
|
+
const actionResult = await performScroll(
|
|
12177
|
+
this.page,
|
|
12178
|
+
resolution.path,
|
|
12179
|
+
options
|
|
12180
|
+
);
|
|
12181
|
+
if (!actionResult.ok) {
|
|
12182
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
12183
|
+
action: "scroll",
|
|
12184
|
+
error: actionResult.error || defaultActionFailureMessage("scroll"),
|
|
12185
|
+
fallbackMessage: defaultActionFailureMessage("scroll")
|
|
12186
|
+
});
|
|
12187
|
+
throw this.buildActionError(
|
|
12188
|
+
"scroll",
|
|
12189
|
+
options.description,
|
|
12190
|
+
failure,
|
|
12191
|
+
actionResult.usedSelector || null
|
|
12192
|
+
);
|
|
12193
|
+
}
|
|
12194
|
+
return actionResult;
|
|
12195
|
+
}
|
|
11230
12196
|
);
|
|
11231
|
-
if (!actionResult.ok) {
|
|
11232
|
-
const failure = actionResult.failure || classifyActionFailure({
|
|
11233
|
-
action: "scroll",
|
|
11234
|
-
error: actionResult.error || defaultActionFailureMessage("scroll"),
|
|
11235
|
-
fallbackMessage: defaultActionFailureMessage("scroll")
|
|
11236
|
-
});
|
|
11237
|
-
throw this.buildActionError(
|
|
11238
|
-
"scroll",
|
|
11239
|
-
options.description,
|
|
11240
|
-
failure,
|
|
11241
|
-
actionResult.usedSelector || null
|
|
11242
|
-
);
|
|
11243
|
-
}
|
|
11244
|
-
return actionResult;
|
|
11245
12197
|
}
|
|
11246
12198
|
);
|
|
11247
12199
|
this.snapshotCache = null;
|
|
@@ -11527,11 +12479,17 @@ var Opensteer = class _Opensteer {
|
|
|
11527
12479
|
resolution.counter
|
|
11528
12480
|
);
|
|
11529
12481
|
}
|
|
11530
|
-
await this.
|
|
12482
|
+
await this.runWithCursorPreview(
|
|
12483
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
11531
12484
|
"uploadFile",
|
|
11532
|
-
options.wait,
|
|
11533
12485
|
async () => {
|
|
11534
|
-
await
|
|
12486
|
+
await this.runWithPostActionWait(
|
|
12487
|
+
"uploadFile",
|
|
12488
|
+
options.wait,
|
|
12489
|
+
async () => {
|
|
12490
|
+
await handle.setInputFiles(options.paths);
|
|
12491
|
+
}
|
|
12492
|
+
);
|
|
11535
12493
|
}
|
|
11536
12494
|
);
|
|
11537
12495
|
} catch (err) {
|
|
@@ -11568,29 +12526,35 @@ var Opensteer = class _Opensteer {
|
|
|
11568
12526
|
throw new Error("Unable to resolve element path for file upload.");
|
|
11569
12527
|
}
|
|
11570
12528
|
const path5 = resolution.path;
|
|
11571
|
-
const result = await this.
|
|
12529
|
+
const result = await this.runWithCursorPreview(
|
|
12530
|
+
() => this.resolvePathTargetPoint(path5),
|
|
11572
12531
|
"uploadFile",
|
|
11573
|
-
options.wait,
|
|
11574
12532
|
async () => {
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
12533
|
+
return await this.runWithPostActionWait(
|
|
12534
|
+
"uploadFile",
|
|
12535
|
+
options.wait,
|
|
12536
|
+
async () => {
|
|
12537
|
+
const actionResult = await performFileUpload(
|
|
12538
|
+
this.page,
|
|
12539
|
+
path5,
|
|
12540
|
+
options.paths
|
|
12541
|
+
);
|
|
12542
|
+
if (!actionResult.ok) {
|
|
12543
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
12544
|
+
action: "uploadFile",
|
|
12545
|
+
error: actionResult.error || defaultActionFailureMessage("uploadFile"),
|
|
12546
|
+
fallbackMessage: defaultActionFailureMessage("uploadFile")
|
|
12547
|
+
});
|
|
12548
|
+
throw this.buildActionError(
|
|
12549
|
+
"uploadFile",
|
|
12550
|
+
options.description,
|
|
12551
|
+
failure,
|
|
12552
|
+
actionResult.usedSelector || null
|
|
12553
|
+
);
|
|
12554
|
+
}
|
|
12555
|
+
return actionResult;
|
|
12556
|
+
}
|
|
11579
12557
|
);
|
|
11580
|
-
if (!actionResult.ok) {
|
|
11581
|
-
const failure = actionResult.failure || classifyActionFailure({
|
|
11582
|
-
action: "uploadFile",
|
|
11583
|
-
error: actionResult.error || defaultActionFailureMessage("uploadFile"),
|
|
11584
|
-
fallbackMessage: defaultActionFailureMessage("uploadFile")
|
|
11585
|
-
});
|
|
11586
|
-
throw this.buildActionError(
|
|
11587
|
-
"uploadFile",
|
|
11588
|
-
options.description,
|
|
11589
|
-
failure,
|
|
11590
|
-
actionResult.usedSelector || null
|
|
11591
|
-
);
|
|
11592
|
-
}
|
|
11593
|
-
return actionResult;
|
|
11594
12558
|
}
|
|
11595
12559
|
);
|
|
11596
12560
|
this.snapshotCache = null;
|
|
@@ -11726,6 +12690,25 @@ var Opensteer = class _Opensteer {
|
|
|
11726
12690
|
getConfig() {
|
|
11727
12691
|
return this.config;
|
|
11728
12692
|
}
|
|
12693
|
+
setCursorEnabled(enabled) {
|
|
12694
|
+
this.getCursorController().setEnabled(enabled);
|
|
12695
|
+
}
|
|
12696
|
+
getCursorState() {
|
|
12697
|
+
const controller = this.cursorController;
|
|
12698
|
+
if (!controller) {
|
|
12699
|
+
return {
|
|
12700
|
+
enabled: this.config.cursor?.enabled === true,
|
|
12701
|
+
active: false,
|
|
12702
|
+
reason: this.config.cursor?.enabled === true ? "not_initialized" : "disabled"
|
|
12703
|
+
};
|
|
12704
|
+
}
|
|
12705
|
+
const status = controller.getStatus();
|
|
12706
|
+
return {
|
|
12707
|
+
enabled: status.enabled,
|
|
12708
|
+
active: status.active,
|
|
12709
|
+
reason: status.reason
|
|
12710
|
+
};
|
|
12711
|
+
}
|
|
11729
12712
|
getStorage() {
|
|
11730
12713
|
return this.storage;
|
|
11731
12714
|
}
|
|
@@ -11753,24 +12736,107 @@ var Opensteer = class _Opensteer {
|
|
|
11753
12736
|
this.agentExecutionInFlight = true;
|
|
11754
12737
|
try {
|
|
11755
12738
|
const options = normalizeExecuteOptions(instructionOrOptions);
|
|
12739
|
+
const cursorController = this.getCursorController();
|
|
12740
|
+
const previousCursorEnabled = cursorController.isEnabled();
|
|
12741
|
+
if (options.highlightCursor !== void 0) {
|
|
12742
|
+
cursorController.setEnabled(options.highlightCursor);
|
|
12743
|
+
}
|
|
11756
12744
|
const handler = new OpensteerCuaAgentHandler({
|
|
11757
12745
|
page: this.page,
|
|
11758
12746
|
config: resolvedAgentConfig,
|
|
11759
12747
|
client: createCuaClient(resolvedAgentConfig),
|
|
11760
|
-
|
|
12748
|
+
cursorController,
|
|
11761
12749
|
onMutatingAction: () => {
|
|
11762
12750
|
this.snapshotCache = null;
|
|
11763
12751
|
}
|
|
11764
12752
|
});
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
12753
|
+
try {
|
|
12754
|
+
const result = await handler.execute(options);
|
|
12755
|
+
this.snapshotCache = null;
|
|
12756
|
+
return result;
|
|
12757
|
+
} finally {
|
|
12758
|
+
if (options.highlightCursor !== void 0) {
|
|
12759
|
+
cursorController.setEnabled(previousCursorEnabled);
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
11768
12762
|
} finally {
|
|
11769
12763
|
this.agentExecutionInFlight = false;
|
|
11770
12764
|
}
|
|
11771
12765
|
}
|
|
11772
12766
|
};
|
|
11773
12767
|
}
|
|
12768
|
+
getCursorController() {
|
|
12769
|
+
if (!this.cursorController) {
|
|
12770
|
+
this.cursorController = new CursorController({
|
|
12771
|
+
config: this.config.cursor,
|
|
12772
|
+
debug: Boolean(this.config.debug)
|
|
12773
|
+
});
|
|
12774
|
+
if (this.pageRef) {
|
|
12775
|
+
void this.cursorController.attachPage(this.pageRef);
|
|
12776
|
+
}
|
|
12777
|
+
}
|
|
12778
|
+
return this.cursorController;
|
|
12779
|
+
}
|
|
12780
|
+
async runWithCursorPreview(pointResolver, intent, execute) {
|
|
12781
|
+
if (this.isCursorPreviewEnabled()) {
|
|
12782
|
+
const point = await pointResolver();
|
|
12783
|
+
await this.previewCursorPoint(point, intent);
|
|
12784
|
+
}
|
|
12785
|
+
return await execute();
|
|
12786
|
+
}
|
|
12787
|
+
isCursorPreviewEnabled() {
|
|
12788
|
+
return this.cursorController ? this.cursorController.isEnabled() : this.config.cursor?.enabled === true;
|
|
12789
|
+
}
|
|
12790
|
+
async previewCursorPoint(point, intent) {
|
|
12791
|
+
const cursor = this.getCursorController();
|
|
12792
|
+
await cursor.attachPage(this.page);
|
|
12793
|
+
await cursor.preview(point, intent);
|
|
12794
|
+
}
|
|
12795
|
+
resolveCursorPointFromBoundingBox(box, position) {
|
|
12796
|
+
if (position) {
|
|
12797
|
+
return {
|
|
12798
|
+
x: box.x + position.x,
|
|
12799
|
+
y: box.y + position.y
|
|
12800
|
+
};
|
|
12801
|
+
}
|
|
12802
|
+
return {
|
|
12803
|
+
x: box.x + box.width / 2,
|
|
12804
|
+
y: box.y + box.height / 2
|
|
12805
|
+
};
|
|
12806
|
+
}
|
|
12807
|
+
async resolveHandleTargetPoint(handle, position) {
|
|
12808
|
+
try {
|
|
12809
|
+
const box = await handle.boundingBox();
|
|
12810
|
+
if (!box) return null;
|
|
12811
|
+
return this.resolveCursorPointFromBoundingBox(box, position);
|
|
12812
|
+
} catch {
|
|
12813
|
+
return null;
|
|
12814
|
+
}
|
|
12815
|
+
}
|
|
12816
|
+
async resolvePathTargetPoint(path5, position) {
|
|
12817
|
+
if (!path5) {
|
|
12818
|
+
return null;
|
|
12819
|
+
}
|
|
12820
|
+
let resolved = null;
|
|
12821
|
+
try {
|
|
12822
|
+
resolved = await resolveElementPath(this.page, path5);
|
|
12823
|
+
return await this.resolveHandleTargetPoint(resolved.element, position);
|
|
12824
|
+
} catch {
|
|
12825
|
+
return null;
|
|
12826
|
+
} finally {
|
|
12827
|
+
await resolved?.element.dispose().catch(() => void 0);
|
|
12828
|
+
}
|
|
12829
|
+
}
|
|
12830
|
+
async resolveViewportAnchorPoint() {
|
|
12831
|
+
const viewport = this.page.viewportSize();
|
|
12832
|
+
if (viewport?.width && viewport?.height) {
|
|
12833
|
+
return {
|
|
12834
|
+
x: viewport.width / 2,
|
|
12835
|
+
y: viewport.height / 2
|
|
12836
|
+
};
|
|
12837
|
+
}
|
|
12838
|
+
return null;
|
|
12839
|
+
}
|
|
11774
12840
|
async runWithPostActionWait(action, waitOverride, execute) {
|
|
11775
12841
|
const waitSession = createPostActionWaitSession(
|
|
11776
12842
|
this.page,
|
|
@@ -11803,13 +12869,19 @@ var Opensteer = class _Opensteer {
|
|
|
11803
12869
|
resolution.counter
|
|
11804
12870
|
);
|
|
11805
12871
|
}
|
|
11806
|
-
await this.
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
|
|
12872
|
+
await this.runWithCursorPreview(
|
|
12873
|
+
() => this.resolveHandleTargetPoint(handle),
|
|
12874
|
+
method,
|
|
12875
|
+
async () => {
|
|
12876
|
+
await this.runWithPostActionWait(method, options.wait, async () => {
|
|
12877
|
+
await handle.click({
|
|
12878
|
+
button: options.button,
|
|
12879
|
+
clickCount: options.clickCount,
|
|
12880
|
+
modifiers: options.modifiers
|
|
12881
|
+
});
|
|
12882
|
+
});
|
|
12883
|
+
}
|
|
12884
|
+
);
|
|
11813
12885
|
} catch (err) {
|
|
11814
12886
|
const failure = classifyActionFailure({
|
|
11815
12887
|
action: method,
|
|
@@ -11844,25 +12916,31 @@ var Opensteer = class _Opensteer {
|
|
|
11844
12916
|
throw new Error("Unable to resolve element path for click action.");
|
|
11845
12917
|
}
|
|
11846
12918
|
const path5 = resolution.path;
|
|
11847
|
-
const result = await this.
|
|
12919
|
+
const result = await this.runWithCursorPreview(
|
|
12920
|
+
() => this.resolvePathTargetPoint(path5),
|
|
11848
12921
|
method,
|
|
11849
|
-
options.wait,
|
|
11850
12922
|
async () => {
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
12923
|
+
return await this.runWithPostActionWait(
|
|
12924
|
+
method,
|
|
12925
|
+
options.wait,
|
|
12926
|
+
async () => {
|
|
12927
|
+
const actionResult = await performClick(this.page, path5, options);
|
|
12928
|
+
if (!actionResult.ok) {
|
|
12929
|
+
const failure = actionResult.failure || classifyActionFailure({
|
|
12930
|
+
action: method,
|
|
12931
|
+
error: actionResult.error || defaultActionFailureMessage(method),
|
|
12932
|
+
fallbackMessage: defaultActionFailureMessage(method)
|
|
12933
|
+
});
|
|
12934
|
+
throw this.buildActionError(
|
|
12935
|
+
method,
|
|
12936
|
+
options.description,
|
|
12937
|
+
failure,
|
|
12938
|
+
actionResult.usedSelector || null
|
|
12939
|
+
);
|
|
12940
|
+
}
|
|
12941
|
+
return actionResult;
|
|
12942
|
+
}
|
|
12943
|
+
);
|
|
11866
12944
|
}
|
|
11867
12945
|
);
|
|
11868
12946
|
this.snapshotCache = null;
|
|
@@ -12857,6 +13935,26 @@ function isInternalOrBlankPageUrl(url) {
|
|
|
12857
13935
|
if (url === "about:blank") return true;
|
|
12858
13936
|
return url.startsWith("chrome://") || url.startsWith("devtools://") || url.startsWith("edge://");
|
|
12859
13937
|
}
|
|
13938
|
+
function normalizeCloudBrowserProfilePreference(value, source) {
|
|
13939
|
+
if (!value) {
|
|
13940
|
+
return void 0;
|
|
13941
|
+
}
|
|
13942
|
+
const profileId = typeof value.profileId === "string" ? value.profileId.trim() : "";
|
|
13943
|
+
if (!profileId) {
|
|
13944
|
+
throw new Error(
|
|
13945
|
+
`Invalid cloud browser profile in ${source}: profileId must be a non-empty string.`
|
|
13946
|
+
);
|
|
13947
|
+
}
|
|
13948
|
+
if (value.reuseIfActive !== void 0 && typeof value.reuseIfActive !== "boolean") {
|
|
13949
|
+
throw new Error(
|
|
13950
|
+
`Invalid cloud browser profile in ${source}: reuseIfActive must be a boolean.`
|
|
13951
|
+
);
|
|
13952
|
+
}
|
|
13953
|
+
return {
|
|
13954
|
+
profileId,
|
|
13955
|
+
reuseIfActive: value.reuseIfActive
|
|
13956
|
+
};
|
|
13957
|
+
}
|
|
12860
13958
|
function buildLocalRunId(namespace) {
|
|
12861
13959
|
const normalized = namespace.trim() || "default";
|
|
12862
13960
|
return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto.randomUUID)().slice(0, 8)}`;
|
|
@@ -12879,7 +13977,7 @@ function getMetadataPath(session2) {
|
|
|
12879
13977
|
}
|
|
12880
13978
|
|
|
12881
13979
|
// src/cli/commands.ts
|
|
12882
|
-
var
|
|
13980
|
+
var import_promises3 = require("fs/promises");
|
|
12883
13981
|
var commands = {
|
|
12884
13982
|
async navigate(opensteer, args) {
|
|
12885
13983
|
const url = args.url;
|
|
@@ -12911,6 +14009,21 @@ var commands = {
|
|
|
12911
14009
|
async state(opensteer) {
|
|
12912
14010
|
return await opensteer.state();
|
|
12913
14011
|
},
|
|
14012
|
+
async cursor(opensteer, args) {
|
|
14013
|
+
const mode = typeof args.mode === "string" ? args.mode : "status";
|
|
14014
|
+
if (mode === "on") {
|
|
14015
|
+
opensteer.setCursorEnabled(true);
|
|
14016
|
+
} else if (mode === "off") {
|
|
14017
|
+
opensteer.setCursorEnabled(false);
|
|
14018
|
+
} else if (mode !== "status") {
|
|
14019
|
+
throw new Error(
|
|
14020
|
+
`Invalid cursor mode "${String(mode)}". Use "on", "off", or "status".`
|
|
14021
|
+
);
|
|
14022
|
+
}
|
|
14023
|
+
return {
|
|
14024
|
+
cursor: opensteer.getCursorState()
|
|
14025
|
+
};
|
|
14026
|
+
},
|
|
12914
14027
|
async screenshot(opensteer, args) {
|
|
12915
14028
|
const file = args.file || "screenshot.png";
|
|
12916
14029
|
const type = file.endsWith(".jpg") || file.endsWith(".jpeg") ? "jpeg" : "png";
|
|
@@ -12918,7 +14031,7 @@ var commands = {
|
|
|
12918
14031
|
fullPage: args.fullPage,
|
|
12919
14032
|
type
|
|
12920
14033
|
});
|
|
12921
|
-
await (0,
|
|
14034
|
+
await (0, import_promises3.writeFile)(file, buffer);
|
|
12922
14035
|
return { file };
|
|
12923
14036
|
},
|
|
12924
14037
|
async click(opensteer, args) {
|
|
@@ -13110,10 +14223,175 @@ function getCommandHandler(name) {
|
|
|
13110
14223
|
return commands[name];
|
|
13111
14224
|
}
|
|
13112
14225
|
|
|
14226
|
+
// src/cli/cloud-profile-binding.ts
|
|
14227
|
+
function normalizeCloudProfileBinding(value) {
|
|
14228
|
+
if (!value) {
|
|
14229
|
+
return null;
|
|
14230
|
+
}
|
|
14231
|
+
const profileId = typeof value.profileId === "string" ? value.profileId.trim() : "";
|
|
14232
|
+
if (!profileId) {
|
|
14233
|
+
return null;
|
|
14234
|
+
}
|
|
14235
|
+
return {
|
|
14236
|
+
profileId,
|
|
14237
|
+
reuseIfActive: typeof value.reuseIfActive === "boolean" ? value.reuseIfActive : void 0
|
|
14238
|
+
};
|
|
14239
|
+
}
|
|
14240
|
+
function resolveConfiguredCloudProfileBinding(config) {
|
|
14241
|
+
if (!isCloudConfigured(config)) {
|
|
14242
|
+
return null;
|
|
14243
|
+
}
|
|
14244
|
+
return normalizeCloudProfileBinding(config.cloud.browserProfile);
|
|
14245
|
+
}
|
|
14246
|
+
function resolveSessionCloudProfileBinding(config, requested) {
|
|
14247
|
+
if (!isCloudConfigured(config)) {
|
|
14248
|
+
return null;
|
|
14249
|
+
}
|
|
14250
|
+
return requested ?? resolveConfiguredCloudProfileBinding(config);
|
|
14251
|
+
}
|
|
14252
|
+
function assertCompatibleCloudProfileBinding(sessionId, active, requested) {
|
|
14253
|
+
if (!requested) {
|
|
14254
|
+
return;
|
|
14255
|
+
}
|
|
14256
|
+
if (!active) {
|
|
14257
|
+
throw new Error(
|
|
14258
|
+
[
|
|
14259
|
+
`Session '${sessionId}' is already running without a bound cloud browser profile.`,
|
|
14260
|
+
"Cloud browser profile selection only applies when the session is first opened.",
|
|
14261
|
+
"Close this session or use a different --session to target another profile."
|
|
14262
|
+
].join(" ")
|
|
14263
|
+
);
|
|
14264
|
+
}
|
|
14265
|
+
if (active.profileId === requested.profileId && active.reuseIfActive === requested.reuseIfActive) {
|
|
14266
|
+
return;
|
|
14267
|
+
}
|
|
14268
|
+
throw new Error(
|
|
14269
|
+
[
|
|
14270
|
+
`Session '${sessionId}' is already bound to cloud browser profile ${formatCloudProfileBinding(active)}.`,
|
|
14271
|
+
`Requested ${formatCloudProfileBinding(requested)} does not match.`,
|
|
14272
|
+
"Use the same cloud profile for this session, or start a different --session."
|
|
14273
|
+
].join(" ")
|
|
14274
|
+
);
|
|
14275
|
+
}
|
|
14276
|
+
function formatCloudProfileBinding(binding) {
|
|
14277
|
+
if (binding.reuseIfActive === void 0) {
|
|
14278
|
+
return `'${binding.profileId}'`;
|
|
14279
|
+
}
|
|
14280
|
+
return `'${binding.profileId}' (reuseIfActive=${String(
|
|
14281
|
+
binding.reuseIfActive
|
|
14282
|
+
)})`;
|
|
14283
|
+
}
|
|
14284
|
+
function isCloudConfigured(config) {
|
|
14285
|
+
return Boolean(
|
|
14286
|
+
config.cloud && typeof config.cloud === "object" && !Array.isArray(config.cloud)
|
|
14287
|
+
);
|
|
14288
|
+
}
|
|
14289
|
+
|
|
14290
|
+
// src/cli/open-cloud-auth.ts
|
|
14291
|
+
function normalizeCliOpenCloudAuth(value) {
|
|
14292
|
+
if (value == null) {
|
|
14293
|
+
return null;
|
|
14294
|
+
}
|
|
14295
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
14296
|
+
throw new Error("Invalid open request cloud auth payload.");
|
|
14297
|
+
}
|
|
14298
|
+
const record = value;
|
|
14299
|
+
const apiKey = normalizeNonEmptyString3(record.apiKey);
|
|
14300
|
+
const accessToken = normalizeNonEmptyString3(record.accessToken);
|
|
14301
|
+
const baseUrl = normalizeNonEmptyString3(record.baseUrl);
|
|
14302
|
+
const authScheme = normalizeAuthScheme(record.authScheme);
|
|
14303
|
+
if (!baseUrl) {
|
|
14304
|
+
throw new Error("Open request cloud auth payload is missing baseUrl.");
|
|
14305
|
+
}
|
|
14306
|
+
if ((apiKey ? 1 : 0) + (accessToken ? 1 : 0) !== 1) {
|
|
14307
|
+
throw new Error(
|
|
14308
|
+
"Open request cloud auth payload must include exactly one credential."
|
|
14309
|
+
);
|
|
14310
|
+
}
|
|
14311
|
+
if (accessToken && authScheme !== "bearer") {
|
|
14312
|
+
throw new Error(
|
|
14313
|
+
'Open request cloud auth payload must use authScheme "bearer" with accessToken.'
|
|
14314
|
+
);
|
|
14315
|
+
}
|
|
14316
|
+
return {
|
|
14317
|
+
...apiKey ? { apiKey } : {},
|
|
14318
|
+
...accessToken ? { accessToken } : {},
|
|
14319
|
+
baseUrl,
|
|
14320
|
+
authScheme
|
|
14321
|
+
};
|
|
14322
|
+
}
|
|
14323
|
+
function buildServerOpenConfig(options) {
|
|
14324
|
+
const config = {
|
|
14325
|
+
name: options.name,
|
|
14326
|
+
storage: {
|
|
14327
|
+
rootDir: options.scopeDir
|
|
14328
|
+
},
|
|
14329
|
+
cursor: {
|
|
14330
|
+
enabled: options.cursorEnabled
|
|
14331
|
+
},
|
|
14332
|
+
browser: {
|
|
14333
|
+
headless: options.headless ?? false,
|
|
14334
|
+
connectUrl: options.connectUrl,
|
|
14335
|
+
channel: options.channel,
|
|
14336
|
+
profileDir: options.profileDir
|
|
14337
|
+
}
|
|
14338
|
+
};
|
|
14339
|
+
if (!options.cloudAuth) {
|
|
14340
|
+
return config;
|
|
14341
|
+
}
|
|
14342
|
+
const resolved = resolveConfigWithEnv(
|
|
14343
|
+
{
|
|
14344
|
+
storage: {
|
|
14345
|
+
rootDir: options.scopeDir
|
|
14346
|
+
}
|
|
14347
|
+
},
|
|
14348
|
+
{
|
|
14349
|
+
env: options.env
|
|
14350
|
+
}
|
|
14351
|
+
);
|
|
14352
|
+
const cloudSelection = resolveCloudSelection(
|
|
14353
|
+
{
|
|
14354
|
+
cloud: resolved.config.cloud
|
|
14355
|
+
},
|
|
14356
|
+
resolved.env
|
|
14357
|
+
);
|
|
14358
|
+
if (!cloudSelection.cloud) {
|
|
14359
|
+
return config;
|
|
14360
|
+
}
|
|
14361
|
+
config.cloud = toOpensteerCloudOptions(options.cloudAuth);
|
|
14362
|
+
return config;
|
|
14363
|
+
}
|
|
14364
|
+
function toOpensteerCloudOptions(auth) {
|
|
14365
|
+
return {
|
|
14366
|
+
...auth.apiKey ? { apiKey: auth.apiKey } : {},
|
|
14367
|
+
...auth.accessToken ? { accessToken: auth.accessToken } : {},
|
|
14368
|
+
baseUrl: auth.baseUrl,
|
|
14369
|
+
authScheme: auth.authScheme
|
|
14370
|
+
};
|
|
14371
|
+
}
|
|
14372
|
+
function normalizeNonEmptyString3(value) {
|
|
14373
|
+
if (typeof value !== "string") {
|
|
14374
|
+
return void 0;
|
|
14375
|
+
}
|
|
14376
|
+
const trimmed = value.trim();
|
|
14377
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
14378
|
+
}
|
|
14379
|
+
function normalizeAuthScheme(value) {
|
|
14380
|
+
if (value === "api-key" || value === "bearer") {
|
|
14381
|
+
return value;
|
|
14382
|
+
}
|
|
14383
|
+
throw new Error(
|
|
14384
|
+
'Open request cloud auth payload must use authScheme "api-key" or "bearer".'
|
|
14385
|
+
);
|
|
14386
|
+
}
|
|
14387
|
+
|
|
13113
14388
|
// src/cli/server.ts
|
|
13114
14389
|
var instance = null;
|
|
13115
14390
|
var launchPromise = null;
|
|
13116
14391
|
var selectorNamespace = null;
|
|
14392
|
+
var cloudProfileBinding = null;
|
|
14393
|
+
var cloudAuthOverride = null;
|
|
14394
|
+
var cursorEnabledPreference = readCursorPreferenceFromEnv();
|
|
13117
14395
|
var requestQueue = Promise.resolve();
|
|
13118
14396
|
var shuttingDown = false;
|
|
13119
14397
|
function sanitizeNamespace(value) {
|
|
@@ -13131,6 +14409,36 @@ function invalidateInstance() {
|
|
|
13131
14409
|
instance.close().catch(() => {
|
|
13132
14410
|
});
|
|
13133
14411
|
instance = null;
|
|
14412
|
+
cloudProfileBinding = null;
|
|
14413
|
+
}
|
|
14414
|
+
function normalizeCursorFlag(value) {
|
|
14415
|
+
if (value === void 0 || value === null) {
|
|
14416
|
+
return null;
|
|
14417
|
+
}
|
|
14418
|
+
if (typeof value === "boolean") {
|
|
14419
|
+
return value;
|
|
14420
|
+
}
|
|
14421
|
+
if (typeof value === "number") {
|
|
14422
|
+
if (value === 1) return true;
|
|
14423
|
+
if (value === 0) return false;
|
|
14424
|
+
}
|
|
14425
|
+
throw new Error(
|
|
14426
|
+
'--cursor must be a boolean value ("true" or "false").'
|
|
14427
|
+
);
|
|
14428
|
+
}
|
|
14429
|
+
function readCursorPreferenceFromEnv() {
|
|
14430
|
+
const value = process.env.OPENSTEER_CURSOR;
|
|
14431
|
+
if (typeof value !== "string") {
|
|
14432
|
+
return null;
|
|
14433
|
+
}
|
|
14434
|
+
const normalized = value.trim().toLowerCase();
|
|
14435
|
+
if (normalized === "true" || normalized === "1") {
|
|
14436
|
+
return true;
|
|
14437
|
+
}
|
|
14438
|
+
if (normalized === "false" || normalized === "0") {
|
|
14439
|
+
return false;
|
|
14440
|
+
}
|
|
14441
|
+
return null;
|
|
13134
14442
|
}
|
|
13135
14443
|
function attachLifecycleListeners(inst) {
|
|
13136
14444
|
try {
|
|
@@ -13240,7 +14548,26 @@ async function handleRequest(request, socket) {
|
|
|
13240
14548
|
const connectUrl = args["connect-url"];
|
|
13241
14549
|
const channel = args.channel;
|
|
13242
14550
|
const profileDir = args["profile-dir"];
|
|
14551
|
+
const cloudProfileId = typeof args["cloud-profile-id"] === "string" ? args["cloud-profile-id"].trim() : void 0;
|
|
14552
|
+
const cloudProfileReuseIfActive = typeof args["cloud-profile-reuse-if-active"] === "boolean" ? args["cloud-profile-reuse-if-active"] : void 0;
|
|
14553
|
+
const requestedCloudProfileBinding = normalizeCloudProfileBinding({
|
|
14554
|
+
profileId: cloudProfileId,
|
|
14555
|
+
reuseIfActive: cloudProfileReuseIfActive
|
|
14556
|
+
});
|
|
14557
|
+
const requestedCloudAuth = normalizeCliOpenCloudAuth(
|
|
14558
|
+
args["cloud-auth"]
|
|
14559
|
+
);
|
|
14560
|
+
if (cloudProfileReuseIfActive !== void 0 && !cloudProfileId) {
|
|
14561
|
+
throw new Error(
|
|
14562
|
+
"--cloud-profile-reuse-if-active requires --cloud-profile-id."
|
|
14563
|
+
);
|
|
14564
|
+
}
|
|
14565
|
+
const requestedCursor = normalizeCursorFlag(args.cursor);
|
|
13243
14566
|
const requestedName = typeof args.name === "string" && args.name.trim().length > 0 ? sanitizeNamespace(args.name) : null;
|
|
14567
|
+
if (requestedCursor !== null) {
|
|
14568
|
+
cursorEnabledPreference = requestedCursor;
|
|
14569
|
+
}
|
|
14570
|
+
const effectiveCursorEnabled = cursorEnabledPreference !== null ? cursorEnabledPreference : true;
|
|
13244
14571
|
if (selectorNamespace && requestedName && requestedName !== selectorNamespace) {
|
|
13245
14572
|
sendResponse(socket, {
|
|
13246
14573
|
id,
|
|
@@ -13264,6 +14591,9 @@ async function handleRequest(request, socket) {
|
|
|
13264
14591
|
selectorNamespace = requestedName ?? logicalSession;
|
|
13265
14592
|
}
|
|
13266
14593
|
const activeNamespace = selectorNamespace ?? logicalSession;
|
|
14594
|
+
if (requestedCloudAuth) {
|
|
14595
|
+
cloudAuthOverride = requestedCloudAuth;
|
|
14596
|
+
}
|
|
13267
14597
|
if (instance && !launchPromise) {
|
|
13268
14598
|
try {
|
|
13269
14599
|
if (instance.page.isClosed()) {
|
|
@@ -13273,31 +14603,59 @@ async function handleRequest(request, socket) {
|
|
|
13273
14603
|
invalidateInstance();
|
|
13274
14604
|
}
|
|
13275
14605
|
}
|
|
14606
|
+
if (instance && !launchPromise) {
|
|
14607
|
+
assertCompatibleCloudProfileBinding(
|
|
14608
|
+
logicalSession,
|
|
14609
|
+
cloudProfileBinding,
|
|
14610
|
+
requestedCloudProfileBinding
|
|
14611
|
+
);
|
|
14612
|
+
}
|
|
13276
14613
|
if (!instance) {
|
|
13277
|
-
instance = new Opensteer(
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
14614
|
+
instance = new Opensteer(
|
|
14615
|
+
buildServerOpenConfig({
|
|
14616
|
+
scopeDir,
|
|
14617
|
+
name: activeNamespace,
|
|
14618
|
+
cursorEnabled: effectiveCursorEnabled,
|
|
14619
|
+
headless,
|
|
13281
14620
|
connectUrl,
|
|
13282
14621
|
channel,
|
|
13283
|
-
profileDir
|
|
13284
|
-
|
|
13285
|
-
|
|
14622
|
+
profileDir,
|
|
14623
|
+
cloudAuth: cloudAuthOverride
|
|
14624
|
+
})
|
|
14625
|
+
);
|
|
14626
|
+
const nextCloudProfileBinding = resolveSessionCloudProfileBinding(
|
|
14627
|
+
instance.getConfig(),
|
|
14628
|
+
requestedCloudProfileBinding
|
|
14629
|
+
);
|
|
14630
|
+
if (requestedCloudProfileBinding && !nextCloudProfileBinding) {
|
|
14631
|
+
instance = null;
|
|
14632
|
+
throw new Error(
|
|
14633
|
+
"--cloud-profile-id can only be used when cloud mode is enabled for this session."
|
|
14634
|
+
);
|
|
14635
|
+
}
|
|
13286
14636
|
launchPromise = instance.launch({
|
|
13287
14637
|
headless: headless ?? false,
|
|
14638
|
+
cloudBrowserProfile: cloudProfileId ? {
|
|
14639
|
+
profileId: cloudProfileId,
|
|
14640
|
+
reuseIfActive: cloudProfileReuseIfActive
|
|
14641
|
+
} : void 0,
|
|
13288
14642
|
timeout: connectUrl ? 12e4 : 3e4
|
|
13289
14643
|
});
|
|
13290
14644
|
try {
|
|
13291
14645
|
await launchPromise;
|
|
13292
14646
|
attachLifecycleListeners(instance);
|
|
14647
|
+
cloudProfileBinding = nextCloudProfileBinding;
|
|
13293
14648
|
} catch (err) {
|
|
13294
14649
|
instance = null;
|
|
14650
|
+
cloudProfileBinding = null;
|
|
13295
14651
|
throw err;
|
|
13296
14652
|
} finally {
|
|
13297
14653
|
launchPromise = null;
|
|
13298
14654
|
}
|
|
13299
14655
|
} else if (launchPromise) {
|
|
13300
14656
|
await launchPromise;
|
|
14657
|
+
} else if (requestedCursor !== null) {
|
|
14658
|
+
instance.setCursorEnabled(requestedCursor);
|
|
13301
14659
|
}
|
|
13302
14660
|
if (url) {
|
|
13303
14661
|
await instance.goto(url);
|
|
@@ -13312,6 +14670,7 @@ async function handleRequest(request, socket) {
|
|
|
13312
14670
|
runtimeSession: session,
|
|
13313
14671
|
scopeDir,
|
|
13314
14672
|
name: activeNamespace,
|
|
14673
|
+
cursor: instance.getCursorState(),
|
|
13315
14674
|
cloudSessionId: instance.getCloudSessionId() ?? void 0,
|
|
13316
14675
|
cloudSessionUrl: instance.getCloudSessionUrl() ?? void 0
|
|
13317
14676
|
}
|
|
@@ -13324,6 +14683,41 @@ async function handleRequest(request, socket) {
|
|
|
13324
14683
|
}
|
|
13325
14684
|
return;
|
|
13326
14685
|
}
|
|
14686
|
+
if (command === "cursor") {
|
|
14687
|
+
try {
|
|
14688
|
+
const mode = typeof args.mode === "string" ? args.mode : "status";
|
|
14689
|
+
if (mode === "on") {
|
|
14690
|
+
cursorEnabledPreference = true;
|
|
14691
|
+
instance?.setCursorEnabled(true);
|
|
14692
|
+
} else if (mode === "off") {
|
|
14693
|
+
cursorEnabledPreference = false;
|
|
14694
|
+
instance?.setCursorEnabled(false);
|
|
14695
|
+
} else if (mode !== "status") {
|
|
14696
|
+
throw new Error(
|
|
14697
|
+
`Invalid cursor mode "${mode}". Use "on", "off", or "status".`
|
|
14698
|
+
);
|
|
14699
|
+
}
|
|
14700
|
+
const defaultEnabled = cursorEnabledPreference !== null ? cursorEnabledPreference : true;
|
|
14701
|
+
const cursor = instance ? instance.getCursorState() : {
|
|
14702
|
+
enabled: defaultEnabled,
|
|
14703
|
+
active: false,
|
|
14704
|
+
reason: "session_not_open"
|
|
14705
|
+
};
|
|
14706
|
+
sendResponse(socket, {
|
|
14707
|
+
id,
|
|
14708
|
+
ok: true,
|
|
14709
|
+
result: {
|
|
14710
|
+
cursor
|
|
14711
|
+
}
|
|
14712
|
+
});
|
|
14713
|
+
} catch (err) {
|
|
14714
|
+
sendResponse(
|
|
14715
|
+
socket,
|
|
14716
|
+
buildErrorResponse(id, err, "Failed to update cursor mode.")
|
|
14717
|
+
);
|
|
14718
|
+
}
|
|
14719
|
+
return;
|
|
14720
|
+
}
|
|
13327
14721
|
if (command === "close") {
|
|
13328
14722
|
try {
|
|
13329
14723
|
if (instance) {
|