opensteer 0.4.5 → 0.4.7
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 +4 -3
- package/README.md +4 -4
- package/bin/opensteer.mjs +5 -6
- package/dist/{chunk-2NKR4JZ6.js → chunk-PIJI7FBH.js} +1121 -308
- package/dist/cli/server.cjs +1117 -304
- package/dist/cli/server.js +3 -2
- package/dist/index.cjs +1127 -313
- package/dist/index.d.cts +69 -42
- package/dist/index.d.ts +69 -42
- package/dist/index.js +13 -11
- package/package.json +73 -80
|
@@ -37,15 +37,52 @@ function sanitizeNamespaceSegment(segment) {
|
|
|
37
37
|
// src/navigation.ts
|
|
38
38
|
var DEFAULT_TIMEOUT = 3e4;
|
|
39
39
|
var DEFAULT_SETTLE_MS = 750;
|
|
40
|
+
var FRAME_EVALUATE_GRACE_MS = 200;
|
|
41
|
+
var STEALTH_WORLD_NAME = "__opensteer_wait__";
|
|
42
|
+
var StealthWaitUnavailableError = class extends Error {
|
|
43
|
+
constructor(cause) {
|
|
44
|
+
super("Stealth visual wait requires Chromium CDP support.", { cause });
|
|
45
|
+
this.name = "StealthWaitUnavailableError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function isStealthWaitUnavailableError(error) {
|
|
49
|
+
return error instanceof StealthWaitUnavailableError;
|
|
50
|
+
}
|
|
51
|
+
var FRAME_OWNER_VISIBILITY_FUNCTION = `function() {
|
|
52
|
+
if (!(this instanceof HTMLElement)) return false;
|
|
53
|
+
|
|
54
|
+
var rect = this.getBoundingClientRect();
|
|
55
|
+
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
56
|
+
if (
|
|
57
|
+
rect.bottom <= 0 ||
|
|
58
|
+
rect.right <= 0 ||
|
|
59
|
+
rect.top >= window.innerHeight ||
|
|
60
|
+
rect.left >= window.innerWidth
|
|
61
|
+
) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var style = window.getComputedStyle(this);
|
|
66
|
+
if (
|
|
67
|
+
style.display === 'none' ||
|
|
68
|
+
style.visibility === 'hidden' ||
|
|
69
|
+
Number(style.opacity) === 0
|
|
70
|
+
) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}`;
|
|
40
76
|
function buildStabilityScript(timeout, settleMs) {
|
|
41
77
|
return `new Promise(function(resolve) {
|
|
42
78
|
var deadline = Date.now() + ${timeout};
|
|
43
|
-
var timer = null;
|
|
44
79
|
var resolved = false;
|
|
80
|
+
var timer = null;
|
|
45
81
|
var observers = [];
|
|
46
82
|
var observedShadowRoots = [];
|
|
47
83
|
var fonts = document.fonts;
|
|
48
84
|
var fontsReady = !fonts || fonts.status === 'loaded';
|
|
85
|
+
var lastRelevantMutationAt = Date.now();
|
|
49
86
|
|
|
50
87
|
function clearObservers() {
|
|
51
88
|
for (var i = 0; i < observers.length; i++) {
|
|
@@ -63,9 +100,87 @@ function buildStabilityScript(timeout, settleMs) {
|
|
|
63
100
|
resolve();
|
|
64
101
|
}
|
|
65
102
|
|
|
103
|
+
function isElementVisiblyIntersectingViewport(element) {
|
|
104
|
+
if (!(element instanceof Element)) return false;
|
|
105
|
+
|
|
106
|
+
var rect = element.getBoundingClientRect();
|
|
107
|
+
var inViewport =
|
|
108
|
+
rect.width > 0 &&
|
|
109
|
+
rect.height > 0 &&
|
|
110
|
+
rect.bottom > 0 &&
|
|
111
|
+
rect.right > 0 &&
|
|
112
|
+
rect.top < window.innerHeight &&
|
|
113
|
+
rect.left < window.innerWidth;
|
|
114
|
+
|
|
115
|
+
if (!inViewport) return false;
|
|
116
|
+
|
|
117
|
+
var style = window.getComputedStyle(element);
|
|
118
|
+
if (style.visibility === 'hidden' || style.display === 'none') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (Number(style.opacity) === 0) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resolveRelevantElement(node) {
|
|
129
|
+
if (!node) return null;
|
|
130
|
+
if (node instanceof Element) return node;
|
|
131
|
+
if (typeof ShadowRoot !== 'undefined' && node instanceof ShadowRoot) {
|
|
132
|
+
return node.host instanceof Element ? node.host : null;
|
|
133
|
+
}
|
|
134
|
+
var parentElement = node.parentElement;
|
|
135
|
+
return parentElement instanceof Element ? parentElement : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isNodeVisiblyRelevant(node) {
|
|
139
|
+
var element = resolveRelevantElement(node);
|
|
140
|
+
if (!element) return false;
|
|
141
|
+
return isElementVisiblyIntersectingViewport(element);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function hasRelevantMutation(records) {
|
|
145
|
+
for (var i = 0; i < records.length; i++) {
|
|
146
|
+
var record = records[i];
|
|
147
|
+
if (isNodeVisiblyRelevant(record.target)) return true;
|
|
148
|
+
|
|
149
|
+
var addedNodes = record.addedNodes;
|
|
150
|
+
for (var j = 0; j < addedNodes.length; j++) {
|
|
151
|
+
if (isNodeVisiblyRelevant(addedNodes[j])) return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var removedNodes = record.removedNodes;
|
|
155
|
+
for (var k = 0; k < removedNodes.length; k++) {
|
|
156
|
+
if (isNodeVisiblyRelevant(removedNodes[k])) return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function scheduleCheck() {
|
|
164
|
+
if (resolved) return;
|
|
165
|
+
if (timer) clearTimeout(timer);
|
|
166
|
+
|
|
167
|
+
var remaining = deadline - Date.now();
|
|
168
|
+
if (remaining <= 0) {
|
|
169
|
+
done();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
var checkDelay = Math.min(120, Math.max(16, ${settleMs}));
|
|
174
|
+
timer = setTimeout(checkNow, checkDelay);
|
|
175
|
+
}
|
|
176
|
+
|
|
66
177
|
function observeMutations(target) {
|
|
67
178
|
if (!target) return;
|
|
68
|
-
var observer = new MutationObserver(function() {
|
|
179
|
+
var observer = new MutationObserver(function(records) {
|
|
180
|
+
if (!hasRelevantMutation(records)) return;
|
|
181
|
+
lastRelevantMutationAt = Date.now();
|
|
182
|
+
scheduleCheck();
|
|
183
|
+
});
|
|
69
184
|
observer.observe(target, {
|
|
70
185
|
childList: true,
|
|
71
186
|
subtree: true,
|
|
@@ -103,18 +218,25 @@ function buildStabilityScript(timeout, settleMs) {
|
|
|
103
218
|
var images = root.querySelectorAll('img');
|
|
104
219
|
for (var i = 0; i < images.length; i++) {
|
|
105
220
|
var img = images[i];
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
rect.bottom > 0 &&
|
|
109
|
-
rect.right > 0 &&
|
|
110
|
-
rect.top < window.innerHeight &&
|
|
111
|
-
rect.left < window.innerWidth;
|
|
112
|
-
if (inViewport && !img.complete) return false;
|
|
221
|
+
if (!isElementVisiblyIntersectingViewport(img)) continue;
|
|
222
|
+
if (!img.complete) return false;
|
|
113
223
|
}
|
|
114
224
|
return true;
|
|
115
225
|
}
|
|
116
226
|
|
|
117
|
-
function
|
|
227
|
+
function getAnimationTarget(effect) {
|
|
228
|
+
if (!effect) return null;
|
|
229
|
+
var target = effect.target;
|
|
230
|
+
if (target instanceof Element) return target;
|
|
231
|
+
|
|
232
|
+
if (target && target.element instanceof Element) {
|
|
233
|
+
return target.element;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function hasRunningVisibleFiniteAnimations() {
|
|
118
240
|
if (typeof document.getAnimations !== 'function') return false;
|
|
119
241
|
var animations = document.getAnimations();
|
|
120
242
|
|
|
@@ -128,6 +250,9 @@ function buildStabilityScript(timeout, settleMs) {
|
|
|
128
250
|
? timing.endTime
|
|
129
251
|
: Number.POSITIVE_INFINITY;
|
|
130
252
|
if (Number.isFinite(endTime) && endTime > 0) {
|
|
253
|
+
var target = getAnimationTarget(effect);
|
|
254
|
+
if (!target) continue;
|
|
255
|
+
if (!isElementVisiblyIntersectingViewport(target)) continue;
|
|
131
256
|
return true;
|
|
132
257
|
}
|
|
133
258
|
}
|
|
@@ -138,21 +263,29 @@ function buildStabilityScript(timeout, settleMs) {
|
|
|
138
263
|
function isVisuallyReady() {
|
|
139
264
|
if (!fontsReady) return false;
|
|
140
265
|
if (!checkViewportImages(document)) return false;
|
|
141
|
-
if (
|
|
266
|
+
if (hasRunningVisibleFiniteAnimations()) return false;
|
|
142
267
|
return true;
|
|
143
268
|
}
|
|
144
269
|
|
|
145
|
-
function
|
|
146
|
-
if (Date.now()
|
|
147
|
-
|
|
270
|
+
function checkNow() {
|
|
271
|
+
if (Date.now() >= deadline) {
|
|
272
|
+
done();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
148
276
|
observeOpenShadowRoots();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
277
|
+
|
|
278
|
+
if (!isVisuallyReady()) {
|
|
279
|
+
scheduleCheck();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (Date.now() - lastRelevantMutationAt >= ${settleMs}) {
|
|
284
|
+
done();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
scheduleCheck();
|
|
156
289
|
}
|
|
157
290
|
|
|
158
291
|
observeMutations(document.documentElement);
|
|
@@ -161,67 +294,266 @@ function buildStabilityScript(timeout, settleMs) {
|
|
|
161
294
|
if (fonts && fonts.ready && typeof fonts.ready.then === 'function') {
|
|
162
295
|
fonts.ready.then(function() {
|
|
163
296
|
fontsReady = true;
|
|
164
|
-
|
|
297
|
+
scheduleCheck();
|
|
298
|
+
}, function() {
|
|
299
|
+
fontsReady = true;
|
|
300
|
+
scheduleCheck();
|
|
165
301
|
});
|
|
166
302
|
}
|
|
167
303
|
|
|
168
304
|
var safetyTimer = setTimeout(done, ${timeout});
|
|
169
305
|
|
|
170
|
-
|
|
306
|
+
scheduleCheck();
|
|
171
307
|
})`;
|
|
172
308
|
}
|
|
309
|
+
var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
310
|
+
constructor(session) {
|
|
311
|
+
this.session = session;
|
|
312
|
+
}
|
|
313
|
+
contextsByFrame = /* @__PURE__ */ new Map();
|
|
314
|
+
disposed = false;
|
|
315
|
+
static async create(page) {
|
|
316
|
+
let session;
|
|
317
|
+
try {
|
|
318
|
+
session = await page.context().newCDPSession(page);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
throw new StealthWaitUnavailableError(error);
|
|
321
|
+
}
|
|
322
|
+
const runtime = new _StealthCdpRuntime(session);
|
|
323
|
+
try {
|
|
324
|
+
await runtime.initialize();
|
|
325
|
+
return runtime;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
await runtime.dispose();
|
|
328
|
+
throw new StealthWaitUnavailableError(error);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async dispose() {
|
|
332
|
+
if (this.disposed) return;
|
|
333
|
+
this.disposed = true;
|
|
334
|
+
this.contextsByFrame.clear();
|
|
335
|
+
await this.session.detach().catch(() => void 0);
|
|
336
|
+
}
|
|
337
|
+
async waitForMainFrameVisualStability(options) {
|
|
338
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
339
|
+
const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
|
|
340
|
+
if (timeout <= 0) return;
|
|
341
|
+
const frameRecords = await this.getFrameRecords();
|
|
342
|
+
const mainFrame = frameRecords[0];
|
|
343
|
+
if (!mainFrame) return;
|
|
344
|
+
await this.waitForFrameVisualStability(mainFrame.frameId, timeout, settleMs);
|
|
345
|
+
}
|
|
346
|
+
async collectVisibleFrameIds() {
|
|
347
|
+
const frameRecords = await this.getFrameRecords();
|
|
348
|
+
if (frameRecords.length === 0) return [];
|
|
349
|
+
const visibleFrameIds = [];
|
|
350
|
+
for (const frameRecord of frameRecords) {
|
|
351
|
+
if (!frameRecord.parentFrameId) {
|
|
352
|
+
visibleFrameIds.push(frameRecord.frameId);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const parentContextId = await this.ensureFrameContextId(
|
|
357
|
+
frameRecord.parentFrameId
|
|
358
|
+
);
|
|
359
|
+
const visible = await this.isFrameOwnerVisible(
|
|
360
|
+
frameRecord.frameId,
|
|
361
|
+
parentContextId
|
|
362
|
+
);
|
|
363
|
+
if (visible) {
|
|
364
|
+
visibleFrameIds.push(frameRecord.frameId);
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
if (isIgnorableFrameError(error)) continue;
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return visibleFrameIds;
|
|
372
|
+
}
|
|
373
|
+
async waitForFrameVisualStability(frameId, timeout, settleMs) {
|
|
374
|
+
if (timeout <= 0) return;
|
|
375
|
+
const script = buildStabilityScript(timeout, settleMs);
|
|
376
|
+
let contextId = await this.ensureFrameContextId(frameId);
|
|
377
|
+
try {
|
|
378
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
if (!isMissingExecutionContextError(error)) {
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
this.contextsByFrame.delete(frameId);
|
|
384
|
+
contextId = await this.ensureFrameContextId(frameId);
|
|
385
|
+
await this.evaluateWithGuard(contextId, script, timeout);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async initialize() {
|
|
389
|
+
await this.session.send("Page.enable");
|
|
390
|
+
await this.session.send("Runtime.enable");
|
|
391
|
+
await this.session.send("DOM.enable");
|
|
392
|
+
}
|
|
393
|
+
async getFrameRecords() {
|
|
394
|
+
const treeResult = await this.session.send("Page.getFrameTree");
|
|
395
|
+
const records = [];
|
|
396
|
+
walkFrameTree(treeResult.frameTree, null, records);
|
|
397
|
+
return records;
|
|
398
|
+
}
|
|
399
|
+
async ensureFrameContextId(frameId) {
|
|
400
|
+
const cached = this.contextsByFrame.get(frameId);
|
|
401
|
+
if (cached != null) {
|
|
402
|
+
return cached;
|
|
403
|
+
}
|
|
404
|
+
const world = await this.session.send("Page.createIsolatedWorld", {
|
|
405
|
+
frameId,
|
|
406
|
+
worldName: STEALTH_WORLD_NAME
|
|
407
|
+
});
|
|
408
|
+
this.contextsByFrame.set(frameId, world.executionContextId);
|
|
409
|
+
return world.executionContextId;
|
|
410
|
+
}
|
|
411
|
+
async evaluateWithGuard(contextId, script, timeout) {
|
|
412
|
+
const evaluationPromise = this.evaluateScript(contextId, script);
|
|
413
|
+
const settledPromise = evaluationPromise.then(
|
|
414
|
+
() => ({ kind: "resolved" }),
|
|
415
|
+
(error) => ({ kind: "rejected", error })
|
|
416
|
+
);
|
|
417
|
+
const timeoutPromise = sleep(
|
|
418
|
+
timeout + FRAME_EVALUATE_GRACE_MS
|
|
419
|
+
).then(() => ({ kind: "timeout" }));
|
|
420
|
+
const result = await Promise.race([
|
|
421
|
+
settledPromise,
|
|
422
|
+
timeoutPromise
|
|
423
|
+
]);
|
|
424
|
+
if (result.kind === "rejected") {
|
|
425
|
+
throw result.error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async evaluateScript(contextId, expression) {
|
|
429
|
+
const result = await this.session.send("Runtime.evaluate", {
|
|
430
|
+
contextId,
|
|
431
|
+
expression,
|
|
432
|
+
awaitPromise: true,
|
|
433
|
+
returnByValue: true
|
|
434
|
+
});
|
|
435
|
+
if (result.exceptionDetails) {
|
|
436
|
+
throw new Error(formatCdpException(result.exceptionDetails));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async isFrameOwnerVisible(frameId, parentContextId) {
|
|
440
|
+
const owner = await this.session.send("DOM.getFrameOwner", {
|
|
441
|
+
frameId
|
|
442
|
+
});
|
|
443
|
+
const resolveParams = {
|
|
444
|
+
executionContextId: parentContextId
|
|
445
|
+
};
|
|
446
|
+
if (typeof owner.backendNodeId === "number") {
|
|
447
|
+
resolveParams.backendNodeId = owner.backendNodeId;
|
|
448
|
+
} else if (typeof owner.nodeId === "number") {
|
|
449
|
+
resolveParams.nodeId = owner.nodeId;
|
|
450
|
+
} else {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
const resolved = await this.session.send(
|
|
454
|
+
"DOM.resolveNode",
|
|
455
|
+
resolveParams
|
|
456
|
+
);
|
|
457
|
+
const objectId = resolved.object?.objectId;
|
|
458
|
+
if (!objectId) return false;
|
|
459
|
+
try {
|
|
460
|
+
const callResult = await this.session.send("Runtime.callFunctionOn", {
|
|
461
|
+
objectId,
|
|
462
|
+
functionDeclaration: FRAME_OWNER_VISIBILITY_FUNCTION,
|
|
463
|
+
returnByValue: true
|
|
464
|
+
});
|
|
465
|
+
if (callResult.exceptionDetails) {
|
|
466
|
+
throw new Error(formatCdpException(callResult.exceptionDetails));
|
|
467
|
+
}
|
|
468
|
+
return callResult.result.value === true;
|
|
469
|
+
} finally {
|
|
470
|
+
await this.releaseObject(objectId);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async releaseObject(objectId) {
|
|
474
|
+
await this.session.send("Runtime.releaseObject", {
|
|
475
|
+
objectId
|
|
476
|
+
}).catch(() => void 0);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
173
479
|
async function waitForVisualStability(page, options = {}) {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
480
|
+
const runtime = await StealthCdpRuntime.create(page);
|
|
481
|
+
try {
|
|
482
|
+
await runtime.waitForMainFrameVisualStability(options);
|
|
483
|
+
} finally {
|
|
484
|
+
await runtime.dispose();
|
|
485
|
+
}
|
|
180
486
|
}
|
|
181
487
|
async function waitForVisualStabilityAcrossFrames(page, options = {}) {
|
|
182
488
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
183
489
|
const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
|
|
490
|
+
if (timeout <= 0) return;
|
|
184
491
|
const deadline = Date.now() + timeout;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
492
|
+
const runtime = await StealthCdpRuntime.create(page);
|
|
493
|
+
try {
|
|
494
|
+
while (true) {
|
|
495
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
496
|
+
if (remaining === 0) return;
|
|
497
|
+
const frameIds = await runtime.collectVisibleFrameIds();
|
|
498
|
+
if (frameIds.length === 0) return;
|
|
499
|
+
await Promise.all(
|
|
500
|
+
frameIds.map(async (frameId) => {
|
|
501
|
+
try {
|
|
502
|
+
await runtime.waitForFrameVisualStability(
|
|
503
|
+
frameId,
|
|
504
|
+
remaining,
|
|
505
|
+
settleMs
|
|
506
|
+
);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (isIgnorableFrameError(error)) return;
|
|
509
|
+
throw error;
|
|
510
|
+
}
|
|
511
|
+
})
|
|
512
|
+
);
|
|
513
|
+
const currentFrameIds = await runtime.collectVisibleFrameIds();
|
|
514
|
+
if (sameFrameIds(frameIds, currentFrameIds)) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
205
517
|
}
|
|
518
|
+
} finally {
|
|
519
|
+
await runtime.dispose();
|
|
206
520
|
}
|
|
207
521
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
522
|
+
function walkFrameTree(node, parentFrameId, records) {
|
|
523
|
+
const frameId = node.frame?.id;
|
|
524
|
+
if (!frameId) return;
|
|
525
|
+
records.push({
|
|
526
|
+
frameId,
|
|
527
|
+
parentFrameId
|
|
528
|
+
});
|
|
529
|
+
for (const child of node.childFrames ?? []) {
|
|
530
|
+
walkFrameTree(child, frameId, records);
|
|
531
|
+
}
|
|
213
532
|
}
|
|
214
|
-
function
|
|
533
|
+
function sameFrameIds(before, after) {
|
|
215
534
|
if (before.length !== after.length) return false;
|
|
216
|
-
for (const
|
|
217
|
-
if (!after.includes(
|
|
535
|
+
for (const frameId of before) {
|
|
536
|
+
if (!after.includes(frameId)) return false;
|
|
218
537
|
}
|
|
219
538
|
return true;
|
|
220
539
|
}
|
|
540
|
+
function formatCdpException(details) {
|
|
541
|
+
return details.exception?.description || details.text || "CDP runtime evaluation failed.";
|
|
542
|
+
}
|
|
543
|
+
function isMissingExecutionContextError(error) {
|
|
544
|
+
if (!(error instanceof Error)) return false;
|
|
545
|
+
const message = error.message;
|
|
546
|
+
return message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context");
|
|
547
|
+
}
|
|
221
548
|
function isIgnorableFrameError(error) {
|
|
222
549
|
if (!(error instanceof Error)) return false;
|
|
223
550
|
const message = error.message;
|
|
224
|
-
return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed");
|
|
551
|
+
return message.includes("Frame was detached") || message.includes("Execution context was destroyed") || message.includes("Target page, context or browser has been closed") || message.includes("Cannot find context with specified id") || message.includes("Cannot find execution context") || message.includes("No frame for given id found");
|
|
552
|
+
}
|
|
553
|
+
function sleep(ms) {
|
|
554
|
+
return new Promise((resolve) => {
|
|
555
|
+
setTimeout(resolve, ms);
|
|
556
|
+
});
|
|
225
557
|
}
|
|
226
558
|
|
|
227
559
|
// src/storage/registry.ts
|
|
@@ -1114,6 +1446,66 @@ var OS_BOUNDARY_ATTR = "data-os-boundary";
|
|
|
1114
1446
|
var OS_UNAVAILABLE_ATTR = "data-os-unavailable";
|
|
1115
1447
|
var OS_IFRAME_BOUNDARY_TAG = "os-iframe-root";
|
|
1116
1448
|
var OS_SHADOW_BOUNDARY_TAG = "os-shadow-root";
|
|
1449
|
+
function decodeSerializedNodeTableEntry(nodeTable, rawIndex, label) {
|
|
1450
|
+
if (typeof rawIndex !== "number" || !Number.isInteger(rawIndex) || rawIndex < 0 || rawIndex >= nodeTable.length) {
|
|
1451
|
+
throw new Error(
|
|
1452
|
+
`Invalid serialized path node index at "${label}": expected a valid table index.`
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
const node = nodeTable[rawIndex];
|
|
1456
|
+
if (!node || typeof node !== "object") {
|
|
1457
|
+
throw new Error(
|
|
1458
|
+
`Invalid serialized path node at "${label}": table entry is missing.`
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
return node;
|
|
1462
|
+
}
|
|
1463
|
+
function decodeSerializedDomPath(nodeTable, rawPath, label) {
|
|
1464
|
+
if (!Array.isArray(rawPath)) {
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
`Invalid serialized path at "${label}": expected an array of node indexes.`
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
return rawPath.map(
|
|
1470
|
+
(value, index) => decodeSerializedNodeTableEntry(nodeTable, value, `${label}[${index}]`)
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
function decodeSerializedElementPath(nodeTable, rawPath, label) {
|
|
1474
|
+
if (!rawPath || typeof rawPath !== "object") {
|
|
1475
|
+
throw new Error(
|
|
1476
|
+
`Invalid serialized element path at "${label}": expected an object.`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
if (rawPath.context !== void 0 && !Array.isArray(rawPath.context)) {
|
|
1480
|
+
throw new Error(
|
|
1481
|
+
`Invalid serialized context at "${label}.context": expected an array.`
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
const contextRaw = Array.isArray(rawPath.context) ? rawPath.context : [];
|
|
1485
|
+
const context = contextRaw.map((hop, hopIndex) => {
|
|
1486
|
+
if (!hop || typeof hop !== "object" || hop.kind !== "shadow") {
|
|
1487
|
+
throw new Error(
|
|
1488
|
+
`Invalid serialized context hop at "${label}.context[${hopIndex}]": expected a shadow hop.`
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
return {
|
|
1492
|
+
kind: "shadow",
|
|
1493
|
+
host: decodeSerializedDomPath(
|
|
1494
|
+
nodeTable,
|
|
1495
|
+
hop.host,
|
|
1496
|
+
`${label}.context[${hopIndex}].host`
|
|
1497
|
+
)
|
|
1498
|
+
};
|
|
1499
|
+
});
|
|
1500
|
+
return {
|
|
1501
|
+
context,
|
|
1502
|
+
nodes: decodeSerializedDomPath(
|
|
1503
|
+
nodeTable,
|
|
1504
|
+
rawPath.nodes,
|
|
1505
|
+
`${label}.nodes`
|
|
1506
|
+
)
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1117
1509
|
async function serializePageHTML(page, _options = {}) {
|
|
1118
1510
|
return serializeFrameRecursive(page.mainFrame(), [], "f0");
|
|
1119
1511
|
}
|
|
@@ -1170,6 +1562,8 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
1170
1562
|
(Array.isArray(deferredMatchAttrKeys) ? deferredMatchAttrKeys : []).map((key) => String(key))
|
|
1171
1563
|
);
|
|
1172
1564
|
let counter = 1;
|
|
1565
|
+
const nodeTable = [];
|
|
1566
|
+
const nodeTableIndexByKey = /* @__PURE__ */ new Map();
|
|
1173
1567
|
const entries = [];
|
|
1174
1568
|
const helpers = {
|
|
1175
1569
|
nextToken() {
|
|
@@ -1371,6 +1765,47 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
1371
1765
|
nodes: target
|
|
1372
1766
|
};
|
|
1373
1767
|
},
|
|
1768
|
+
buildPathNodeKey(node) {
|
|
1769
|
+
const attrs = Object.entries(node.attrs || {}).sort(
|
|
1770
|
+
([a], [b]) => a.localeCompare(b)
|
|
1771
|
+
);
|
|
1772
|
+
const match = (node.match || []).map(
|
|
1773
|
+
(clause) => clause.kind === "attr" ? [
|
|
1774
|
+
"attr",
|
|
1775
|
+
clause.key,
|
|
1776
|
+
clause.op || "exact",
|
|
1777
|
+
clause.value ?? null
|
|
1778
|
+
] : ["position", clause.axis]
|
|
1779
|
+
);
|
|
1780
|
+
return JSON.stringify([
|
|
1781
|
+
node.tag,
|
|
1782
|
+
node.position.nthChild,
|
|
1783
|
+
node.position.nthOfType,
|
|
1784
|
+
attrs,
|
|
1785
|
+
match
|
|
1786
|
+
]);
|
|
1787
|
+
},
|
|
1788
|
+
internPathNode(node) {
|
|
1789
|
+
const key = helpers.buildPathNodeKey(node);
|
|
1790
|
+
const existing = nodeTableIndexByKey.get(key);
|
|
1791
|
+
if (existing != null) return existing;
|
|
1792
|
+
const index = nodeTable.length;
|
|
1793
|
+
nodeTable.push(node);
|
|
1794
|
+
nodeTableIndexByKey.set(key, index);
|
|
1795
|
+
return index;
|
|
1796
|
+
},
|
|
1797
|
+
packDomPath(path5) {
|
|
1798
|
+
return path5.map((node) => helpers.internPathNode(node));
|
|
1799
|
+
},
|
|
1800
|
+
packElementPath(path5) {
|
|
1801
|
+
return {
|
|
1802
|
+
context: (path5.context || []).map((hop) => ({
|
|
1803
|
+
kind: "shadow",
|
|
1804
|
+
host: helpers.packDomPath(hop.host)
|
|
1805
|
+
})),
|
|
1806
|
+
nodes: helpers.packDomPath(path5.nodes)
|
|
1807
|
+
};
|
|
1808
|
+
},
|
|
1374
1809
|
ensureNodeId(el) {
|
|
1375
1810
|
const next = `${frameKey2}_${counter++}`;
|
|
1376
1811
|
el.setAttribute(nodeAttr, next);
|
|
@@ -1400,9 +1835,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
1400
1835
|
serializeElement(el) {
|
|
1401
1836
|
const nodeId = helpers.ensureNodeId(el);
|
|
1402
1837
|
const instanceToken = helpers.setInstanceToken(el);
|
|
1838
|
+
const packedPath = helpers.packElementPath(
|
|
1839
|
+
helpers.buildElementPath(el)
|
|
1840
|
+
);
|
|
1403
1841
|
entries.push({
|
|
1404
1842
|
nodeId,
|
|
1405
|
-
path:
|
|
1843
|
+
path: packedPath,
|
|
1406
1844
|
instanceToken
|
|
1407
1845
|
});
|
|
1408
1846
|
const tag = el.tagName.toLowerCase();
|
|
@@ -1430,11 +1868,12 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
1430
1868
|
win[frameTokenKey] = frameToken;
|
|
1431
1869
|
const root = document.documentElement;
|
|
1432
1870
|
if (!root) {
|
|
1433
|
-
return { html: "", frameToken, entries };
|
|
1871
|
+
return { html: "", frameToken, nodeTable, entries };
|
|
1434
1872
|
}
|
|
1435
1873
|
return {
|
|
1436
1874
|
html: helpers.serializeElement(root),
|
|
1437
1875
|
frameToken,
|
|
1876
|
+
nodeTable,
|
|
1438
1877
|
entries
|
|
1439
1878
|
};
|
|
1440
1879
|
},
|
|
@@ -1452,13 +1891,18 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
1452
1891
|
);
|
|
1453
1892
|
const nodePaths = /* @__PURE__ */ new Map();
|
|
1454
1893
|
const nodeMeta = /* @__PURE__ */ new Map();
|
|
1455
|
-
for (const entry of frameSnapshot.entries) {
|
|
1894
|
+
for (const [index, entry] of frameSnapshot.entries.entries()) {
|
|
1895
|
+
const path5 = decodeSerializedElementPath(
|
|
1896
|
+
frameSnapshot.nodeTable,
|
|
1897
|
+
entry.path,
|
|
1898
|
+
`entries[${index}].path`
|
|
1899
|
+
);
|
|
1456
1900
|
nodePaths.set(entry.nodeId, {
|
|
1457
1901
|
context: [
|
|
1458
1902
|
...baseContext,
|
|
1459
|
-
...
|
|
1903
|
+
...path5.context || []
|
|
1460
1904
|
],
|
|
1461
|
-
nodes:
|
|
1905
|
+
nodes: path5.nodes
|
|
1462
1906
|
});
|
|
1463
1907
|
nodeMeta.set(entry.nodeId, {
|
|
1464
1908
|
frameToken: frameSnapshot.frameToken,
|
|
@@ -3854,7 +4298,7 @@ async function performInput(page, path5, options) {
|
|
|
3854
4298
|
await resolved.element.type(options.text);
|
|
3855
4299
|
}
|
|
3856
4300
|
if (options.pressEnter) {
|
|
3857
|
-
await resolved.element.press("Enter");
|
|
4301
|
+
await resolved.element.press("Enter", { noWaitAfter: true });
|
|
3858
4302
|
}
|
|
3859
4303
|
return {
|
|
3860
4304
|
ok: true,
|
|
@@ -4492,33 +4936,36 @@ var OpensteerActionError = class extends Error {
|
|
|
4492
4936
|
}
|
|
4493
4937
|
};
|
|
4494
4938
|
|
|
4495
|
-
// src/
|
|
4496
|
-
var
|
|
4939
|
+
// src/cloud/contracts.ts
|
|
4940
|
+
var cloudSessionContractVersion = "v3";
|
|
4941
|
+
|
|
4942
|
+
// src/cloud/errors.ts
|
|
4943
|
+
var OpensteerCloudError = class extends Error {
|
|
4497
4944
|
code;
|
|
4498
4945
|
status;
|
|
4499
4946
|
details;
|
|
4500
4947
|
constructor(code, message, status, details) {
|
|
4501
4948
|
super(message);
|
|
4502
|
-
this.name = "
|
|
4949
|
+
this.name = "OpensteerCloudError";
|
|
4503
4950
|
this.code = code;
|
|
4504
4951
|
this.status = status;
|
|
4505
4952
|
this.details = details;
|
|
4506
4953
|
}
|
|
4507
4954
|
};
|
|
4508
|
-
function
|
|
4509
|
-
return new
|
|
4510
|
-
"
|
|
4511
|
-
message || `${method} is not supported in
|
|
4955
|
+
function cloudUnsupportedMethodError(method, message) {
|
|
4956
|
+
return new OpensteerCloudError(
|
|
4957
|
+
"CLOUD_UNSUPPORTED_METHOD",
|
|
4958
|
+
message || `${method} is not supported in cloud mode.`
|
|
4512
4959
|
);
|
|
4513
4960
|
}
|
|
4514
|
-
function
|
|
4515
|
-
return new
|
|
4516
|
-
"
|
|
4517
|
-
"
|
|
4961
|
+
function cloudNotLaunchedError() {
|
|
4962
|
+
return new OpensteerCloudError(
|
|
4963
|
+
"CLOUD_SESSION_NOT_FOUND",
|
|
4964
|
+
"Cloud session is not connected. Call launch() first."
|
|
4518
4965
|
);
|
|
4519
4966
|
}
|
|
4520
4967
|
|
|
4521
|
-
// src/
|
|
4968
|
+
// src/cloud/action-ws-client.ts
|
|
4522
4969
|
import WebSocket from "ws";
|
|
4523
4970
|
var ActionWsClient = class _ActionWsClient {
|
|
4524
4971
|
ws;
|
|
@@ -4536,18 +4983,18 @@ var ActionWsClient = class _ActionWsClient {
|
|
|
4536
4983
|
});
|
|
4537
4984
|
ws.on("error", (error) => {
|
|
4538
4985
|
this.rejectAll(
|
|
4539
|
-
new
|
|
4540
|
-
"
|
|
4541
|
-
`
|
|
4986
|
+
new OpensteerCloudError(
|
|
4987
|
+
"CLOUD_TRANSPORT_ERROR",
|
|
4988
|
+
`Cloud action websocket error: ${error.message}`
|
|
4542
4989
|
)
|
|
4543
4990
|
);
|
|
4544
4991
|
});
|
|
4545
4992
|
ws.on("close", () => {
|
|
4546
4993
|
this.closed = true;
|
|
4547
4994
|
this.rejectAll(
|
|
4548
|
-
new
|
|
4549
|
-
"
|
|
4550
|
-
"
|
|
4995
|
+
new OpensteerCloudError(
|
|
4996
|
+
"CLOUD_SESSION_CLOSED",
|
|
4997
|
+
"Cloud action websocket closed."
|
|
4551
4998
|
)
|
|
4552
4999
|
);
|
|
4553
5000
|
});
|
|
@@ -4559,8 +5006,8 @@ var ActionWsClient = class _ActionWsClient {
|
|
|
4559
5006
|
ws.once("open", () => resolve());
|
|
4560
5007
|
ws.once("error", (error) => {
|
|
4561
5008
|
reject(
|
|
4562
|
-
new
|
|
4563
|
-
"
|
|
5009
|
+
new OpensteerCloudError(
|
|
5010
|
+
"CLOUD_TRANSPORT_ERROR",
|
|
4564
5011
|
`Failed to connect action websocket: ${error.message}`
|
|
4565
5012
|
)
|
|
4566
5013
|
);
|
|
@@ -4570,9 +5017,9 @@ var ActionWsClient = class _ActionWsClient {
|
|
|
4570
5017
|
}
|
|
4571
5018
|
async request(method, args) {
|
|
4572
5019
|
if (this.closed || this.ws.readyState !== WebSocket.OPEN) {
|
|
4573
|
-
throw new
|
|
4574
|
-
"
|
|
4575
|
-
"
|
|
5020
|
+
throw new OpensteerCloudError(
|
|
5021
|
+
"CLOUD_SESSION_CLOSED",
|
|
5022
|
+
"Cloud action websocket is closed."
|
|
4576
5023
|
);
|
|
4577
5024
|
}
|
|
4578
5025
|
const id = this.nextRequestId;
|
|
@@ -4591,8 +5038,8 @@ var ActionWsClient = class _ActionWsClient {
|
|
|
4591
5038
|
this.ws.send(JSON.stringify(payload));
|
|
4592
5039
|
} catch (error) {
|
|
4593
5040
|
this.pending.delete(id);
|
|
4594
|
-
const message = error instanceof Error ? error.message : "Failed to send
|
|
4595
|
-
throw new
|
|
5041
|
+
const message = error instanceof Error ? error.message : "Failed to send cloud action request.";
|
|
5042
|
+
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
4596
5043
|
}
|
|
4597
5044
|
return await resultPromise;
|
|
4598
5045
|
}
|
|
@@ -4610,9 +5057,9 @@ var ActionWsClient = class _ActionWsClient {
|
|
|
4610
5057
|
parsed = JSON.parse(rawDataToUtf8(raw));
|
|
4611
5058
|
} catch {
|
|
4612
5059
|
this.rejectAll(
|
|
4613
|
-
new
|
|
4614
|
-
"
|
|
4615
|
-
"Invalid
|
|
5060
|
+
new OpensteerCloudError(
|
|
5061
|
+
"CLOUD_TRANSPORT_ERROR",
|
|
5062
|
+
"Invalid cloud action response payload."
|
|
4616
5063
|
)
|
|
4617
5064
|
);
|
|
4618
5065
|
return;
|
|
@@ -4625,7 +5072,7 @@ var ActionWsClient = class _ActionWsClient {
|
|
|
4625
5072
|
return;
|
|
4626
5073
|
}
|
|
4627
5074
|
pending.reject(
|
|
4628
|
-
new
|
|
5075
|
+
new OpensteerCloudError(
|
|
4629
5076
|
parsed.code,
|
|
4630
5077
|
parsed.error,
|
|
4631
5078
|
void 0,
|
|
@@ -4653,7 +5100,7 @@ function withTokenQuery(wsUrl, token) {
|
|
|
4653
5100
|
return url.toString();
|
|
4654
5101
|
}
|
|
4655
5102
|
|
|
4656
|
-
// src/
|
|
5103
|
+
// src/cloud/local-cache-sync.ts
|
|
4657
5104
|
import fs2 from "fs";
|
|
4658
5105
|
import path3 from "path";
|
|
4659
5106
|
function collectLocalSelectorCacheEntries(storage) {
|
|
@@ -4777,26 +5224,26 @@ function dedupeNewest(entries) {
|
|
|
4777
5224
|
return [...byKey.values()];
|
|
4778
5225
|
}
|
|
4779
5226
|
|
|
4780
|
-
// src/
|
|
5227
|
+
// src/cloud/cdp-client.ts
|
|
4781
5228
|
import {
|
|
4782
5229
|
chromium
|
|
4783
5230
|
} from "playwright";
|
|
4784
|
-
var
|
|
5231
|
+
var CloudCdpClient = class {
|
|
4785
5232
|
async connect(args) {
|
|
4786
5233
|
const endpoint = withTokenQuery2(args.wsUrl, args.token);
|
|
4787
5234
|
let browser;
|
|
4788
5235
|
try {
|
|
4789
5236
|
browser = await chromium.connectOverCDP(endpoint);
|
|
4790
5237
|
} catch (error) {
|
|
4791
|
-
const message = error instanceof Error ? error.message : "Failed to connect to
|
|
4792
|
-
throw new
|
|
5238
|
+
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
5239
|
+
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
4793
5240
|
}
|
|
4794
5241
|
const context = browser.contexts()[0];
|
|
4795
5242
|
if (!context) {
|
|
4796
5243
|
await browser.close();
|
|
4797
|
-
throw new
|
|
4798
|
-
"
|
|
4799
|
-
"
|
|
5244
|
+
throw new OpensteerCloudError(
|
|
5245
|
+
"CLOUD_INTERNAL",
|
|
5246
|
+
"Cloud browser returned no context."
|
|
4800
5247
|
);
|
|
4801
5248
|
}
|
|
4802
5249
|
const page = context.pages()[0] || await context.newPage();
|
|
@@ -4809,34 +5256,46 @@ function withTokenQuery2(wsUrl, token) {
|
|
|
4809
5256
|
return url.toString();
|
|
4810
5257
|
}
|
|
4811
5258
|
|
|
4812
|
-
// src/
|
|
5259
|
+
// src/cloud/session-client.ts
|
|
4813
5260
|
var CACHE_IMPORT_BATCH_SIZE = 200;
|
|
4814
|
-
var
|
|
5261
|
+
var CloudSessionClient = class {
|
|
4815
5262
|
baseUrl;
|
|
4816
5263
|
key;
|
|
4817
|
-
|
|
5264
|
+
authScheme;
|
|
5265
|
+
constructor(baseUrl, key, authScheme = "api-key") {
|
|
4818
5266
|
this.baseUrl = normalizeBaseUrl(baseUrl);
|
|
4819
5267
|
this.key = key;
|
|
5268
|
+
this.authScheme = authScheme;
|
|
4820
5269
|
}
|
|
4821
5270
|
async create(request) {
|
|
4822
5271
|
const response = await fetch(`${this.baseUrl}/sessions`, {
|
|
4823
5272
|
method: "POST",
|
|
4824
5273
|
headers: {
|
|
4825
5274
|
"content-type": "application/json",
|
|
4826
|
-
|
|
5275
|
+
...this.authHeaders()
|
|
4827
5276
|
},
|
|
4828
5277
|
body: JSON.stringify(request)
|
|
4829
5278
|
});
|
|
4830
5279
|
if (!response.ok) {
|
|
4831
5280
|
throw await parseHttpError(response);
|
|
4832
5281
|
}
|
|
4833
|
-
|
|
5282
|
+
let body;
|
|
5283
|
+
try {
|
|
5284
|
+
body = await response.json();
|
|
5285
|
+
} catch {
|
|
5286
|
+
throw new OpensteerCloudError(
|
|
5287
|
+
"CLOUD_CONTRACT_MISMATCH",
|
|
5288
|
+
"Invalid cloud session create response: expected a JSON object.",
|
|
5289
|
+
response.status
|
|
5290
|
+
);
|
|
5291
|
+
}
|
|
5292
|
+
return parseCreateResponse(body, response.status);
|
|
4834
5293
|
}
|
|
4835
5294
|
async close(sessionId) {
|
|
4836
5295
|
const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
|
|
4837
5296
|
method: "DELETE",
|
|
4838
5297
|
headers: {
|
|
4839
|
-
|
|
5298
|
+
...this.authHeaders()
|
|
4840
5299
|
}
|
|
4841
5300
|
});
|
|
4842
5301
|
if (response.status === 204) {
|
|
@@ -4866,7 +5325,7 @@ var RemoteSessionClient = class {
|
|
|
4866
5325
|
method: "POST",
|
|
4867
5326
|
headers: {
|
|
4868
5327
|
"content-type": "application/json",
|
|
4869
|
-
|
|
5328
|
+
...this.authHeaders()
|
|
4870
5329
|
},
|
|
4871
5330
|
body: JSON.stringify({ entries })
|
|
4872
5331
|
});
|
|
@@ -4875,10 +5334,148 @@ var RemoteSessionClient = class {
|
|
|
4875
5334
|
}
|
|
4876
5335
|
return await response.json();
|
|
4877
5336
|
}
|
|
5337
|
+
authHeaders() {
|
|
5338
|
+
if (this.authScheme === "bearer") {
|
|
5339
|
+
return {
|
|
5340
|
+
authorization: `Bearer ${this.key}`
|
|
5341
|
+
};
|
|
5342
|
+
}
|
|
5343
|
+
return {
|
|
5344
|
+
"x-api-key": this.key
|
|
5345
|
+
};
|
|
5346
|
+
}
|
|
4878
5347
|
};
|
|
4879
5348
|
function normalizeBaseUrl(baseUrl) {
|
|
4880
5349
|
return baseUrl.replace(/\/+$/, "");
|
|
4881
5350
|
}
|
|
5351
|
+
function parseCreateResponse(body, status) {
|
|
5352
|
+
const root = requireObject(
|
|
5353
|
+
body,
|
|
5354
|
+
"Invalid cloud session create response: expected a JSON object.",
|
|
5355
|
+
status
|
|
5356
|
+
);
|
|
5357
|
+
const sessionId = requireString(root, "sessionId", status);
|
|
5358
|
+
const actionWsUrl = requireString(root, "actionWsUrl", status);
|
|
5359
|
+
const cdpWsUrl = requireString(root, "cdpWsUrl", status);
|
|
5360
|
+
const actionToken = requireString(root, "actionToken", status);
|
|
5361
|
+
const cdpToken = requireString(root, "cdpToken", status);
|
|
5362
|
+
const cloudSessionUrl = requireString(root, "cloudSessionUrl", status);
|
|
5363
|
+
const cloudSessionRoot = requireObject(
|
|
5364
|
+
root.cloudSession,
|
|
5365
|
+
"Invalid cloud session create response: cloudSession must be an object.",
|
|
5366
|
+
status
|
|
5367
|
+
);
|
|
5368
|
+
const cloudSession = {
|
|
5369
|
+
sessionId: requireString(cloudSessionRoot, "sessionId", status, "cloudSession"),
|
|
5370
|
+
workspaceId: requireString(
|
|
5371
|
+
cloudSessionRoot,
|
|
5372
|
+
"workspaceId",
|
|
5373
|
+
status,
|
|
5374
|
+
"cloudSession"
|
|
5375
|
+
),
|
|
5376
|
+
state: requireString(cloudSessionRoot, "state", status, "cloudSession"),
|
|
5377
|
+
createdAt: requireNumber(cloudSessionRoot, "createdAt", status, "cloudSession"),
|
|
5378
|
+
sourceType: requireSourceType(cloudSessionRoot, "sourceType", status, "cloudSession"),
|
|
5379
|
+
sourceRef: optionalString(cloudSessionRoot, "sourceRef", status, "cloudSession"),
|
|
5380
|
+
label: optionalString(cloudSessionRoot, "label", status, "cloudSession")
|
|
5381
|
+
};
|
|
5382
|
+
const expiresAt = optionalNumber(root, "expiresAt", status);
|
|
5383
|
+
return {
|
|
5384
|
+
sessionId,
|
|
5385
|
+
actionWsUrl,
|
|
5386
|
+
cdpWsUrl,
|
|
5387
|
+
actionToken,
|
|
5388
|
+
cdpToken,
|
|
5389
|
+
expiresAt,
|
|
5390
|
+
cloudSessionUrl,
|
|
5391
|
+
cloudSession
|
|
5392
|
+
};
|
|
5393
|
+
}
|
|
5394
|
+
function requireObject(value, message, status) {
|
|
5395
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5396
|
+
throw new OpensteerCloudError("CLOUD_CONTRACT_MISMATCH", message, status);
|
|
5397
|
+
}
|
|
5398
|
+
return value;
|
|
5399
|
+
}
|
|
5400
|
+
function requireString(source, field, status, parent) {
|
|
5401
|
+
const value = source[field];
|
|
5402
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
5403
|
+
throw new OpensteerCloudError(
|
|
5404
|
+
"CLOUD_CONTRACT_MISMATCH",
|
|
5405
|
+
`Invalid cloud session create response: ${formatFieldPath(
|
|
5406
|
+
field,
|
|
5407
|
+
parent
|
|
5408
|
+
)} must be a non-empty string.`,
|
|
5409
|
+
status
|
|
5410
|
+
);
|
|
5411
|
+
}
|
|
5412
|
+
return value;
|
|
5413
|
+
}
|
|
5414
|
+
function requireNumber(source, field, status, parent) {
|
|
5415
|
+
const value = source[field];
|
|
5416
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5417
|
+
throw new OpensteerCloudError(
|
|
5418
|
+
"CLOUD_CONTRACT_MISMATCH",
|
|
5419
|
+
`Invalid cloud session create response: ${formatFieldPath(
|
|
5420
|
+
field,
|
|
5421
|
+
parent
|
|
5422
|
+
)} must be a finite number.`,
|
|
5423
|
+
status
|
|
5424
|
+
);
|
|
5425
|
+
}
|
|
5426
|
+
return value;
|
|
5427
|
+
}
|
|
5428
|
+
function optionalString(source, field, status, parent) {
|
|
5429
|
+
const value = source[field];
|
|
5430
|
+
if (value == null) {
|
|
5431
|
+
return void 0;
|
|
5432
|
+
}
|
|
5433
|
+
if (typeof value !== "string") {
|
|
5434
|
+
throw new OpensteerCloudError(
|
|
5435
|
+
"CLOUD_CONTRACT_MISMATCH",
|
|
5436
|
+
`Invalid cloud session create response: ${formatFieldPath(
|
|
5437
|
+
field,
|
|
5438
|
+
parent
|
|
5439
|
+
)} must be a string when present.`,
|
|
5440
|
+
status
|
|
5441
|
+
);
|
|
5442
|
+
}
|
|
5443
|
+
return value;
|
|
5444
|
+
}
|
|
5445
|
+
function optionalNumber(source, field, status, parent) {
|
|
5446
|
+
const value = source[field];
|
|
5447
|
+
if (value == null) {
|
|
5448
|
+
return void 0;
|
|
5449
|
+
}
|
|
5450
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
5451
|
+
throw new OpensteerCloudError(
|
|
5452
|
+
"CLOUD_CONTRACT_MISMATCH",
|
|
5453
|
+
`Invalid cloud session create response: ${formatFieldPath(
|
|
5454
|
+
field,
|
|
5455
|
+
parent
|
|
5456
|
+
)} must be a finite number when present.`,
|
|
5457
|
+
status
|
|
5458
|
+
);
|
|
5459
|
+
}
|
|
5460
|
+
return value;
|
|
5461
|
+
}
|
|
5462
|
+
function requireSourceType(source, field, status, parent) {
|
|
5463
|
+
const value = source[field];
|
|
5464
|
+
if (value === "agent-thread" || value === "agent-run" || value === "local-cloud" || value === "manual") {
|
|
5465
|
+
return value;
|
|
5466
|
+
}
|
|
5467
|
+
throw new OpensteerCloudError(
|
|
5468
|
+
"CLOUD_CONTRACT_MISMATCH",
|
|
5469
|
+
`Invalid cloud session create response: ${formatFieldPath(
|
|
5470
|
+
field,
|
|
5471
|
+
parent
|
|
5472
|
+
)} must be one of "agent-thread", "agent-run", "local-cloud", or "manual".`,
|
|
5473
|
+
status
|
|
5474
|
+
);
|
|
5475
|
+
}
|
|
5476
|
+
function formatFieldPath(field, parent) {
|
|
5477
|
+
return parent ? `"${parent}.${field}"` : `"${field}"`;
|
|
5478
|
+
}
|
|
4882
5479
|
function zeroImportResponse() {
|
|
4883
5480
|
return {
|
|
4884
5481
|
imported: 0,
|
|
@@ -4902,19 +5499,19 @@ async function parseHttpError(response) {
|
|
|
4902
5499
|
} catch {
|
|
4903
5500
|
body = null;
|
|
4904
5501
|
}
|
|
4905
|
-
const code = typeof body?.code === "string" ?
|
|
4906
|
-
const message = typeof body?.error === "string" ? body.error : `
|
|
4907
|
-
return new
|
|
5502
|
+
const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
|
|
5503
|
+
const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
|
|
5504
|
+
return new OpensteerCloudError(code, message, response.status, body?.details);
|
|
4908
5505
|
}
|
|
4909
|
-
function
|
|
4910
|
-
if (code === "
|
|
5506
|
+
function toCloudErrorCode(code) {
|
|
5507
|
+
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") {
|
|
4911
5508
|
return code;
|
|
4912
5509
|
}
|
|
4913
|
-
return "
|
|
5510
|
+
return "CLOUD_TRANSPORT_ERROR";
|
|
4914
5511
|
}
|
|
4915
5512
|
|
|
4916
5513
|
// src/opensteer.ts
|
|
4917
|
-
import { createHash } from "crypto";
|
|
5514
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
4918
5515
|
|
|
4919
5516
|
// src/browser/pool.ts
|
|
4920
5517
|
import {
|
|
@@ -5330,7 +5927,7 @@ var BrowserPool = class {
|
|
|
5330
5927
|
const context = contexts[0];
|
|
5331
5928
|
const pages = context.pages();
|
|
5332
5929
|
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
5333
|
-
return { browser, context, page,
|
|
5930
|
+
return { browser, context, page, isExternal: true };
|
|
5334
5931
|
} catch (error) {
|
|
5335
5932
|
if (browser) {
|
|
5336
5933
|
await browser.close().catch(() => void 0);
|
|
@@ -5365,7 +5962,7 @@ var BrowserPool = class {
|
|
|
5365
5962
|
context = await browser.newContext(options.context || {});
|
|
5366
5963
|
page = await context.newPage();
|
|
5367
5964
|
}
|
|
5368
|
-
return { browser, context, page,
|
|
5965
|
+
return { browser, context, page, isExternal: false };
|
|
5369
5966
|
}
|
|
5370
5967
|
async launchSandbox(options) {
|
|
5371
5968
|
const browser = await chromium2.launch({
|
|
@@ -5376,7 +5973,7 @@ var BrowserPool = class {
|
|
|
5376
5973
|
const context = await browser.newContext(options.context || {});
|
|
5377
5974
|
const page = await context.newPage();
|
|
5378
5975
|
this.browser = browser;
|
|
5379
|
-
return { browser, context, page,
|
|
5976
|
+
return { browser, context, page, isExternal: false };
|
|
5380
5977
|
}
|
|
5381
5978
|
};
|
|
5382
5979
|
|
|
@@ -5413,28 +6010,27 @@ function assertNoLegacyAiConfig(source, config) {
|
|
|
5413
6010
|
);
|
|
5414
6011
|
}
|
|
5415
6012
|
}
|
|
5416
|
-
function
|
|
6013
|
+
function assertNoLegacyRuntimeConfig(source, config) {
|
|
5417
6014
|
if (!config || typeof config !== "object") return;
|
|
5418
6015
|
const configRecord = config;
|
|
5419
6016
|
if (hasOwn(configRecord, "runtime")) {
|
|
5420
6017
|
throw new Error(
|
|
5421
|
-
`Legacy "runtime" config is no longer supported in ${source}. Use top-level "
|
|
6018
|
+
`Legacy "runtime" config is no longer supported in ${source}. Use top-level "cloud" instead.`
|
|
5422
6019
|
);
|
|
5423
6020
|
}
|
|
5424
|
-
if (hasOwn(configRecord, "
|
|
6021
|
+
if (hasOwn(configRecord, "mode")) {
|
|
5425
6022
|
throw new Error(
|
|
5426
|
-
`Top-level "
|
|
6023
|
+
`Top-level "mode" config is no longer supported in ${source}. Use "cloud: true" to enable cloud mode.`
|
|
5427
6024
|
);
|
|
5428
6025
|
}
|
|
5429
|
-
|
|
5430
|
-
if (typeof remoteValue === "boolean") {
|
|
6026
|
+
if (hasOwn(configRecord, "remote")) {
|
|
5431
6027
|
throw new Error(
|
|
5432
|
-
`
|
|
6028
|
+
`Top-level "remote" config is no longer supported in ${source}. Use "cloud" options instead.`
|
|
5433
6029
|
);
|
|
5434
6030
|
}
|
|
5435
|
-
if (
|
|
6031
|
+
if (hasOwn(configRecord, "apiKey")) {
|
|
5436
6032
|
throw new Error(
|
|
5437
|
-
`
|
|
6033
|
+
`Top-level "apiKey" config is not supported in ${source}. Use "cloud.apiKey" instead.`
|
|
5438
6034
|
);
|
|
5439
6035
|
}
|
|
5440
6036
|
}
|
|
@@ -5480,20 +6076,52 @@ function parseNumber(value) {
|
|
|
5480
6076
|
if (!Number.isFinite(parsed)) return void 0;
|
|
5481
6077
|
return parsed;
|
|
5482
6078
|
}
|
|
5483
|
-
function
|
|
6079
|
+
function parseRuntimeMode(value, source) {
|
|
5484
6080
|
if (value == null) return void 0;
|
|
5485
6081
|
if (typeof value !== "string") {
|
|
5486
6082
|
throw new Error(
|
|
5487
|
-
`Invalid ${source} value "${String(value)}". Use "local" or "
|
|
6083
|
+
`Invalid ${source} value "${String(value)}". Use "local" or "cloud".`
|
|
5488
6084
|
);
|
|
5489
6085
|
}
|
|
5490
6086
|
const normalized = value.trim().toLowerCase();
|
|
5491
6087
|
if (!normalized) return void 0;
|
|
5492
|
-
if (normalized === "local" || normalized === "
|
|
6088
|
+
if (normalized === "local" || normalized === "cloud") {
|
|
5493
6089
|
return normalized;
|
|
5494
6090
|
}
|
|
5495
6091
|
throw new Error(
|
|
5496
|
-
`Invalid ${source} value "${value}". Use "local" or "
|
|
6092
|
+
`Invalid ${source} value "${value}". Use "local" or "cloud".`
|
|
6093
|
+
);
|
|
6094
|
+
}
|
|
6095
|
+
function parseAuthScheme(value, source) {
|
|
6096
|
+
if (value == null) return void 0;
|
|
6097
|
+
if (typeof value !== "string") {
|
|
6098
|
+
throw new Error(
|
|
6099
|
+
`Invalid ${source} value "${String(value)}". Use "api-key" or "bearer".`
|
|
6100
|
+
);
|
|
6101
|
+
}
|
|
6102
|
+
const normalized = value.trim().toLowerCase();
|
|
6103
|
+
if (!normalized) return void 0;
|
|
6104
|
+
if (normalized === "api-key" || normalized === "bearer") {
|
|
6105
|
+
return normalized;
|
|
6106
|
+
}
|
|
6107
|
+
throw new Error(
|
|
6108
|
+
`Invalid ${source} value "${value}". Use "api-key" or "bearer".`
|
|
6109
|
+
);
|
|
6110
|
+
}
|
|
6111
|
+
function parseCloudAnnounce(value, source) {
|
|
6112
|
+
if (value == null) return void 0;
|
|
6113
|
+
if (typeof value !== "string") {
|
|
6114
|
+
throw new Error(
|
|
6115
|
+
`Invalid ${source} value "${String(value)}". Use "always", "off", or "tty".`
|
|
6116
|
+
);
|
|
6117
|
+
}
|
|
6118
|
+
const normalized = value.trim().toLowerCase();
|
|
6119
|
+
if (!normalized) return void 0;
|
|
6120
|
+
if (normalized === "always" || normalized === "off" || normalized === "tty") {
|
|
6121
|
+
return normalized;
|
|
6122
|
+
}
|
|
6123
|
+
throw new Error(
|
|
6124
|
+
`Invalid ${source} value "${value}". Use "always", "off", or "tty".`
|
|
5497
6125
|
);
|
|
5498
6126
|
}
|
|
5499
6127
|
function resolveOpensteerApiKey() {
|
|
@@ -5501,29 +6129,46 @@ function resolveOpensteerApiKey() {
|
|
|
5501
6129
|
if (!value) return void 0;
|
|
5502
6130
|
return value;
|
|
5503
6131
|
}
|
|
5504
|
-
function
|
|
6132
|
+
function resolveOpensteerAuthScheme() {
|
|
6133
|
+
return parseAuthScheme(
|
|
6134
|
+
process.env.OPENSTEER_AUTH_SCHEME,
|
|
6135
|
+
"OPENSTEER_AUTH_SCHEME"
|
|
6136
|
+
);
|
|
6137
|
+
}
|
|
6138
|
+
function normalizeCloudOptions(value) {
|
|
5505
6139
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5506
6140
|
return void 0;
|
|
5507
6141
|
}
|
|
5508
6142
|
return value;
|
|
5509
6143
|
}
|
|
5510
|
-
function
|
|
5511
|
-
|
|
5512
|
-
if (
|
|
6144
|
+
function parseCloudEnabled(value, source) {
|
|
6145
|
+
if (value == null) return void 0;
|
|
6146
|
+
if (typeof value === "boolean") return value;
|
|
6147
|
+
if (typeof value === "object" && !Array.isArray(value)) return true;
|
|
6148
|
+
throw new Error(
|
|
6149
|
+
`Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
|
|
6150
|
+
);
|
|
6151
|
+
}
|
|
6152
|
+
function resolveCloudSelection(config) {
|
|
6153
|
+
const configCloud = parseCloudEnabled(config.cloud, "cloud");
|
|
6154
|
+
if (configCloud !== void 0) {
|
|
5513
6155
|
return {
|
|
5514
|
-
|
|
5515
|
-
source: "config.
|
|
6156
|
+
cloud: configCloud,
|
|
6157
|
+
source: "config.cloud"
|
|
5516
6158
|
};
|
|
5517
6159
|
}
|
|
5518
|
-
const envMode =
|
|
6160
|
+
const envMode = parseRuntimeMode(
|
|
6161
|
+
process.env.OPENSTEER_MODE,
|
|
6162
|
+
"OPENSTEER_MODE"
|
|
6163
|
+
);
|
|
5519
6164
|
if (envMode) {
|
|
5520
6165
|
return {
|
|
5521
|
-
|
|
6166
|
+
cloud: envMode === "cloud",
|
|
5522
6167
|
source: "env.OPENSTEER_MODE"
|
|
5523
6168
|
};
|
|
5524
6169
|
}
|
|
5525
6170
|
return {
|
|
5526
|
-
|
|
6171
|
+
cloud: false,
|
|
5527
6172
|
source: "default"
|
|
5528
6173
|
};
|
|
5529
6174
|
}
|
|
@@ -5539,11 +6184,11 @@ function resolveConfig(input = {}) {
|
|
|
5539
6184
|
);
|
|
5540
6185
|
}
|
|
5541
6186
|
assertNoLegacyAiConfig("Opensteer constructor config", input);
|
|
5542
|
-
|
|
6187
|
+
assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
|
|
5543
6188
|
const rootDir = input.storage?.rootDir ?? DEFAULT_CONFIG.storage.rootDir ?? process.cwd();
|
|
5544
6189
|
const fileConfig = loadConfigFile(rootDir);
|
|
5545
6190
|
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
5546
|
-
|
|
6191
|
+
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
5547
6192
|
const envConfig = {
|
|
5548
6193
|
browser: {
|
|
5549
6194
|
headless: parseBool(process.env.OPENSTEER_HEADLESS),
|
|
@@ -5560,20 +6205,39 @@ function resolveConfig(input = {}) {
|
|
|
5560
6205
|
const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
|
|
5561
6206
|
const resolved = mergeDeep(mergedWithEnv, input);
|
|
5562
6207
|
const envApiKey = resolveOpensteerApiKey();
|
|
5563
|
-
const
|
|
5564
|
-
const
|
|
5565
|
-
|
|
6208
|
+
const envAuthScheme = resolveOpensteerAuthScheme();
|
|
6209
|
+
const envCloudAnnounce = parseCloudAnnounce(
|
|
6210
|
+
process.env.OPENSTEER_REMOTE_ANNOUNCE,
|
|
6211
|
+
"OPENSTEER_REMOTE_ANNOUNCE"
|
|
6212
|
+
);
|
|
6213
|
+
const inputCloudOptions = normalizeCloudOptions(input.cloud);
|
|
6214
|
+
const inputAuthScheme = parseAuthScheme(
|
|
6215
|
+
inputCloudOptions?.authScheme,
|
|
6216
|
+
"cloud.authScheme"
|
|
5566
6217
|
);
|
|
5567
|
-
const
|
|
5568
|
-
|
|
6218
|
+
const inputCloudAnnounce = parseCloudAnnounce(
|
|
6219
|
+
inputCloudOptions?.announce,
|
|
6220
|
+
"cloud.announce"
|
|
6221
|
+
);
|
|
6222
|
+
const inputHasCloudApiKey = Boolean(
|
|
6223
|
+
inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
|
|
6224
|
+
);
|
|
6225
|
+
const cloudSelection = resolveCloudSelection({
|
|
6226
|
+
cloud: resolved.cloud
|
|
5569
6227
|
});
|
|
5570
|
-
if (
|
|
5571
|
-
const
|
|
5572
|
-
|
|
6228
|
+
if (cloudSelection.cloud) {
|
|
6229
|
+
const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
|
|
6230
|
+
const authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
|
|
6231
|
+
const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
|
|
6232
|
+
resolved.cloud = {
|
|
6233
|
+
...resolvedCloud,
|
|
6234
|
+
authScheme,
|
|
6235
|
+
announce
|
|
6236
|
+
};
|
|
5573
6237
|
}
|
|
5574
|
-
if (envApiKey &&
|
|
5575
|
-
resolved.
|
|
5576
|
-
...
|
|
6238
|
+
if (envApiKey && cloudSelection.cloud && !inputHasCloudApiKey) {
|
|
6239
|
+
resolved.cloud = {
|
|
6240
|
+
...normalizeCloudOptions(resolved.cloud) ?? {},
|
|
5577
6241
|
apiKey: envApiKey
|
|
5578
6242
|
};
|
|
5579
6243
|
}
|
|
@@ -5648,7 +6312,26 @@ var ACTION_WAIT_PROFILES = {
|
|
|
5648
6312
|
type: ROBUST_PROFILE
|
|
5649
6313
|
};
|
|
5650
6314
|
var NETWORK_POLL_MS = 50;
|
|
5651
|
-
var
|
|
6315
|
+
var NETWORK_RELAX_AFTER_MS = 1800;
|
|
6316
|
+
var RELAXED_ALLOWED_PENDING = 2;
|
|
6317
|
+
var HEAVY_VISUAL_REQUEST_WINDOW_MS = 5e3;
|
|
6318
|
+
var TRACKED_RESOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
6319
|
+
"document",
|
|
6320
|
+
"fetch",
|
|
6321
|
+
"xhr",
|
|
6322
|
+
"stylesheet",
|
|
6323
|
+
"image",
|
|
6324
|
+
"font",
|
|
6325
|
+
"media"
|
|
6326
|
+
]);
|
|
6327
|
+
var HEAVY_RESOURCE_TYPES = /* @__PURE__ */ new Set(["document", "fetch", "xhr"]);
|
|
6328
|
+
var HEAVY_VISUAL_RESOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
6329
|
+
"stylesheet",
|
|
6330
|
+
"image",
|
|
6331
|
+
"font",
|
|
6332
|
+
"media"
|
|
6333
|
+
]);
|
|
6334
|
+
var IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["websocket", "eventsource", "manifest"]);
|
|
5652
6335
|
var NOOP_SESSION = {
|
|
5653
6336
|
async wait() {
|
|
5654
6337
|
},
|
|
@@ -5658,7 +6341,7 @@ var NOOP_SESSION = {
|
|
|
5658
6341
|
function createPostActionWaitSession(page, action, override) {
|
|
5659
6342
|
const profile = resolveActionWaitProfile(action, override);
|
|
5660
6343
|
if (!profile.enabled) return NOOP_SESSION;
|
|
5661
|
-
const tracker = profile.includeNetwork ? new
|
|
6344
|
+
const tracker = profile.includeNetwork ? new AdaptiveNetworkTracker(page) : null;
|
|
5662
6345
|
tracker?.start();
|
|
5663
6346
|
let settled = false;
|
|
5664
6347
|
return {
|
|
@@ -5666,19 +6349,32 @@ function createPostActionWaitSession(page, action, override) {
|
|
|
5666
6349
|
if (settled) return;
|
|
5667
6350
|
settled = true;
|
|
5668
6351
|
const deadline = Date.now() + profile.timeout;
|
|
5669
|
-
const
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
6352
|
+
const visualTimeout = profile.includeNetwork ? Math.min(
|
|
6353
|
+
profile.timeout,
|
|
6354
|
+
resolveNetworkBackedVisualTimeout(profile.settleMs)
|
|
6355
|
+
) : profile.timeout;
|
|
5673
6356
|
try {
|
|
5674
|
-
|
|
5675
|
-
waitForVisualStabilityAcrossFrames(page, {
|
|
5676
|
-
timeout:
|
|
6357
|
+
try {
|
|
6358
|
+
await waitForVisualStabilityAcrossFrames(page, {
|
|
6359
|
+
timeout: visualTimeout,
|
|
5677
6360
|
settleMs: profile.settleMs
|
|
5678
|
-
})
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
6361
|
+
});
|
|
6362
|
+
} catch (error) {
|
|
6363
|
+
if (isStealthWaitUnavailableError(error)) {
|
|
6364
|
+
throw error;
|
|
6365
|
+
}
|
|
6366
|
+
} finally {
|
|
6367
|
+
tracker?.freezeCollection();
|
|
6368
|
+
}
|
|
6369
|
+
if (tracker) {
|
|
6370
|
+
try {
|
|
6371
|
+
await tracker.waitForQuiet({
|
|
6372
|
+
deadline,
|
|
6373
|
+
quietMs: profile.networkQuietMs
|
|
6374
|
+
});
|
|
6375
|
+
} catch {
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
5682
6378
|
} finally {
|
|
5683
6379
|
tracker?.stop();
|
|
5684
6380
|
}
|
|
@@ -5718,53 +6414,70 @@ function normalizeMs(value, fallback) {
|
|
|
5718
6414
|
}
|
|
5719
6415
|
return Math.max(0, Math.floor(value));
|
|
5720
6416
|
}
|
|
5721
|
-
|
|
6417
|
+
function resolveNetworkBackedVisualTimeout(settleMs) {
|
|
6418
|
+
const derived = settleMs * 3 + 300;
|
|
6419
|
+
return Math.max(1200, Math.min(2500, derived));
|
|
6420
|
+
}
|
|
6421
|
+
var AdaptiveNetworkTracker = class {
|
|
5722
6422
|
constructor(page) {
|
|
5723
6423
|
this.page = page;
|
|
5724
6424
|
}
|
|
5725
|
-
pending = /* @__PURE__ */ new
|
|
6425
|
+
pending = /* @__PURE__ */ new Map();
|
|
5726
6426
|
started = false;
|
|
6427
|
+
collecting = false;
|
|
6428
|
+
startedAt = 0;
|
|
5727
6429
|
idleSince = Date.now();
|
|
5728
6430
|
start() {
|
|
5729
6431
|
if (this.started) return;
|
|
5730
6432
|
this.started = true;
|
|
6433
|
+
this.collecting = true;
|
|
6434
|
+
this.startedAt = Date.now();
|
|
6435
|
+
this.idleSince = this.startedAt;
|
|
5731
6436
|
this.page.on("request", this.handleRequestStarted);
|
|
5732
6437
|
this.page.on("requestfinished", this.handleRequestFinished);
|
|
5733
6438
|
this.page.on("requestfailed", this.handleRequestFinished);
|
|
5734
6439
|
}
|
|
6440
|
+
freezeCollection() {
|
|
6441
|
+
if (!this.started) return;
|
|
6442
|
+
this.collecting = false;
|
|
6443
|
+
}
|
|
5735
6444
|
stop() {
|
|
5736
6445
|
if (!this.started) return;
|
|
5737
6446
|
this.started = false;
|
|
6447
|
+
this.collecting = false;
|
|
5738
6448
|
this.page.off("request", this.handleRequestStarted);
|
|
5739
6449
|
this.page.off("requestfinished", this.handleRequestFinished);
|
|
5740
6450
|
this.page.off("requestfailed", this.handleRequestFinished);
|
|
5741
6451
|
this.pending.clear();
|
|
6452
|
+
this.startedAt = 0;
|
|
5742
6453
|
this.idleSince = Date.now();
|
|
5743
6454
|
}
|
|
5744
6455
|
async waitForQuiet(options) {
|
|
5745
6456
|
const quietMs = Math.max(0, options.quietMs);
|
|
5746
6457
|
if (quietMs === 0) return;
|
|
5747
6458
|
while (Date.now() < options.deadline) {
|
|
5748
|
-
|
|
6459
|
+
const now = Date.now();
|
|
6460
|
+
const allowedPending = this.resolveAllowedPending(now);
|
|
6461
|
+
if (this.pending.size <= allowedPending) {
|
|
5749
6462
|
if (this.idleSince === 0) {
|
|
5750
|
-
this.idleSince =
|
|
6463
|
+
this.idleSince = now;
|
|
5751
6464
|
}
|
|
5752
|
-
const idleFor =
|
|
6465
|
+
const idleFor = now - this.idleSince;
|
|
5753
6466
|
if (idleFor >= quietMs) {
|
|
5754
6467
|
return;
|
|
5755
6468
|
}
|
|
5756
6469
|
} else {
|
|
5757
6470
|
this.idleSince = 0;
|
|
5758
6471
|
}
|
|
5759
|
-
const remaining = Math.max(1, options.deadline -
|
|
5760
|
-
await
|
|
6472
|
+
const remaining = Math.max(1, options.deadline - now);
|
|
6473
|
+
await sleep2(Math.min(NETWORK_POLL_MS, remaining));
|
|
5761
6474
|
}
|
|
5762
6475
|
}
|
|
5763
6476
|
handleRequestStarted = (request) => {
|
|
5764
|
-
if (!this.started) return;
|
|
5765
|
-
const
|
|
5766
|
-
if (
|
|
5767
|
-
this.pending.
|
|
6477
|
+
if (!this.started || !this.collecting) return;
|
|
6478
|
+
const trackedRequest = this.classifyRequest(request);
|
|
6479
|
+
if (!trackedRequest) return;
|
|
6480
|
+
this.pending.set(request, trackedRequest);
|
|
5768
6481
|
this.idleSince = 0;
|
|
5769
6482
|
};
|
|
5770
6483
|
handleRequestFinished = (request) => {
|
|
@@ -5774,8 +6487,35 @@ var ScopedNetworkTracker = class {
|
|
|
5774
6487
|
this.idleSince = Date.now();
|
|
5775
6488
|
}
|
|
5776
6489
|
};
|
|
6490
|
+
classifyRequest(request) {
|
|
6491
|
+
const resourceType = request.resourceType().toLowerCase();
|
|
6492
|
+
if (IGNORED_RESOURCE_TYPES.has(resourceType)) return null;
|
|
6493
|
+
if (!TRACKED_RESOURCE_TYPES.has(resourceType)) return null;
|
|
6494
|
+
const frame = request.frame();
|
|
6495
|
+
if (!frame || frame !== this.page.mainFrame()) return null;
|
|
6496
|
+
return {
|
|
6497
|
+
resourceType,
|
|
6498
|
+
startedAt: Date.now()
|
|
6499
|
+
};
|
|
6500
|
+
}
|
|
6501
|
+
resolveAllowedPending(now) {
|
|
6502
|
+
const relaxed = now - this.startedAt >= NETWORK_RELAX_AFTER_MS ? RELAXED_ALLOWED_PENDING : 0;
|
|
6503
|
+
if (this.hasHeavyPending(now)) return 0;
|
|
6504
|
+
return relaxed;
|
|
6505
|
+
}
|
|
6506
|
+
hasHeavyPending(now) {
|
|
6507
|
+
for (const trackedRequest of this.pending.values()) {
|
|
6508
|
+
if (HEAVY_RESOURCE_TYPES.has(trackedRequest.resourceType)) {
|
|
6509
|
+
return true;
|
|
6510
|
+
}
|
|
6511
|
+
if (HEAVY_VISUAL_RESOURCE_TYPES.has(trackedRequest.resourceType) && now - trackedRequest.startedAt < HEAVY_VISUAL_REQUEST_WINDOW_MS) {
|
|
6512
|
+
return true;
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
return false;
|
|
6516
|
+
}
|
|
5777
6517
|
};
|
|
5778
|
-
async function
|
|
6518
|
+
async function sleep2(ms) {
|
|
5779
6519
|
await new Promise((resolve) => {
|
|
5780
6520
|
setTimeout(resolve, ms);
|
|
5781
6521
|
});
|
|
@@ -6893,22 +7633,35 @@ function clonePersistedExtractNode(node) {
|
|
|
6893
7633
|
return JSON.parse(JSON.stringify(node));
|
|
6894
7634
|
}
|
|
6895
7635
|
|
|
6896
|
-
// src/
|
|
6897
|
-
var
|
|
6898
|
-
|
|
7636
|
+
// src/cloud/runtime.ts
|
|
7637
|
+
var DEFAULT_CLOUD_BASE_URL = "https://remote.opensteer.com";
|
|
7638
|
+
var DEFAULT_CLOUD_APP_URL = "https://opensteer.com";
|
|
7639
|
+
function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key", appUrl = resolveCloudAppUrl()) {
|
|
6899
7640
|
return {
|
|
6900
|
-
sessionClient: new
|
|
6901
|
-
cdpClient: new
|
|
7641
|
+
sessionClient: new CloudSessionClient(baseUrl, key, authScheme),
|
|
7642
|
+
cdpClient: new CloudCdpClient(),
|
|
7643
|
+
appUrl: normalizeCloudAppUrl(appUrl),
|
|
6902
7644
|
actionClient: null,
|
|
6903
|
-
sessionId: null
|
|
7645
|
+
sessionId: null,
|
|
7646
|
+
localRunId: null,
|
|
7647
|
+
cloudSessionUrl: null
|
|
6904
7648
|
};
|
|
6905
7649
|
}
|
|
6906
|
-
function
|
|
7650
|
+
function resolveCloudBaseUrl() {
|
|
6907
7651
|
const value = process.env.OPENSTEER_BASE_URL?.trim();
|
|
6908
|
-
if (!value) return
|
|
7652
|
+
if (!value) return DEFAULT_CLOUD_BASE_URL;
|
|
7653
|
+
return value.replace(/\/+$/, "");
|
|
7654
|
+
}
|
|
7655
|
+
function resolveCloudAppUrl() {
|
|
7656
|
+
const value = process.env.OPENSTEER_APP_URL?.trim();
|
|
7657
|
+
if (!value) return DEFAULT_CLOUD_APP_URL;
|
|
7658
|
+
return normalizeCloudAppUrl(value);
|
|
7659
|
+
}
|
|
7660
|
+
function normalizeCloudAppUrl(value) {
|
|
7661
|
+
if (!value) return null;
|
|
6909
7662
|
return value.replace(/\/+$/, "");
|
|
6910
7663
|
}
|
|
6911
|
-
function
|
|
7664
|
+
function readCloudActionDescription(payload) {
|
|
6912
7665
|
const description = payload.description;
|
|
6913
7666
|
if (typeof description !== "string") return void 0;
|
|
6914
7667
|
const normalized = description.trim();
|
|
@@ -6916,7 +7669,7 @@ function readRemoteActionDescription(payload) {
|
|
|
6916
7669
|
}
|
|
6917
7670
|
|
|
6918
7671
|
// src/opensteer.ts
|
|
6919
|
-
var
|
|
7672
|
+
var CLOUD_INTERACTION_METHODS = /* @__PURE__ */ new Set([
|
|
6920
7673
|
"click",
|
|
6921
7674
|
"dblclick",
|
|
6922
7675
|
"rightclick",
|
|
@@ -6933,7 +7686,7 @@ var Opensteer = class _Opensteer {
|
|
|
6933
7686
|
namespace;
|
|
6934
7687
|
storage;
|
|
6935
7688
|
pool;
|
|
6936
|
-
|
|
7689
|
+
cloud;
|
|
6937
7690
|
browser = null;
|
|
6938
7691
|
pageRef = null;
|
|
6939
7692
|
contextRef = null;
|
|
@@ -6941,8 +7694,8 @@ var Opensteer = class _Opensteer {
|
|
|
6941
7694
|
snapshotCache = null;
|
|
6942
7695
|
constructor(config = {}) {
|
|
6943
7696
|
const resolved = resolveConfig(config);
|
|
6944
|
-
const
|
|
6945
|
-
|
|
7697
|
+
const cloudSelection = resolveCloudSelection({
|
|
7698
|
+
cloud: resolved.cloud
|
|
6946
7699
|
});
|
|
6947
7700
|
const model = resolved.model;
|
|
6948
7701
|
this.config = resolved;
|
|
@@ -6952,20 +7705,22 @@ var Opensteer = class _Opensteer {
|
|
|
6952
7705
|
this.namespace = resolveNamespace(resolved, rootDir);
|
|
6953
7706
|
this.storage = new LocalSelectorStorage(rootDir, this.namespace);
|
|
6954
7707
|
this.pool = new BrowserPool(resolved.browser || {});
|
|
6955
|
-
if (
|
|
6956
|
-
const
|
|
6957
|
-
const apiKey =
|
|
7708
|
+
if (cloudSelection.cloud) {
|
|
7709
|
+
const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
|
|
7710
|
+
const apiKey = cloudConfig?.apiKey?.trim();
|
|
6958
7711
|
if (!apiKey) {
|
|
6959
7712
|
throw new Error(
|
|
6960
|
-
"
|
|
7713
|
+
"Cloud mode requires a non-empty API key via cloud.apiKey or OPENSTEER_API_KEY."
|
|
6961
7714
|
);
|
|
6962
7715
|
}
|
|
6963
|
-
this.
|
|
7716
|
+
this.cloud = createCloudRuntimeState(
|
|
6964
7717
|
apiKey,
|
|
6965
|
-
|
|
7718
|
+
cloudConfig?.baseUrl,
|
|
7719
|
+
cloudConfig?.authScheme,
|
|
7720
|
+
cloudConfig?.appUrl
|
|
6966
7721
|
);
|
|
6967
7722
|
} else {
|
|
6968
|
-
this.
|
|
7723
|
+
this.cloud = null;
|
|
6969
7724
|
}
|
|
6970
7725
|
}
|
|
6971
7726
|
createLazyResolveCallback(model) {
|
|
@@ -7003,32 +7758,32 @@ var Opensteer = class _Opensteer {
|
|
|
7003
7758
|
};
|
|
7004
7759
|
return extract;
|
|
7005
7760
|
}
|
|
7006
|
-
async
|
|
7007
|
-
const result = await this.
|
|
7761
|
+
async invokeCloudActionAndResetCache(method, args) {
|
|
7762
|
+
const result = await this.invokeCloudAction(method, args);
|
|
7008
7763
|
this.snapshotCache = null;
|
|
7009
7764
|
return result;
|
|
7010
7765
|
}
|
|
7011
|
-
async
|
|
7012
|
-
const actionClient = this.
|
|
7013
|
-
const sessionId = this.
|
|
7766
|
+
async invokeCloudAction(method, args) {
|
|
7767
|
+
const actionClient = this.cloud?.actionClient;
|
|
7768
|
+
const sessionId = this.cloud?.sessionId;
|
|
7014
7769
|
if (!actionClient || !sessionId) {
|
|
7015
|
-
throw
|
|
7770
|
+
throw cloudNotLaunchedError();
|
|
7016
7771
|
}
|
|
7017
7772
|
const payload = args && typeof args === "object" ? args : {};
|
|
7018
7773
|
try {
|
|
7019
7774
|
return await actionClient.request(method, payload);
|
|
7020
7775
|
} catch (err) {
|
|
7021
|
-
if (err instanceof
|
|
7776
|
+
if (err instanceof OpensteerCloudError && err.code === "CLOUD_ACTION_FAILED" && CLOUD_INTERACTION_METHODS.has(method)) {
|
|
7022
7777
|
const detailsRecord = err.details && typeof err.details === "object" ? err.details : null;
|
|
7023
|
-
const
|
|
7778
|
+
const cloudFailure = normalizeActionFailure(
|
|
7024
7779
|
detailsRecord?.actionFailure
|
|
7025
7780
|
);
|
|
7026
|
-
const failure =
|
|
7781
|
+
const failure = cloudFailure || classifyActionFailure({
|
|
7027
7782
|
action: method,
|
|
7028
7783
|
error: err,
|
|
7029
7784
|
fallbackMessage: defaultActionFailureMessage(method)
|
|
7030
7785
|
});
|
|
7031
|
-
const description =
|
|
7786
|
+
const description = readCloudActionDescription(payload);
|
|
7032
7787
|
throw this.buildActionError(
|
|
7033
7788
|
method,
|
|
7034
7789
|
description,
|
|
@@ -7069,8 +7824,36 @@ var Opensteer = class _Opensteer {
|
|
|
7069
7824
|
}
|
|
7070
7825
|
return this.contextRef;
|
|
7071
7826
|
}
|
|
7072
|
-
|
|
7073
|
-
return this.
|
|
7827
|
+
getCloudSessionId() {
|
|
7828
|
+
return this.cloud?.sessionId ?? null;
|
|
7829
|
+
}
|
|
7830
|
+
getCloudSessionUrl() {
|
|
7831
|
+
return this.cloud?.cloudSessionUrl ?? null;
|
|
7832
|
+
}
|
|
7833
|
+
announceCloudSession(args) {
|
|
7834
|
+
if (!this.shouldAnnounceCloudSession()) {
|
|
7835
|
+
return;
|
|
7836
|
+
}
|
|
7837
|
+
const fields = [
|
|
7838
|
+
`sessionId=${args.sessionId}`,
|
|
7839
|
+
`workspaceId=${args.workspaceId}`
|
|
7840
|
+
];
|
|
7841
|
+
if (args.cloudSessionUrl) {
|
|
7842
|
+
fields.push(`url=${args.cloudSessionUrl}`);
|
|
7843
|
+
}
|
|
7844
|
+
process.stderr.write(`[opensteer] cloud session ready ${fields.join(" ")}
|
|
7845
|
+
`);
|
|
7846
|
+
}
|
|
7847
|
+
shouldAnnounceCloudSession() {
|
|
7848
|
+
const cloudConfig = this.config.cloud && typeof this.config.cloud === "object" ? this.config.cloud : null;
|
|
7849
|
+
const announce = cloudConfig?.announce ?? "always";
|
|
7850
|
+
if (announce === "off") {
|
|
7851
|
+
return false;
|
|
7852
|
+
}
|
|
7853
|
+
if (announce === "tty") {
|
|
7854
|
+
return Boolean(process.stderr.isTTY);
|
|
7855
|
+
}
|
|
7856
|
+
return true;
|
|
7074
7857
|
}
|
|
7075
7858
|
async launch(options = {}) {
|
|
7076
7859
|
if (this.pageRef && !this.ownsBrowser) {
|
|
@@ -7081,22 +7864,29 @@ var Opensteer = class _Opensteer {
|
|
|
7081
7864
|
if (this.pageRef && this.ownsBrowser) {
|
|
7082
7865
|
return;
|
|
7083
7866
|
}
|
|
7084
|
-
if (this.
|
|
7867
|
+
if (this.cloud) {
|
|
7085
7868
|
let actionClient = null;
|
|
7086
7869
|
let browser = null;
|
|
7087
7870
|
let sessionId = null;
|
|
7871
|
+
let localRunId = null;
|
|
7088
7872
|
try {
|
|
7089
7873
|
try {
|
|
7090
|
-
await this.
|
|
7874
|
+
await this.syncLocalSelectorCacheToCloud();
|
|
7091
7875
|
} catch (error) {
|
|
7092
7876
|
if (this.config.debug) {
|
|
7093
7877
|
const message = error instanceof Error ? error.message : String(error);
|
|
7094
7878
|
console.warn(
|
|
7095
|
-
`[opensteer]
|
|
7879
|
+
`[opensteer] cloud selector cache sync failed: ${message}`
|
|
7096
7880
|
);
|
|
7097
7881
|
}
|
|
7098
7882
|
}
|
|
7099
|
-
|
|
7883
|
+
localRunId = this.cloud.localRunId || buildLocalRunId(this.namespace);
|
|
7884
|
+
this.cloud.localRunId = localRunId;
|
|
7885
|
+
const session2 = await this.cloud.sessionClient.create({
|
|
7886
|
+
cloudSessionContractVersion,
|
|
7887
|
+
sourceType: "local-cloud",
|
|
7888
|
+
clientSessionHint: this.namespace,
|
|
7889
|
+
localRunId,
|
|
7100
7890
|
name: this.namespace,
|
|
7101
7891
|
model: this.config.model,
|
|
7102
7892
|
launchContext: options.context || void 0
|
|
@@ -7107,7 +7897,7 @@ var Opensteer = class _Opensteer {
|
|
|
7107
7897
|
token: session2.actionToken,
|
|
7108
7898
|
sessionId: session2.sessionId
|
|
7109
7899
|
});
|
|
7110
|
-
const cdpConnection = await this.
|
|
7900
|
+
const cdpConnection = await this.cloud.cdpClient.connect({
|
|
7111
7901
|
wsUrl: session2.cdpWsUrl,
|
|
7112
7902
|
token: session2.cdpToken
|
|
7113
7903
|
});
|
|
@@ -7117,8 +7907,17 @@ var Opensteer = class _Opensteer {
|
|
|
7117
7907
|
this.pageRef = cdpConnection.page;
|
|
7118
7908
|
this.ownsBrowser = true;
|
|
7119
7909
|
this.snapshotCache = null;
|
|
7120
|
-
this.
|
|
7121
|
-
this.
|
|
7910
|
+
this.cloud.actionClient = actionClient;
|
|
7911
|
+
this.cloud.sessionId = sessionId;
|
|
7912
|
+
this.cloud.cloudSessionUrl = buildCloudSessionUrl(
|
|
7913
|
+
this.cloud.appUrl,
|
|
7914
|
+
session2.cloudSession.sessionId
|
|
7915
|
+
);
|
|
7916
|
+
this.announceCloudSession({
|
|
7917
|
+
sessionId: session2.sessionId,
|
|
7918
|
+
workspaceId: session2.cloudSession.workspaceId,
|
|
7919
|
+
cloudSessionUrl: this.cloud.cloudSessionUrl
|
|
7920
|
+
});
|
|
7122
7921
|
return;
|
|
7123
7922
|
} catch (error) {
|
|
7124
7923
|
if (actionClient) {
|
|
@@ -7128,8 +7927,9 @@ var Opensteer = class _Opensteer {
|
|
|
7128
7927
|
await browser.close().catch(() => void 0);
|
|
7129
7928
|
}
|
|
7130
7929
|
if (sessionId) {
|
|
7131
|
-
await this.
|
|
7930
|
+
await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
|
|
7132
7931
|
}
|
|
7932
|
+
this.cloud.cloudSessionUrl = null;
|
|
7133
7933
|
throw error;
|
|
7134
7934
|
}
|
|
7135
7935
|
}
|
|
@@ -7147,13 +7947,13 @@ var Opensteer = class _Opensteer {
|
|
|
7147
7947
|
}
|
|
7148
7948
|
static from(page, config = {}) {
|
|
7149
7949
|
const resolvedConfig = resolveConfig(config);
|
|
7150
|
-
const
|
|
7151
|
-
|
|
7950
|
+
const cloudSelection = resolveCloudSelection({
|
|
7951
|
+
cloud: resolvedConfig.cloud
|
|
7152
7952
|
});
|
|
7153
|
-
if (
|
|
7154
|
-
throw
|
|
7953
|
+
if (cloudSelection.cloud) {
|
|
7954
|
+
throw cloudUnsupportedMethodError(
|
|
7155
7955
|
"Opensteer.from(page)",
|
|
7156
|
-
"Opensteer.from(page) is not supported in
|
|
7956
|
+
"Opensteer.from(page) is not supported in cloud mode."
|
|
7157
7957
|
);
|
|
7158
7958
|
}
|
|
7159
7959
|
const instance = new _Opensteer(config);
|
|
@@ -7166,12 +7966,14 @@ var Opensteer = class _Opensteer {
|
|
|
7166
7966
|
}
|
|
7167
7967
|
async close() {
|
|
7168
7968
|
this.snapshotCache = null;
|
|
7169
|
-
if (this.
|
|
7170
|
-
const actionClient = this.
|
|
7171
|
-
const sessionId = this.
|
|
7969
|
+
if (this.cloud) {
|
|
7970
|
+
const actionClient = this.cloud.actionClient;
|
|
7971
|
+
const sessionId = this.cloud.sessionId;
|
|
7172
7972
|
const browser = this.browser;
|
|
7173
|
-
this.
|
|
7174
|
-
this.
|
|
7973
|
+
this.cloud.actionClient = null;
|
|
7974
|
+
this.cloud.sessionId = null;
|
|
7975
|
+
this.cloud.localRunId = null;
|
|
7976
|
+
this.cloud.cloudSessionUrl = null;
|
|
7175
7977
|
this.browser = null;
|
|
7176
7978
|
this.pageRef = null;
|
|
7177
7979
|
this.contextRef = null;
|
|
@@ -7183,7 +7985,7 @@ var Opensteer = class _Opensteer {
|
|
|
7183
7985
|
await browser.close().catch(() => void 0);
|
|
7184
7986
|
}
|
|
7185
7987
|
if (sessionId) {
|
|
7186
|
-
await this.
|
|
7988
|
+
await this.cloud.sessionClient.close(sessionId).catch(() => void 0);
|
|
7187
7989
|
}
|
|
7188
7990
|
return;
|
|
7189
7991
|
}
|
|
@@ -7195,17 +7997,17 @@ var Opensteer = class _Opensteer {
|
|
|
7195
7997
|
this.contextRef = null;
|
|
7196
7998
|
this.ownsBrowser = false;
|
|
7197
7999
|
}
|
|
7198
|
-
async
|
|
7199
|
-
if (!this.
|
|
8000
|
+
async syncLocalSelectorCacheToCloud() {
|
|
8001
|
+
if (!this.cloud) return;
|
|
7200
8002
|
const entries = collectLocalSelectorCacheEntries(this.storage);
|
|
7201
8003
|
if (!entries.length) return;
|
|
7202
|
-
await this.
|
|
8004
|
+
await this.cloud.sessionClient.importSelectorCache({
|
|
7203
8005
|
entries
|
|
7204
8006
|
});
|
|
7205
8007
|
}
|
|
7206
8008
|
async goto(url, options) {
|
|
7207
|
-
if (this.
|
|
7208
|
-
await this.
|
|
8009
|
+
if (this.cloud) {
|
|
8010
|
+
await this.invokeCloudActionAndResetCache("goto", { url, options });
|
|
7209
8011
|
return;
|
|
7210
8012
|
}
|
|
7211
8013
|
const { waitUntil = "domcontentloaded", ...rest } = options ?? {};
|
|
@@ -7214,8 +8016,8 @@ var Opensteer = class _Opensteer {
|
|
|
7214
8016
|
this.snapshotCache = null;
|
|
7215
8017
|
}
|
|
7216
8018
|
async snapshot(options = {}) {
|
|
7217
|
-
if (this.
|
|
7218
|
-
return await this.
|
|
8019
|
+
if (this.cloud) {
|
|
8020
|
+
return await this.invokeCloudActionAndResetCache("snapshot", {
|
|
7219
8021
|
options
|
|
7220
8022
|
});
|
|
7221
8023
|
}
|
|
@@ -7224,8 +8026,8 @@ var Opensteer = class _Opensteer {
|
|
|
7224
8026
|
return prepared.cleanedHtml;
|
|
7225
8027
|
}
|
|
7226
8028
|
async state() {
|
|
7227
|
-
if (this.
|
|
7228
|
-
return await this.
|
|
8029
|
+
if (this.cloud) {
|
|
8030
|
+
return await this.invokeCloudAction("state", {});
|
|
7229
8031
|
}
|
|
7230
8032
|
const html = await this.snapshot({ mode: "action" });
|
|
7231
8033
|
return {
|
|
@@ -7235,8 +8037,8 @@ var Opensteer = class _Opensteer {
|
|
|
7235
8037
|
};
|
|
7236
8038
|
}
|
|
7237
8039
|
async screenshot(options = {}) {
|
|
7238
|
-
if (this.
|
|
7239
|
-
const b64 = await this.
|
|
8040
|
+
if (this.cloud) {
|
|
8041
|
+
const b64 = await this.invokeCloudAction(
|
|
7240
8042
|
"screenshot",
|
|
7241
8043
|
options
|
|
7242
8044
|
);
|
|
@@ -7250,8 +8052,8 @@ var Opensteer = class _Opensteer {
|
|
|
7250
8052
|
});
|
|
7251
8053
|
}
|
|
7252
8054
|
async click(options) {
|
|
7253
|
-
if (this.
|
|
7254
|
-
return await this.
|
|
8055
|
+
if (this.cloud) {
|
|
8056
|
+
return await this.invokeCloudActionAndResetCache(
|
|
7255
8057
|
"click",
|
|
7256
8058
|
options
|
|
7257
8059
|
);
|
|
@@ -7263,8 +8065,8 @@ var Opensteer = class _Opensteer {
|
|
|
7263
8065
|
});
|
|
7264
8066
|
}
|
|
7265
8067
|
async dblclick(options) {
|
|
7266
|
-
if (this.
|
|
7267
|
-
return await this.
|
|
8068
|
+
if (this.cloud) {
|
|
8069
|
+
return await this.invokeCloudActionAndResetCache(
|
|
7268
8070
|
"dblclick",
|
|
7269
8071
|
options
|
|
7270
8072
|
);
|
|
@@ -7276,8 +8078,8 @@ var Opensteer = class _Opensteer {
|
|
|
7276
8078
|
});
|
|
7277
8079
|
}
|
|
7278
8080
|
async rightclick(options) {
|
|
7279
|
-
if (this.
|
|
7280
|
-
return await this.
|
|
8081
|
+
if (this.cloud) {
|
|
8082
|
+
return await this.invokeCloudActionAndResetCache(
|
|
7281
8083
|
"rightclick",
|
|
7282
8084
|
options
|
|
7283
8085
|
);
|
|
@@ -7289,8 +8091,8 @@ var Opensteer = class _Opensteer {
|
|
|
7289
8091
|
});
|
|
7290
8092
|
}
|
|
7291
8093
|
async hover(options) {
|
|
7292
|
-
if (this.
|
|
7293
|
-
return await this.
|
|
8094
|
+
if (this.cloud) {
|
|
8095
|
+
return await this.invokeCloudActionAndResetCache(
|
|
7294
8096
|
"hover",
|
|
7295
8097
|
options
|
|
7296
8098
|
);
|
|
@@ -7388,8 +8190,8 @@ var Opensteer = class _Opensteer {
|
|
|
7388
8190
|
);
|
|
7389
8191
|
}
|
|
7390
8192
|
async input(options) {
|
|
7391
|
-
if (this.
|
|
7392
|
-
return await this.
|
|
8193
|
+
if (this.cloud) {
|
|
8194
|
+
return await this.invokeCloudActionAndResetCache(
|
|
7393
8195
|
"input",
|
|
7394
8196
|
options
|
|
7395
8197
|
);
|
|
@@ -7418,7 +8220,7 @@ var Opensteer = class _Opensteer {
|
|
|
7418
8220
|
await handle.type(options.text);
|
|
7419
8221
|
}
|
|
7420
8222
|
if (options.pressEnter) {
|
|
7421
|
-
await handle.press("Enter");
|
|
8223
|
+
await handle.press("Enter", { noWaitAfter: true });
|
|
7422
8224
|
}
|
|
7423
8225
|
});
|
|
7424
8226
|
} catch (err) {
|
|
@@ -7491,8 +8293,8 @@ var Opensteer = class _Opensteer {
|
|
|
7491
8293
|
);
|
|
7492
8294
|
}
|
|
7493
8295
|
async select(options) {
|
|
7494
|
-
if (this.
|
|
7495
|
-
return await this.
|
|
8296
|
+
if (this.cloud) {
|
|
8297
|
+
return await this.invokeCloudActionAndResetCache(
|
|
7496
8298
|
"select",
|
|
7497
8299
|
options
|
|
7498
8300
|
);
|
|
@@ -7601,8 +8403,8 @@ var Opensteer = class _Opensteer {
|
|
|
7601
8403
|
);
|
|
7602
8404
|
}
|
|
7603
8405
|
async scroll(options = {}) {
|
|
7604
|
-
if (this.
|
|
7605
|
-
return await this.
|
|
8406
|
+
if (this.cloud) {
|
|
8407
|
+
return await this.invokeCloudActionAndResetCache(
|
|
7606
8408
|
"scroll",
|
|
7607
8409
|
options
|
|
7608
8410
|
);
|
|
@@ -7703,14 +8505,14 @@ var Opensteer = class _Opensteer {
|
|
|
7703
8505
|
}
|
|
7704
8506
|
// --- Tab Management ---
|
|
7705
8507
|
async tabs() {
|
|
7706
|
-
if (this.
|
|
7707
|
-
return await this.
|
|
8508
|
+
if (this.cloud) {
|
|
8509
|
+
return await this.invokeCloudAction("tabs", {});
|
|
7708
8510
|
}
|
|
7709
8511
|
return listTabs(this.context, this.page);
|
|
7710
8512
|
}
|
|
7711
8513
|
async newTab(url) {
|
|
7712
|
-
if (this.
|
|
7713
|
-
return await this.
|
|
8514
|
+
if (this.cloud) {
|
|
8515
|
+
return await this.invokeCloudActionAndResetCache("newTab", {
|
|
7714
8516
|
url
|
|
7715
8517
|
});
|
|
7716
8518
|
}
|
|
@@ -7720,8 +8522,8 @@ var Opensteer = class _Opensteer {
|
|
|
7720
8522
|
return info;
|
|
7721
8523
|
}
|
|
7722
8524
|
async switchTab(index) {
|
|
7723
|
-
if (this.
|
|
7724
|
-
await this.
|
|
8525
|
+
if (this.cloud) {
|
|
8526
|
+
await this.invokeCloudActionAndResetCache("switchTab", { index });
|
|
7725
8527
|
return;
|
|
7726
8528
|
}
|
|
7727
8529
|
const page = await switchTab(this.context, index);
|
|
@@ -7729,8 +8531,8 @@ var Opensteer = class _Opensteer {
|
|
|
7729
8531
|
this.snapshotCache = null;
|
|
7730
8532
|
}
|
|
7731
8533
|
async closeTab(index) {
|
|
7732
|
-
if (this.
|
|
7733
|
-
await this.
|
|
8534
|
+
if (this.cloud) {
|
|
8535
|
+
await this.invokeCloudActionAndResetCache("closeTab", { index });
|
|
7734
8536
|
return;
|
|
7735
8537
|
}
|
|
7736
8538
|
const newPage = await closeTab(this.context, this.page, index);
|
|
@@ -7741,8 +8543,8 @@ var Opensteer = class _Opensteer {
|
|
|
7741
8543
|
}
|
|
7742
8544
|
// --- Cookie Management ---
|
|
7743
8545
|
async getCookies(url) {
|
|
7744
|
-
if (this.
|
|
7745
|
-
return await this.
|
|
8546
|
+
if (this.cloud) {
|
|
8547
|
+
return await this.invokeCloudAction(
|
|
7746
8548
|
"getCookies",
|
|
7747
8549
|
{ url }
|
|
7748
8550
|
);
|
|
@@ -7750,41 +8552,41 @@ var Opensteer = class _Opensteer {
|
|
|
7750
8552
|
return getCookies(this.context, url);
|
|
7751
8553
|
}
|
|
7752
8554
|
async setCookie(cookie) {
|
|
7753
|
-
if (this.
|
|
7754
|
-
await this.
|
|
8555
|
+
if (this.cloud) {
|
|
8556
|
+
await this.invokeCloudAction("setCookie", cookie);
|
|
7755
8557
|
return;
|
|
7756
8558
|
}
|
|
7757
8559
|
return setCookie(this.context, cookie);
|
|
7758
8560
|
}
|
|
7759
8561
|
async clearCookies() {
|
|
7760
|
-
if (this.
|
|
7761
|
-
await this.
|
|
8562
|
+
if (this.cloud) {
|
|
8563
|
+
await this.invokeCloudAction("clearCookies", {});
|
|
7762
8564
|
return;
|
|
7763
8565
|
}
|
|
7764
8566
|
return clearCookies(this.context);
|
|
7765
8567
|
}
|
|
7766
8568
|
async exportCookies(filePath, url) {
|
|
7767
|
-
if (this.
|
|
7768
|
-
throw
|
|
8569
|
+
if (this.cloud) {
|
|
8570
|
+
throw cloudUnsupportedMethodError(
|
|
7769
8571
|
"exportCookies",
|
|
7770
|
-
"exportCookies() is not supported in
|
|
8572
|
+
"exportCookies() is not supported in cloud mode because it depends on local filesystem paths."
|
|
7771
8573
|
);
|
|
7772
8574
|
}
|
|
7773
8575
|
return exportCookies(this.context, filePath, url);
|
|
7774
8576
|
}
|
|
7775
8577
|
async importCookies(filePath) {
|
|
7776
|
-
if (this.
|
|
7777
|
-
throw
|
|
8578
|
+
if (this.cloud) {
|
|
8579
|
+
throw cloudUnsupportedMethodError(
|
|
7778
8580
|
"importCookies",
|
|
7779
|
-
"importCookies() is not supported in
|
|
8581
|
+
"importCookies() is not supported in cloud mode because it depends on local filesystem paths."
|
|
7780
8582
|
);
|
|
7781
8583
|
}
|
|
7782
8584
|
return importCookies(this.context, filePath);
|
|
7783
8585
|
}
|
|
7784
8586
|
// --- Keyboard Input ---
|
|
7785
8587
|
async pressKey(key) {
|
|
7786
|
-
if (this.
|
|
7787
|
-
await this.
|
|
8588
|
+
if (this.cloud) {
|
|
8589
|
+
await this.invokeCloudActionAndResetCache("pressKey", { key });
|
|
7788
8590
|
return;
|
|
7789
8591
|
}
|
|
7790
8592
|
await this.runWithPostActionWait("pressKey", void 0, async () => {
|
|
@@ -7793,8 +8595,8 @@ var Opensteer = class _Opensteer {
|
|
|
7793
8595
|
this.snapshotCache = null;
|
|
7794
8596
|
}
|
|
7795
8597
|
async type(text) {
|
|
7796
|
-
if (this.
|
|
7797
|
-
await this.
|
|
8598
|
+
if (this.cloud) {
|
|
8599
|
+
await this.invokeCloudActionAndResetCache("type", { text });
|
|
7798
8600
|
return;
|
|
7799
8601
|
}
|
|
7800
8602
|
await this.runWithPostActionWait("type", void 0, async () => {
|
|
@@ -7804,8 +8606,8 @@ var Opensteer = class _Opensteer {
|
|
|
7804
8606
|
}
|
|
7805
8607
|
// --- Element Info ---
|
|
7806
8608
|
async getElementText(options) {
|
|
7807
|
-
if (this.
|
|
7808
|
-
return await this.
|
|
8609
|
+
if (this.cloud) {
|
|
8610
|
+
return await this.invokeCloudAction("getElementText", options);
|
|
7809
8611
|
}
|
|
7810
8612
|
return this.executeElementInfoAction(
|
|
7811
8613
|
"getElementText",
|
|
@@ -7818,8 +8620,8 @@ var Opensteer = class _Opensteer {
|
|
|
7818
8620
|
);
|
|
7819
8621
|
}
|
|
7820
8622
|
async getElementValue(options) {
|
|
7821
|
-
if (this.
|
|
7822
|
-
return await this.
|
|
8623
|
+
if (this.cloud) {
|
|
8624
|
+
return await this.invokeCloudAction(
|
|
7823
8625
|
"getElementValue",
|
|
7824
8626
|
options
|
|
7825
8627
|
);
|
|
@@ -7834,8 +8636,8 @@ var Opensteer = class _Opensteer {
|
|
|
7834
8636
|
);
|
|
7835
8637
|
}
|
|
7836
8638
|
async getElementAttributes(options) {
|
|
7837
|
-
if (this.
|
|
7838
|
-
return await this.
|
|
8639
|
+
if (this.cloud) {
|
|
8640
|
+
return await this.invokeCloudAction(
|
|
7839
8641
|
"getElementAttributes",
|
|
7840
8642
|
options
|
|
7841
8643
|
);
|
|
@@ -7856,8 +8658,8 @@ var Opensteer = class _Opensteer {
|
|
|
7856
8658
|
);
|
|
7857
8659
|
}
|
|
7858
8660
|
async getElementBoundingBox(options) {
|
|
7859
|
-
if (this.
|
|
7860
|
-
return await this.
|
|
8661
|
+
if (this.cloud) {
|
|
8662
|
+
return await this.invokeCloudAction(
|
|
7861
8663
|
"getElementBoundingBox",
|
|
7862
8664
|
options
|
|
7863
8665
|
);
|
|
@@ -7872,14 +8674,14 @@ var Opensteer = class _Opensteer {
|
|
|
7872
8674
|
);
|
|
7873
8675
|
}
|
|
7874
8676
|
async getHtml(selector) {
|
|
7875
|
-
if (this.
|
|
7876
|
-
return await this.
|
|
8677
|
+
if (this.cloud) {
|
|
8678
|
+
return await this.invokeCloudAction("getHtml", { selector });
|
|
7877
8679
|
}
|
|
7878
8680
|
return getPageHtml(this.page, selector);
|
|
7879
8681
|
}
|
|
7880
8682
|
async getTitle() {
|
|
7881
|
-
if (this.
|
|
7882
|
-
return await this.
|
|
8683
|
+
if (this.cloud) {
|
|
8684
|
+
return await this.invokeCloudAction("getTitle", {});
|
|
7883
8685
|
}
|
|
7884
8686
|
return getPageTitle(this.page);
|
|
7885
8687
|
}
|
|
@@ -7917,10 +8719,10 @@ var Opensteer = class _Opensteer {
|
|
|
7917
8719
|
}
|
|
7918
8720
|
// --- File Upload ---
|
|
7919
8721
|
async uploadFile(options) {
|
|
7920
|
-
if (this.
|
|
7921
|
-
throw
|
|
8722
|
+
if (this.cloud) {
|
|
8723
|
+
throw cloudUnsupportedMethodError(
|
|
7922
8724
|
"uploadFile",
|
|
7923
|
-
"uploadFile() is not supported in
|
|
8725
|
+
"uploadFile() is not supported in cloud mode because file paths must be accessible on the cloud runtime."
|
|
7924
8726
|
);
|
|
7925
8727
|
}
|
|
7926
8728
|
const storageKey = this.resolveStorageKey(options.description);
|
|
@@ -8022,15 +8824,15 @@ var Opensteer = class _Opensteer {
|
|
|
8022
8824
|
}
|
|
8023
8825
|
// --- Wait for Text ---
|
|
8024
8826
|
async waitForText(text, options) {
|
|
8025
|
-
if (this.
|
|
8026
|
-
await this.
|
|
8827
|
+
if (this.cloud) {
|
|
8828
|
+
await this.invokeCloudAction("waitForText", { text, options });
|
|
8027
8829
|
return;
|
|
8028
8830
|
}
|
|
8029
8831
|
await this.page.getByText(text).first().waitFor({ timeout: options?.timeout ?? 3e4 });
|
|
8030
8832
|
}
|
|
8031
8833
|
async extract(options) {
|
|
8032
|
-
if (this.
|
|
8033
|
-
return await this.
|
|
8834
|
+
if (this.cloud) {
|
|
8835
|
+
return await this.invokeCloudAction("extract", options);
|
|
8034
8836
|
}
|
|
8035
8837
|
const storageKey = this.resolveStorageKey(options.description);
|
|
8036
8838
|
const schemaHash = options.schema ? computeSchemaHash(options.schema) : null;
|
|
@@ -8082,8 +8884,8 @@ var Opensteer = class _Opensteer {
|
|
|
8082
8884
|
return inflateDataPathObject(data);
|
|
8083
8885
|
}
|
|
8084
8886
|
async extractFromPlan(options) {
|
|
8085
|
-
if (this.
|
|
8086
|
-
return await this.
|
|
8887
|
+
if (this.cloud) {
|
|
8888
|
+
return await this.invokeCloudAction(
|
|
8087
8889
|
"extractFromPlan",
|
|
8088
8890
|
options
|
|
8089
8891
|
);
|
|
@@ -8132,10 +8934,10 @@ var Opensteer = class _Opensteer {
|
|
|
8132
8934
|
return this.storage;
|
|
8133
8935
|
}
|
|
8134
8936
|
clearCache() {
|
|
8135
|
-
if (this.
|
|
8937
|
+
if (this.cloud) {
|
|
8136
8938
|
this.snapshotCache = null;
|
|
8137
|
-
if (!this.
|
|
8138
|
-
void this.
|
|
8939
|
+
if (!this.cloud.actionClient) return;
|
|
8940
|
+
void this.invokeCloudAction("clearCache", {});
|
|
8139
8941
|
return;
|
|
8140
8942
|
}
|
|
8141
8943
|
this.storage.clearNamespace();
|
|
@@ -9163,6 +9965,16 @@ function getScrollDelta2(options) {
|
|
|
9163
9965
|
return { x: 0, y: absoluteAmount };
|
|
9164
9966
|
}
|
|
9165
9967
|
}
|
|
9968
|
+
function buildLocalRunId(namespace) {
|
|
9969
|
+
const normalized = namespace.trim() || "default";
|
|
9970
|
+
return `${normalized}-${Date.now().toString(36)}-${randomUUID2().slice(0, 8)}`;
|
|
9971
|
+
}
|
|
9972
|
+
function buildCloudSessionUrl(appUrl, sessionId) {
|
|
9973
|
+
if (!appUrl) {
|
|
9974
|
+
return null;
|
|
9975
|
+
}
|
|
9976
|
+
return `${appUrl}/browser/${encodeURIComponent(sessionId)}`;
|
|
9977
|
+
}
|
|
9166
9978
|
|
|
9167
9979
|
export {
|
|
9168
9980
|
normalizeNamespace,
|
|
@@ -9225,12 +10037,13 @@ export {
|
|
|
9225
10037
|
getPageTitle,
|
|
9226
10038
|
performFileUpload,
|
|
9227
10039
|
OpensteerActionError,
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
10040
|
+
cloudSessionContractVersion,
|
|
10041
|
+
OpensteerCloudError,
|
|
10042
|
+
cloudUnsupportedMethodError,
|
|
10043
|
+
cloudNotLaunchedError,
|
|
9231
10044
|
ActionWsClient,
|
|
9232
10045
|
collectLocalSelectorCacheEntries,
|
|
9233
|
-
|
|
9234
|
-
|
|
10046
|
+
CloudCdpClient,
|
|
10047
|
+
CloudSessionClient,
|
|
9235
10048
|
Opensteer
|
|
9236
10049
|
};
|