@opencode/browse 0.0.6-dev.28 → 0.0.6-dev.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/THIRD_PARTY_NOTICES.txt +310 -0
- package/dist/Undici-88kyhfpm.mjs +25295 -0
- package/dist/cli-pazs0vgt.mjs +6694 -0
- package/dist/cli.mjs +10 -10
- package/dist/{helpers-3fm1nwkt.mjs → helpers-1afz3xb6.mjs} +2 -1
- package/dist/index-yghakpfn.mjs +12 -0
- package/dist/{main-x8w3s3rv.mjs → main-30dkfffj.mjs} +5 -4
- package/dist/{main-xg3r1ze3.mjs → main-3w449766.mjs} +9 -9
- package/dist/main-9z6jbwdp.mjs +14661 -0
- package/dist/main-aqzas8ts.mjs +1775 -0
- package/dist/main-brs53cap.mjs +205 -0
- package/dist/{main-8ncff5jp.mjs → main-ea9djgtm.mjs} +2 -2
- package/dist/main-k9drabmz.mjs +4 -0
- package/dist/{main-rsqahh12.mjs → main-q641ckf5.mjs} +1416 -97
- package/dist/{main-tfkqwwrb.mjs → main-va90zea2.mjs} +30 -20
- package/dist/main-yf9xhpn4.mjs +290 -0
- package/dist/{main-scyv26xb.mjs → main-yxvg4ms6.mjs} +2 -2
- package/dist/mcp-4v2gsdr7.mjs +8515 -0
- package/dist/{relay-epeqzmcr.mjs → relay-sxbxskww.mjs} +739 -693
- package/dist/{runtime-update-3925nwpw.mjs → runtime-update-tksdpg29.mjs} +22 -11
- package/dist/{setup-ys0qf6by.mjs → setup-92ht2epc.mjs} +10 -40
- package/dist/{supervisor-yb615ygt.mjs → supervisor-0afrkpjb.mjs} +3 -3
- package/dist/{update-wjkqfk5f.mjs → update-vv7x6c6j.mjs} +3 -3
- package/dist/{version-rh26jqa2.mjs → version-pvx4s3qg.mjs} +1 -1
- package/package.json +4 -3
- package/dist/cli-wgxfjgb3.mjs +0 -1796
- package/dist/main-4t3a1fw9.mjs +0 -4
- package/dist/main-8h1nwn45.mjs +0 -1410
- package/dist/mcp-7jjntfwf.mjs +0 -905
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
opencodeBrowserVersion2,
|
|
3
3
|
opencodeBrowserBuildId2
|
|
4
|
-
} from "./main-
|
|
4
|
+
} from "./main-k9drabmz.mjs";
|
|
5
5
|
import {
|
|
6
6
|
dataRoot,
|
|
7
7
|
oldStoreMarker,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
setStoredMode,
|
|
20
20
|
autoUpdateStatus,
|
|
21
21
|
createAutoUpdater
|
|
22
|
-
} from "./main-
|
|
22
|
+
} from "./main-ea9djgtm.mjs";
|
|
23
23
|
import {
|
|
24
24
|
extensionProtocolVersion,
|
|
25
25
|
minExtensionProtocolVersion,
|
|
@@ -43,8 +43,13 @@ import {
|
|
|
43
43
|
relayToken,
|
|
44
44
|
secretsEqual,
|
|
45
45
|
ensurePrivateDirectory
|
|
46
|
-
} from "./main-
|
|
46
|
+
} from "./main-30dkfffj.mjs";
|
|
47
47
|
import {
|
|
48
|
+
withDefault,
|
|
49
|
+
option,
|
|
50
|
+
String2,
|
|
51
|
+
Int,
|
|
52
|
+
Boolean2,
|
|
48
53
|
ExecuteRequest,
|
|
49
54
|
SessionAdoptRequest,
|
|
50
55
|
SessionNewRequest,
|
|
@@ -92,11 +97,11 @@ import {
|
|
|
92
97
|
isRestrictedTarget2,
|
|
93
98
|
isRestrictedUrl2,
|
|
94
99
|
dataUrlDocument2
|
|
95
|
-
} from "./main-
|
|
100
|
+
} from "./main-aqzas8ts.mjs";
|
|
96
101
|
import {
|
|
97
102
|
relayHostname,
|
|
98
103
|
isLocalPeer,
|
|
99
|
-
|
|
104
|
+
context2,
|
|
100
105
|
pendingPairingCodeKeys,
|
|
101
106
|
consumePairingCode,
|
|
102
107
|
recordPairingFailure,
|
|
@@ -114,8 +119,9 @@ import {
|
|
|
114
119
|
wrapToken,
|
|
115
120
|
installedDevExtension,
|
|
116
121
|
syncDevExtension
|
|
117
|
-
} from "./main-
|
|
122
|
+
} from "./main-3w449766.mjs";
|
|
118
123
|
import {
|
|
124
|
+
makeUnsafe,
|
|
119
125
|
SettingsInputError,
|
|
120
126
|
parseSettingChanges,
|
|
121
127
|
settingsFromExtension,
|
|
@@ -141,21 +147,136 @@ import {
|
|
|
141
147
|
checkNavigationUrl,
|
|
142
148
|
readSecret,
|
|
143
149
|
secretText
|
|
144
|
-
} from "./main-
|
|
150
|
+
} from "./main-q641ckf5.mjs";
|
|
145
151
|
import {
|
|
146
152
|
defaultJournalBaseDir,
|
|
147
153
|
makeJournalEntry,
|
|
148
154
|
appendJournalEntry,
|
|
149
155
|
startJournalRetention
|
|
150
|
-
} from "./main-
|
|
156
|
+
} from "./main-va90zea2.mjs";
|
|
151
157
|
import {
|
|
152
158
|
createUpdateWatcher2
|
|
153
|
-
} from "./main-
|
|
159
|
+
} from "./main-yxvg4ms6.mjs";
|
|
160
|
+
import {
|
|
161
|
+
isNone,
|
|
162
|
+
isSome,
|
|
163
|
+
getOrElse,
|
|
164
|
+
getOrUndefined,
|
|
165
|
+
isFailure,
|
|
166
|
+
isSuccess,
|
|
167
|
+
taggedEnum,
|
|
168
|
+
void_2,
|
|
169
|
+
isSuccess2,
|
|
170
|
+
isFailure2,
|
|
171
|
+
asVoid,
|
|
172
|
+
makeUnsafe3,
|
|
173
|
+
make5,
|
|
174
|
+
_await,
|
|
175
|
+
done,
|
|
176
|
+
fail3,
|
|
177
|
+
succeed4,
|
|
178
|
+
doneUnsafe,
|
|
179
|
+
make6,
|
|
180
|
+
close,
|
|
181
|
+
modifyDelay,
|
|
182
|
+
spaced,
|
|
183
|
+
setInputType,
|
|
184
|
+
all,
|
|
185
|
+
forEach,
|
|
186
|
+
promise,
|
|
187
|
+
tryPromise,
|
|
188
|
+
succeed6,
|
|
189
|
+
suspend2,
|
|
190
|
+
sync2,
|
|
191
|
+
void_3,
|
|
192
|
+
callback2,
|
|
193
|
+
never,
|
|
194
|
+
gen,
|
|
195
|
+
fail4,
|
|
196
|
+
try_,
|
|
197
|
+
flatMap3,
|
|
198
|
+
andThen,
|
|
199
|
+
tap,
|
|
200
|
+
result,
|
|
201
|
+
exit,
|
|
202
|
+
map5,
|
|
203
|
+
asVoid2,
|
|
204
|
+
catch_,
|
|
205
|
+
catchCause,
|
|
206
|
+
mapError,
|
|
207
|
+
tapError,
|
|
208
|
+
retry,
|
|
209
|
+
ignore,
|
|
210
|
+
orElseSucceed,
|
|
211
|
+
timeout,
|
|
212
|
+
timeoutOption,
|
|
213
|
+
timeoutOrElse,
|
|
214
|
+
sleep,
|
|
215
|
+
raceFirst,
|
|
216
|
+
match3,
|
|
217
|
+
matchEffect,
|
|
218
|
+
context,
|
|
219
|
+
scoped,
|
|
220
|
+
acquireRelease,
|
|
221
|
+
acquireUseRelease,
|
|
222
|
+
ensuring,
|
|
223
|
+
onExit,
|
|
224
|
+
onInterrupt,
|
|
225
|
+
uninterruptible,
|
|
226
|
+
uninterruptibleMask2,
|
|
227
|
+
repeat,
|
|
228
|
+
forkIn,
|
|
229
|
+
forkScoped,
|
|
230
|
+
forkDetach,
|
|
231
|
+
runFork,
|
|
232
|
+
runForkWith,
|
|
233
|
+
runPromise,
|
|
234
|
+
runSync,
|
|
235
|
+
fnUntraced,
|
|
236
|
+
fn,
|
|
237
|
+
join,
|
|
238
|
+
interrupt3,
|
|
239
|
+
makeUnsafe5,
|
|
240
|
+
is,
|
|
241
|
+
decodeUnknownEffect2,
|
|
242
|
+
decodeUnknownOption,
|
|
243
|
+
decodeUnknownSync,
|
|
244
|
+
encodeSync,
|
|
245
|
+
optionalKey,
|
|
246
|
+
Literal,
|
|
247
|
+
String3,
|
|
248
|
+
Number2,
|
|
249
|
+
Boolean3,
|
|
250
|
+
Struct,
|
|
251
|
+
ArraySchema,
|
|
252
|
+
Union,
|
|
253
|
+
Literals,
|
|
254
|
+
TaggedUnion,
|
|
255
|
+
Int2,
|
|
256
|
+
Defect,
|
|
257
|
+
TaggedError2
|
|
258
|
+
} from "./main-9z6jbwdp.mjs";
|
|
259
|
+
import {
|
|
260
|
+
currentTimeMillis2,
|
|
261
|
+
value2,
|
|
262
|
+
when2,
|
|
263
|
+
orElse,
|
|
264
|
+
exhaustive
|
|
265
|
+
} from "./main-brs53cap.mjs";
|
|
266
|
+
import {
|
|
267
|
+
isString,
|
|
268
|
+
isNumber,
|
|
269
|
+
isBoolean,
|
|
270
|
+
isBigInt,
|
|
271
|
+
isSymbol,
|
|
272
|
+
isFunction,
|
|
273
|
+
isObjectOrArray,
|
|
274
|
+
isObject
|
|
275
|
+
} from "./main-yf9xhpn4.mjs";
|
|
154
276
|
|
|
155
277
|
// src/relay.ts
|
|
156
278
|
import crypto10 from "node:crypto";
|
|
157
279
|
import path14 from "node:path";
|
|
158
|
-
import { Config, Effect as Effect34, Option as Option4 } from "effect";
|
|
159
280
|
|
|
160
281
|
// src/relay/browser-links.ts
|
|
161
282
|
import dns from "node:dns/promises";
|
|
@@ -164,7 +285,6 @@ import os from "node:os";
|
|
|
164
285
|
import path from "node:path";
|
|
165
286
|
|
|
166
287
|
// src/relay/rpc.ts
|
|
167
|
-
import { Effect, Predicate } from "effect";
|
|
168
288
|
import { WebSocket } from "ws";
|
|
169
289
|
class ExtensionRpc {
|
|
170
290
|
timeouts;
|
|
@@ -254,15 +374,15 @@ class ExtensionRpc {
|
|
|
254
374
|
return true;
|
|
255
375
|
}
|
|
256
376
|
send(command) {
|
|
257
|
-
return
|
|
377
|
+
return callback2((resume) => {
|
|
258
378
|
const socket = this.socket;
|
|
259
379
|
if (this.protocolCompatible === false) {
|
|
260
|
-
resume(
|
|
261
|
-
return
|
|
380
|
+
resume(fail4(new Error(extensionProtocolAdvice(this.protocolVersion, { install: installCommand() }))));
|
|
381
|
+
return void_3;
|
|
262
382
|
}
|
|
263
383
|
if (!socket || socket.readyState !== WebSocket.OPEN || !this.acceptsEvents) {
|
|
264
|
-
resume(
|
|
265
|
-
return
|
|
384
|
+
resume(fail4(new Error("browse extension is not connected")));
|
|
385
|
+
return void_3;
|
|
266
386
|
}
|
|
267
387
|
const id = this.nextRequestId++;
|
|
268
388
|
const message = { ...command, id };
|
|
@@ -279,28 +399,28 @@ class ExtensionRpc {
|
|
|
279
399
|
};
|
|
280
400
|
const timeout = timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
281
401
|
this.probeLiveness(socket);
|
|
282
|
-
finish(
|
|
402
|
+
finish(fail4(new Error(`Extension command timed out after ${timeoutMs}ms: ${command.method}`)));
|
|
283
403
|
}, timeoutMs);
|
|
284
|
-
const debuggerTabId = command.method === "debugger.sendCommand" &&
|
|
404
|
+
const debuggerTabId = command.method === "debugger.sendCommand" && isNumber(command.params?.tabId) ? command.params.tabId : undefined;
|
|
285
405
|
this.pendingRequests.set(id, {
|
|
286
406
|
resolve: (value) => {
|
|
287
|
-
finish(
|
|
407
|
+
finish(succeed6(value));
|
|
288
408
|
},
|
|
289
409
|
reject: (error) => {
|
|
290
|
-
finish(
|
|
410
|
+
finish(fail4(error));
|
|
291
411
|
},
|
|
292
412
|
...debuggerTabId === undefined ? {} : { debuggerTabId }
|
|
293
413
|
});
|
|
294
414
|
try {
|
|
295
415
|
socket.send(JSON.stringify(message), (error) => {
|
|
296
416
|
if (error) {
|
|
297
|
-
finish(
|
|
417
|
+
finish(fail4(new Error(`send extension command: ${command.method}`, { cause: error })));
|
|
298
418
|
}
|
|
299
419
|
});
|
|
300
420
|
} catch (error) {
|
|
301
|
-
finish(
|
|
421
|
+
finish(fail4(new Error(`send extension command: ${command.method}`, { cause: error })));
|
|
302
422
|
}
|
|
303
|
-
return
|
|
423
|
+
return sync2(() => {
|
|
304
424
|
completed = true;
|
|
305
425
|
clearTimeout(timeout);
|
|
306
426
|
this.pendingRequests.delete(id);
|
|
@@ -342,7 +462,7 @@ function awaitsPagePromise(command) {
|
|
|
342
462
|
return false;
|
|
343
463
|
const method = command.params?.method;
|
|
344
464
|
const params = command.params?.params;
|
|
345
|
-
return (method === "Runtime.evaluate" || method === "Runtime.callFunctionOn") &&
|
|
465
|
+
return (method === "Runtime.evaluate" || method === "Runtime.callFunctionOn") && isObject(params) && params.awaitPromise === true;
|
|
346
466
|
}
|
|
347
467
|
|
|
348
468
|
// src/relay/browser-links.ts
|
|
@@ -491,8 +611,7 @@ class BrowserLinks {
|
|
|
491
611
|
}
|
|
492
612
|
|
|
493
613
|
// src/cdp/client-pool.ts
|
|
494
|
-
|
|
495
|
-
var ClientCdpSessionAlias = Data.taggedEnum();
|
|
614
|
+
var ClientCdpSessionAlias = taggedEnum();
|
|
496
615
|
|
|
497
616
|
class CdpClientPool {
|
|
498
617
|
send;
|
|
@@ -678,11 +797,10 @@ function canClientSeeTarget(options) {
|
|
|
678
797
|
}
|
|
679
798
|
|
|
680
799
|
// src/cdp/target-registry.ts
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
ownerSessionId: Schema.optionalKey(Schema.String)
|
|
800
|
+
class TargetOwnershipError extends TaggedError2()("TargetRegistry.TargetOwnershipError", {
|
|
801
|
+
message: String3,
|
|
802
|
+
reason: Literals(["not-found", "owned", "generation-changed"]),
|
|
803
|
+
ownerSessionId: optionalKey(String3)
|
|
686
804
|
}) {
|
|
687
805
|
}
|
|
688
806
|
|
|
@@ -1191,12 +1309,8 @@ class CdpRouter {
|
|
|
1191
1309
|
}
|
|
1192
1310
|
}
|
|
1193
1311
|
|
|
1194
|
-
// src/cdp/runtime.ts
|
|
1195
|
-
import { Deferred, Effect as Effect2, Fiber, Predicate as Predicate3 } from "effect";
|
|
1196
|
-
|
|
1197
1312
|
// src/update/runtime-diagnostics.ts
|
|
1198
1313
|
import crypto2 from "node:crypto";
|
|
1199
|
-
import { Predicate as Predicate2 } from "effect";
|
|
1200
1314
|
|
|
1201
1315
|
// src/execute/read-timeout.ts
|
|
1202
1316
|
var installed = new WeakSet;
|
|
@@ -1312,7 +1426,7 @@ function boundedToken(value) {
|
|
|
1312
1426
|
return `${normalized.slice(0, maxDiagnosticTokenLength - 9)}~${crypto2.createHash("sha256").update(value).digest("hex").slice(0, 8)}`;
|
|
1313
1427
|
}
|
|
1314
1428
|
function errorMessages(cause) {
|
|
1315
|
-
return errorCauses(cause).flatMap((error) => error instanceof Error ? [error.message] :
|
|
1429
|
+
return errorCauses(cause).flatMap((error) => error instanceof Error ? [error.message] : isString(error) ? [error] : []);
|
|
1316
1430
|
}
|
|
1317
1431
|
function errorCauses(cause) {
|
|
1318
1432
|
const causes = [];
|
|
@@ -1354,7 +1468,7 @@ class CdpRuntime {
|
|
|
1354
1468
|
this.settle(waiter);
|
|
1355
1469
|
for (const waiter of this.waiters) {
|
|
1356
1470
|
if (waiter.sessionId === event.sessionId && waiter.nativeReady(event))
|
|
1357
|
-
|
|
1471
|
+
doneUnsafe(waiter.ready, succeed6(true));
|
|
1358
1472
|
}
|
|
1359
1473
|
}
|
|
1360
1474
|
deliver(client, event) {
|
|
@@ -1380,7 +1494,7 @@ class CdpRuntime {
|
|
|
1380
1494
|
const requester = this.requester(client, route, canContinue);
|
|
1381
1495
|
return (result) => {
|
|
1382
1496
|
const frameId = getString2(getObject2(getObject2(getObject2(result)?.frameTree)?.frame), "id");
|
|
1383
|
-
if (!
|
|
1497
|
+
if (!isString(frameId) || !requester.current())
|
|
1384
1498
|
return;
|
|
1385
1499
|
requester.frameId = frameId;
|
|
1386
1500
|
for (const waiter of this.waiters)
|
|
@@ -1478,17 +1592,17 @@ class CdpRuntime {
|
|
|
1478
1592
|
sessions.set(sessionId, next);
|
|
1479
1593
|
return next;
|
|
1480
1594
|
}
|
|
1481
|
-
enable =
|
|
1595
|
+
enable = fn("CdpRuntime.enable")(function* (route, params, canContinue, client) {
|
|
1482
1596
|
const current = this.capture(route);
|
|
1483
1597
|
const permitted = () => current() && canContinue();
|
|
1484
1598
|
const requester = client ? this.requesters.get(client)?.get(routeSessionId(route)) : undefined;
|
|
1485
1599
|
const rootFrame = () => requester && permitted() && requester.current() ? requester.frameId : undefined;
|
|
1486
|
-
const first = yield* this.observe(route,
|
|
1600
|
+
const first = yield* this.observe(route, suspend2(() => permitted() ? this.options.send({
|
|
1487
1601
|
tabId: route.tabId,
|
|
1488
1602
|
method: "Runtime.enable",
|
|
1489
1603
|
params,
|
|
1490
1604
|
...route.chromeSessionId === undefined ? {} : { sessionId: route.chromeSessionId }
|
|
1491
|
-
}) :
|
|
1605
|
+
}) : fail4(new Error("CDP target changed before Runtime.enable"))), client && requester ? {
|
|
1492
1606
|
replay: () => {
|
|
1493
1607
|
const frameId = rootFrame();
|
|
1494
1608
|
return frameId !== undefined && this.replayCached(client, route, frameId);
|
|
@@ -1500,12 +1614,12 @@ class CdpRuntime {
|
|
|
1500
1614
|
} : {});
|
|
1501
1615
|
this.trace(route, `runtime-enable defaultContextSeen=${first.seen}`);
|
|
1502
1616
|
if (!first.seen && permitted()) {
|
|
1503
|
-
const retry = yield* this.observe(route, this.reset(route, "Runtime.disable", {}, permitted).pipe(
|
|
1617
|
+
const retry = yield* this.observe(route, this.reset(route, "Runtime.disable", {}, permitted).pipe(andThen(() => this.reset(route, "Runtime.enable", params, permitted))));
|
|
1504
1618
|
this.trace(route, `runtime-reset phase=missing-default-context defaultContextSeen=${retry.seen}`);
|
|
1505
1619
|
}
|
|
1506
1620
|
return first.result;
|
|
1507
1621
|
});
|
|
1508
|
-
disableIdle =
|
|
1622
|
+
disableIdle = fn("CdpRuntime.disableIdle")(function* (stillIdle) {
|
|
1509
1623
|
const { registry } = this.options;
|
|
1510
1624
|
const routes = registry.listRootTargets().map((target) => ({ tabId: target.tabId, rootSessionId: target.sessionId }));
|
|
1511
1625
|
for (const target of registry.childTargets.values()) {
|
|
@@ -1535,34 +1649,34 @@ class CdpRuntime {
|
|
|
1535
1649
|
}
|
|
1536
1650
|
settle(waiter) {
|
|
1537
1651
|
if (waiter.replay?.())
|
|
1538
|
-
|
|
1652
|
+
doneUnsafe(waiter.ready, succeed6(true));
|
|
1539
1653
|
}
|
|
1540
1654
|
observe(route, command, options = {}) {
|
|
1541
1655
|
const runtime = this;
|
|
1542
|
-
return
|
|
1543
|
-
const waiter = { sessionId: routeSessionId(route), ready:
|
|
1656
|
+
return acquireUseRelease(sync2(() => {
|
|
1657
|
+
const waiter = { sessionId: routeSessionId(route), ready: makeUnsafe3(), nativeReady: options.nativeReady ?? (() => true) };
|
|
1544
1658
|
runtime.waiters.add(waiter);
|
|
1545
1659
|
return waiter;
|
|
1546
|
-
}), (waiter) =>
|
|
1547
|
-
const seen = yield*
|
|
1660
|
+
}), (waiter) => gen(function* () {
|
|
1661
|
+
const seen = yield* forkScoped(_await(waiter.ready).pipe(timeoutOrElse({
|
|
1548
1662
|
duration: "3 seconds",
|
|
1549
|
-
orElse: () =>
|
|
1663
|
+
orElse: () => succeed6(false)
|
|
1550
1664
|
})), { startImmediately: true });
|
|
1551
1665
|
const result = yield* command;
|
|
1552
1666
|
if (options.replay)
|
|
1553
1667
|
waiter.replay = options.replay;
|
|
1554
1668
|
runtime.settle(waiter);
|
|
1555
|
-
return { result, seen: yield*
|
|
1556
|
-
}).pipe(
|
|
1669
|
+
return { result, seen: yield* join(seen) };
|
|
1670
|
+
}).pipe(scoped), (waiter) => sync2(() => {
|
|
1557
1671
|
runtime.waiters.delete(waiter);
|
|
1558
1672
|
}));
|
|
1559
1673
|
}
|
|
1560
1674
|
reset(route, method, params, current) {
|
|
1561
|
-
return
|
|
1675
|
+
return suspend2(() => {
|
|
1562
1676
|
if (!current())
|
|
1563
|
-
return
|
|
1677
|
+
return void_3;
|
|
1564
1678
|
this.trace(route, `runtime-reset command=${method}`);
|
|
1565
|
-
return this.options.send({ tabId: route.tabId, method, params, ...route.chromeSessionId === undefined ? {} : { sessionId: route.chromeSessionId } }).pipe(
|
|
1679
|
+
return this.options.send({ tabId: route.tabId, method, params, ...route.chromeSessionId === undefined ? {} : { sessionId: route.chromeSessionId } }).pipe(match3({
|
|
1566
1680
|
onFailure: (error) => this.trace(route, `runtime-reset command=${method} outcome=failed failure=${runtimeFailureKind(error)}`),
|
|
1567
1681
|
onSuccess: () => this.trace(route, `runtime-reset command=${method} outcome=ok`)
|
|
1568
1682
|
}));
|
|
@@ -1590,7 +1704,6 @@ class CdpRuntime {
|
|
|
1590
1704
|
}
|
|
1591
1705
|
|
|
1592
1706
|
// src/execute.ts
|
|
1593
|
-
import { Effect as Effect13 } from "effect";
|
|
1594
1707
|
import { chromium, errors } from "patchright-core";
|
|
1595
1708
|
|
|
1596
1709
|
// src/snapshot/aria-snapshot.ts
|
|
@@ -1800,18 +1913,17 @@ function redactionContextIsGone(cause) {
|
|
|
1800
1913
|
}
|
|
1801
1914
|
|
|
1802
1915
|
// src/auth/authenticated-origin.ts
|
|
1803
|
-
import { Effect as Effect3, Match, Schema as Schema2 } from "effect";
|
|
1804
1916
|
var defaultTimeoutMs = 30000;
|
|
1805
1917
|
var defaultMaxResponseBytes = 2000000;
|
|
1806
1918
|
|
|
1807
|
-
class AuthenticatedOriginError extends
|
|
1808
|
-
message:
|
|
1809
|
-
reason:
|
|
1810
|
-
cause:
|
|
1919
|
+
class AuthenticatedOriginError extends TaggedError2()("AuthenticatedOrigin.Error", {
|
|
1920
|
+
message: String3,
|
|
1921
|
+
reason: Literals(["invalid-request", "page-failed"]),
|
|
1922
|
+
cause: optionalKey(Defect())
|
|
1811
1923
|
}) {
|
|
1812
1924
|
}
|
|
1813
|
-
var requestJson =
|
|
1814
|
-
const target = yield*
|
|
1925
|
+
var requestJson = fn("AuthenticatedOrigin.requestJson")(function* (page, request) {
|
|
1926
|
+
const target = yield* try_({
|
|
1815
1927
|
try: () => {
|
|
1816
1928
|
const origin = normalizeOrigin(request.origin);
|
|
1817
1929
|
return {
|
|
@@ -1828,7 +1940,7 @@ var requestJson = Effect3.fn("AuthenticatedOrigin.requestJson")(function* (page,
|
|
|
1828
1940
|
});
|
|
1829
1941
|
const { origin, requestUrl, startUrl } = target;
|
|
1830
1942
|
if (pageOrigin(page.url()) !== origin && startUrl !== undefined) {
|
|
1831
|
-
yield*
|
|
1943
|
+
yield* tryPromise({
|
|
1832
1944
|
try: () => page.goto(startUrl, { waitUntil: "domcontentloaded", timeout: request.timeoutMs ?? defaultTimeoutMs }),
|
|
1833
1945
|
catch: (cause) => new AuthenticatedOriginError({
|
|
1834
1946
|
message: cause instanceof Error ? cause.message : "Navigate to authenticated origin",
|
|
@@ -1853,15 +1965,15 @@ var requestJson = Effect3.fn("AuthenticatedOrigin.requestJson")(function* (page,
|
|
|
1853
1965
|
timeoutMs: request.timeoutMs ?? defaultTimeoutMs,
|
|
1854
1966
|
maxResponseBytes: request.maxResponseBytes ?? defaultMaxResponseBytes
|
|
1855
1967
|
};
|
|
1856
|
-
const pageResult = yield*
|
|
1968
|
+
const pageResult = yield* tryPromise({
|
|
1857
1969
|
try: () => page.evaluate(runPageRequest, input),
|
|
1858
1970
|
catch: (cause) => new AuthenticatedOriginError({
|
|
1859
1971
|
message: cause instanceof Error ? cause.message : "Run authenticated page request",
|
|
1860
1972
|
reason: "page-failed",
|
|
1861
1973
|
cause
|
|
1862
1974
|
})
|
|
1863
|
-
}).pipe(
|
|
1864
|
-
return
|
|
1975
|
+
}).pipe(uninterruptible);
|
|
1976
|
+
return value2(pageResult).pipe(when2({ kind: "OriginMismatch" }, ({ expectedOrigin, actualOrigin }) => AuthenticatedJsonOutcome.cases.OriginMismatch.make({ expectedOrigin, actualOrigin })), when2({ kind: "HttpError" }, ({ status }) => AuthenticatedJsonOutcome.cases.HttpError.make({ status })), when2({ kind: "ResponseTooLarge" }, ({ status, maxResponseBytes }) => AuthenticatedJsonOutcome.cases.ResponseTooLarge.make({ status, maxResponseBytes })), when2({ kind: "Success" }, ({ status, value }) => AuthenticatedJsonOutcome.cases.Success.make({ status, value })), when2({ kind: "InvalidJson" }, ({ status }) => AuthenticatedJsonOutcome.cases.InvalidJson.make({ status })), when2({ kind: "RequestFailed" }, ({ outcome }) => AuthenticatedJsonOutcome.cases.RequestFailed.make({ outcome })), exhaustive);
|
|
1865
1977
|
});
|
|
1866
1978
|
function normalizeOrigin(value) {
|
|
1867
1979
|
let url;
|
|
@@ -3546,7 +3658,6 @@ function describe(arg) {
|
|
|
3546
3658
|
|
|
3547
3659
|
// src/execute/log-capture.ts
|
|
3548
3660
|
import util4 from "node:util";
|
|
3549
|
-
import { Predicate as Predicate4 } from "effect";
|
|
3550
3661
|
var maxCapturedPageLogs = 50;
|
|
3551
3662
|
var maxCapturedScriptLogs = 100;
|
|
3552
3663
|
function createExecuteLogCapture(limits = {}) {
|
|
@@ -3732,20 +3843,16 @@ function createSandboxConsole(options) {
|
|
|
3732
3843
|
};
|
|
3733
3844
|
}
|
|
3734
3845
|
function formatLogValue(value) {
|
|
3735
|
-
if (
|
|
3846
|
+
if (isString(value)) {
|
|
3736
3847
|
return value;
|
|
3737
3848
|
}
|
|
3738
3849
|
return util4.inspect(value, { depth: 4, colors: false, maxArrayLength: 100, maxStringLength: 1000 });
|
|
3739
3850
|
}
|
|
3740
3851
|
|
|
3741
|
-
// src/execute/page-recovery.ts
|
|
3742
|
-
import { Effect as Effect5, Schema as Schema4 } from "effect";
|
|
3743
|
-
|
|
3744
3852
|
// src/execute/target-selection.ts
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
reason: Schema3.Literals(["invalid", "not-found", "ambiguous"])
|
|
3853
|
+
class TargetSelectionError extends TaggedError2()("Execute.TargetSelectionError", {
|
|
3854
|
+
message: String3,
|
|
3855
|
+
reason: Literals(["invalid", "not-found", "ambiguous"])
|
|
3749
3856
|
}) {
|
|
3750
3857
|
}
|
|
3751
3858
|
function hasExplicitTargetSelection(selection) {
|
|
@@ -3811,11 +3918,11 @@ function selectPage({ pages, selection }) {
|
|
|
3811
3918
|
return selectTarget({ targets: pages, selection, getUrl: (page) => page.url() });
|
|
3812
3919
|
}
|
|
3813
3920
|
async function resolvePageTargetId(page) {
|
|
3814
|
-
return await
|
|
3921
|
+
return await runPromise(runPlaywrightOperation({
|
|
3815
3922
|
label: "Resolve session page target id",
|
|
3816
3923
|
timeoutMs: playwrightCloseTimeoutMs,
|
|
3817
3924
|
run: () => pageTargetId(page)
|
|
3818
|
-
}).pipe(
|
|
3925
|
+
}).pipe(match3({
|
|
3819
3926
|
onFailure: () => {
|
|
3820
3927
|
return;
|
|
3821
3928
|
},
|
|
@@ -3867,18 +3974,18 @@ var defaultPageReattachedWarning = "Chrome re-attached the debugger to the sessi
|
|
|
3867
3974
|
var pdfViewerWarning = `This tab shows a PDF in Chrome's viewer, whose text isn't in the DOM. page.url() is the file's URL; save the file with: const b64 = await page.evaluate(async () => { const bytes = new Uint8Array(await (await fetch(location.href)).arrayBuffer()); let s = ""; for (let i = 0; i < bytes.length; i += 0x8000) s += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); return btoa(s) }); await files.write("file.pdf", Buffer.from(b64, "base64"))`;
|
|
3868
3975
|
var defaultPageReplacedWarning = "The session default page target was replaced; rebound to the same browser tab. References to the old page in state are stale.";
|
|
3869
3976
|
|
|
3870
|
-
class PlaywrightOperationError extends
|
|
3871
|
-
message:
|
|
3872
|
-
operation:
|
|
3873
|
-
reason:
|
|
3874
|
-
cause:
|
|
3977
|
+
class PlaywrightOperationError extends TaggedError2()("Execute.PlaywrightOperationError", {
|
|
3978
|
+
message: String3,
|
|
3979
|
+
operation: String3,
|
|
3980
|
+
reason: Literals(["failed", "timeout"]),
|
|
3981
|
+
cause: optionalKey(Defect())
|
|
3875
3982
|
}) {
|
|
3876
3983
|
}
|
|
3877
3984
|
|
|
3878
|
-
class SessionPageRecoveryError extends
|
|
3879
|
-
message:
|
|
3880
|
-
reason:
|
|
3881
|
-
cause:
|
|
3985
|
+
class SessionPageRecoveryError extends TaggedError2()("Execute.SessionPageRecoveryError", {
|
|
3986
|
+
message: String3,
|
|
3987
|
+
reason: Literals(["adopted-unresponsive", "adopted-initialization-timeout", "owned-unresponsive", "close-failed", "target-unavailable"]),
|
|
3988
|
+
cause: Defect()
|
|
3882
3989
|
}) {
|
|
3883
3990
|
}
|
|
3884
3991
|
|
|
@@ -3899,27 +4006,27 @@ var operationFailure = (label, cause) => new PlaywrightOperationError({
|
|
|
3899
4006
|
reason: "failed",
|
|
3900
4007
|
cause
|
|
3901
4008
|
});
|
|
3902
|
-
var runPlaywrightOperation =
|
|
4009
|
+
var runPlaywrightOperation = fn("Execute.playwrightOperation")((options) => tryPromise({
|
|
3903
4010
|
try: options.run,
|
|
3904
4011
|
catch: (cause) => operationFailure(options.label, cause)
|
|
3905
|
-
}).pipe(
|
|
4012
|
+
}).pipe(timeoutOrElse({
|
|
3906
4013
|
duration: options.timeoutMs,
|
|
3907
|
-
orElse: () =>
|
|
4014
|
+
orElse: () => fail4(new PlaywrightOperationError({
|
|
3908
4015
|
message: `${options.label} timed out after ${options.timeoutMs}ms`,
|
|
3909
4016
|
operation: options.label,
|
|
3910
4017
|
reason: "timeout"
|
|
3911
4018
|
}))
|
|
3912
4019
|
})));
|
|
3913
|
-
var runSettledPlaywrightOperation =
|
|
4020
|
+
var runSettledPlaywrightOperation = fn("Execute.settledPlaywrightOperation")((options) => tryPromise({
|
|
3914
4021
|
try: options.run,
|
|
3915
4022
|
catch: (cause) => operationFailure(options.label, cause)
|
|
3916
4023
|
}));
|
|
3917
|
-
var recoverSessionPage =
|
|
4024
|
+
var recoverSessionPage = fn("Execute.recoverSessionPage")(function* (options) {
|
|
3918
4025
|
const healthFailure = yield* runPlaywrightOperation({
|
|
3919
4026
|
label: "Session page health check",
|
|
3920
4027
|
timeoutMs: options.timeoutMs,
|
|
3921
4028
|
run: options.healthCheck
|
|
3922
|
-
}).pipe(
|
|
4029
|
+
}).pipe(match3({
|
|
3923
4030
|
onFailure: (error) => error,
|
|
3924
4031
|
onSuccess: () => {
|
|
3925
4032
|
return;
|
|
@@ -3949,7 +4056,7 @@ var recoverSessionPage = Effect5.fn("Execute.recoverSessionPage")(function* (opt
|
|
|
3949
4056
|
label: "Close unhealthy session page",
|
|
3950
4057
|
timeoutMs: options.timeoutMs,
|
|
3951
4058
|
run: options.close
|
|
3952
|
-
}).pipe(
|
|
4059
|
+
}).pipe(match3({
|
|
3953
4060
|
onFailure: (error) => error,
|
|
3954
4061
|
onSuccess: () => {
|
|
3955
4062
|
return;
|
|
@@ -4044,11 +4151,11 @@ var shouldCloseCurrentPageOnAdopt = (options) => {
|
|
|
4044
4151
|
return options.hasCurrentPage && options.ownsCurrentPage && !options.currentPageIsSelected && !options.currentPageIsClosed;
|
|
4045
4152
|
};
|
|
4046
4153
|
async function showsPdf(page) {
|
|
4047
|
-
return await
|
|
4154
|
+
return await runPromise(runPlaywrightOperation({
|
|
4048
4155
|
label: "Check for the PDF viewer",
|
|
4049
4156
|
timeoutMs: playwrightCloseTimeoutMs,
|
|
4050
4157
|
run: () => page.evaluate(() => document.contentType === "application/pdf")
|
|
4051
|
-
}).pipe(
|
|
4158
|
+
}).pipe(orElseSucceed(() => false)));
|
|
4052
4159
|
}
|
|
4053
4160
|
async function openPageWithTargetId(context, targetId) {
|
|
4054
4161
|
const pages = context.pages().filter((page) => !page.isClosed());
|
|
@@ -4366,9 +4473,6 @@ ${wrapCode(code)}
|
|
|
4366
4473
|
}`;
|
|
4367
4474
|
}
|
|
4368
4475
|
|
|
4369
|
-
// src/execute/default-page.ts
|
|
4370
|
-
import { Effect as Effect6 } from "effect";
|
|
4371
|
-
|
|
4372
4476
|
// src/execute/guards.ts
|
|
4373
4477
|
var downloadCapabilityErrorMessage = "Downloads are unavailable in browse extension-backed tabs: Chromium blocks Browser.setDownloadBehavior and Page.setDownloadBehavior through chrome.debugger, so Playwright cannot retain an artifact for download.saveAs(). Fetch the response in the page and save the returned bytes with files.write() when the site exposes them.";
|
|
4374
4478
|
var downloadGuardedPages = new WeakSet;
|
|
@@ -4692,7 +4796,7 @@ class DefaultPageHost {
|
|
|
4692
4796
|
async checkSessionPage(page, options) {
|
|
4693
4797
|
const timeoutMs = this.pageHealthCheckTimeoutMsOption ?? sessionPageHealthCheckTimeoutMs;
|
|
4694
4798
|
const sandbox = this;
|
|
4695
|
-
const recovery = await
|
|
4799
|
+
const recovery = await runPromise(recoverSessionPage({
|
|
4696
4800
|
ownsPage: this.ownsPage,
|
|
4697
4801
|
get url() {
|
|
4698
4802
|
return page.url();
|
|
@@ -4726,7 +4830,6 @@ class DefaultPageHost {
|
|
|
4726
4830
|
|
|
4727
4831
|
// src/execute/result-formatting.ts
|
|
4728
4832
|
import util5 from "node:util";
|
|
4729
|
-
import { Predicate as Predicate5 } from "effect";
|
|
4730
4833
|
var maxExecuteResultChars = 1e5;
|
|
4731
4834
|
function boundExecuteResult(text, value, hasValue) {
|
|
4732
4835
|
const valueChars = hasValue && value !== null && typeof value === "object" ? safeJsonStringify(value)?.length ?? 0 : typeof value === "string" ? value.length : 0;
|
|
@@ -4742,7 +4845,7 @@ function boundExecuteResult(text, value, hasValue) {
|
|
|
4742
4845
|
};
|
|
4743
4846
|
}
|
|
4744
4847
|
function stringifyResult(result) {
|
|
4745
|
-
if (
|
|
4848
|
+
if (isString(result)) {
|
|
4746
4849
|
return result;
|
|
4747
4850
|
}
|
|
4748
4851
|
if (result === undefined) {
|
|
@@ -4769,7 +4872,7 @@ function extractExecuteMedia(value) {
|
|
|
4769
4872
|
media.push(image);
|
|
4770
4873
|
return { type: image.type, mimeType: image.mimeType, size: image.size };
|
|
4771
4874
|
}
|
|
4772
|
-
if (!
|
|
4875
|
+
if (!isObjectOrArray(item))
|
|
4773
4876
|
return item;
|
|
4774
4877
|
const previous = seen.get(item);
|
|
4775
4878
|
if (previous !== undefined)
|
|
@@ -4845,25 +4948,25 @@ function jsonValueWarning(value, jsonSafe) {
|
|
|
4845
4948
|
return `Execute result could not be represented as JSON value: ${jsonSafe.reason}`;
|
|
4846
4949
|
}
|
|
4847
4950
|
function convertJsonSafe(value, options) {
|
|
4848
|
-
if (value === null ||
|
|
4951
|
+
if (value === null || isString(value) || isBoolean(value)) {
|
|
4849
4952
|
return { omit: false, value };
|
|
4850
4953
|
}
|
|
4851
|
-
if (
|
|
4954
|
+
if (isNumber(value)) {
|
|
4852
4955
|
return { omit: false, value: Number.isFinite(value) ? value : null };
|
|
4853
4956
|
}
|
|
4854
|
-
if (
|
|
4957
|
+
if (isBigInt(value)) {
|
|
4855
4958
|
return { omit: false, value: value.toString() };
|
|
4856
4959
|
}
|
|
4857
4960
|
if (value === undefined) {
|
|
4858
4961
|
return { omit: true, reason: "undefined" };
|
|
4859
4962
|
}
|
|
4860
|
-
if (
|
|
4963
|
+
if (isFunction(value)) {
|
|
4861
4964
|
return { omit: true, reason: "function value" };
|
|
4862
4965
|
}
|
|
4863
|
-
if (
|
|
4966
|
+
if (isSymbol(value)) {
|
|
4864
4967
|
return { omit: true, reason: "symbol value" };
|
|
4865
4968
|
}
|
|
4866
|
-
if (!
|
|
4969
|
+
if (!isObjectOrArray(value)) {
|
|
4867
4970
|
return { omit: true, reason: "unsupported value" };
|
|
4868
4971
|
}
|
|
4869
4972
|
const isMap = value instanceof Map;
|
|
@@ -4889,7 +4992,7 @@ function convertJsonSafe(value, options) {
|
|
|
4889
4992
|
return { omit: true, reason: "map entries unavailable" };
|
|
4890
4993
|
}
|
|
4891
4994
|
for (const [key, item] of entries) {
|
|
4892
|
-
if (!
|
|
4995
|
+
if (!isString(key)) {
|
|
4893
4996
|
return { omit: true, reason: "map contains non-string key" };
|
|
4894
4997
|
}
|
|
4895
4998
|
const converted = convertJsonSafe(item, { seen: options.seen, depth: options.depth + 1 });
|
|
@@ -4992,37 +5095,33 @@ function safeJsonStringify(value) {
|
|
|
4992
5095
|
}
|
|
4993
5096
|
}
|
|
4994
5097
|
|
|
4995
|
-
// src/execute/sandbox-globals.ts
|
|
4996
|
-
import { Effect as Effect9 } from "effect";
|
|
4997
|
-
|
|
4998
5098
|
// src/execute/demonstration.ts
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
name: Schema5.optionalKey(Schema5.String)
|
|
5099
|
+
var DemonstrationStep = Union([
|
|
5100
|
+
Struct({
|
|
5101
|
+
kind: Literal("click"),
|
|
5102
|
+
selector: String3,
|
|
5103
|
+
role: optionalKey(String3),
|
|
5104
|
+
name: optionalKey(String3)
|
|
5006
5105
|
}),
|
|
5007
|
-
|
|
5008
|
-
kind:
|
|
5009
|
-
selector:
|
|
5010
|
-
value:
|
|
5011
|
-
redacted:
|
|
5106
|
+
Struct({
|
|
5107
|
+
kind: Literal("fill"),
|
|
5108
|
+
selector: String3,
|
|
5109
|
+
value: String3,
|
|
5110
|
+
redacted: optionalKey(Boolean3)
|
|
5012
5111
|
}),
|
|
5013
|
-
|
|
5014
|
-
kind:
|
|
5015
|
-
selector:
|
|
5016
|
-
checked:
|
|
5112
|
+
Struct({
|
|
5113
|
+
kind: Literal("check"),
|
|
5114
|
+
selector: String3,
|
|
5115
|
+
checked: Boolean3
|
|
5017
5116
|
}),
|
|
5018
|
-
|
|
5019
|
-
kind:
|
|
5020
|
-
selector:
|
|
5021
|
-
value:
|
|
5117
|
+
Struct({
|
|
5118
|
+
kind: Literal("select"),
|
|
5119
|
+
selector: String3,
|
|
5120
|
+
value: String3
|
|
5022
5121
|
}),
|
|
5023
|
-
|
|
5024
|
-
kind:
|
|
5025
|
-
url:
|
|
5122
|
+
Struct({
|
|
5123
|
+
kind: Literal("navigation"),
|
|
5124
|
+
url: String3
|
|
5026
5125
|
})
|
|
5027
5126
|
]);
|
|
5028
5127
|
var pageRecorders = new WeakMap;
|
|
@@ -5089,10 +5188,10 @@ async function startDemonstrationRecorder(page) {
|
|
|
5089
5188
|
function formatDemonstrationCode(options) {
|
|
5090
5189
|
const lines = [
|
|
5091
5190
|
`// Recorded from ${options.startedUrl}`,
|
|
5092
|
-
...options.steps.map((step) =>
|
|
5191
|
+
...options.steps.map((step) => value2(step).pipe(when2({ kind: "navigation" }, ({ url }) => `// Navigated to ${url}`), when2({ kind: "click" }, ({ selector, role, name }) => {
|
|
5093
5192
|
const description = role && name ? ` // ${role} ${JSON.stringify(name)}` : "";
|
|
5094
5193
|
return `await page.locator(${JSON.stringify(selector)}).click()${description}`;
|
|
5095
|
-
}),
|
|
5194
|
+
}), when2({ kind: "fill" }, ({ selector, value, redacted }) => redacted ? `// Fill ${JSON.stringify(selector)} from an approved secret source.` : `await page.locator(${JSON.stringify(selector)}).fill(${JSON.stringify(value)})`), when2({ kind: "check" }, ({ selector, checked }) => `await page.locator(${JSON.stringify(selector)}).${checked ? "check" : "uncheck"}()`), when2({ kind: "select" }, ({ selector, value }) => `await page.locator(${JSON.stringify(selector)}).selectOption(${JSON.stringify(value)})`), exhaustive)),
|
|
5096
5195
|
"return { url: page.url(), title: await page.title() }"
|
|
5097
5196
|
];
|
|
5098
5197
|
return lines.join(`
|
|
@@ -5116,7 +5215,7 @@ function appendStep(steps, next) {
|
|
|
5116
5215
|
return;
|
|
5117
5216
|
steps.push(next);
|
|
5118
5217
|
}
|
|
5119
|
-
var isDemonstrationStep =
|
|
5218
|
+
var isDemonstrationStep = is(DemonstrationStep);
|
|
5120
5219
|
function installDemonstrationListeners(options) {
|
|
5121
5220
|
const world = globalThis;
|
|
5122
5221
|
if (world.__opencodeBrowserDemonstration?.id === options.recorderId)
|
|
@@ -5250,9 +5349,6 @@ function removeDemonstrationListeners(options) {
|
|
|
5250
5349
|
state.cleanup();
|
|
5251
5350
|
}
|
|
5252
5351
|
|
|
5253
|
-
// src/ghost-cursor.ts
|
|
5254
|
-
import { Effect as Effect7, Match as Match3, Predicate as Predicate7 } from "effect";
|
|
5255
|
-
|
|
5256
5352
|
// src/ghost-cursor/types.ts
|
|
5257
5353
|
var ghostCursorClientVersion = 13;
|
|
5258
5354
|
var ghostCursorOperationList = [
|
|
@@ -6244,7 +6340,6 @@ var ghostCursorOverlaysSource = ` const zoomTo = (payload) => {
|
|
|
6244
6340
|
})();`;
|
|
6245
6341
|
|
|
6246
6342
|
// src/ghost-cursor/page-commands.ts
|
|
6247
|
-
import { Predicate as Predicate6 } from "effect";
|
|
6248
6343
|
var ghostCursorCommandMethod = "OpenCodeBrowser.ghostCursor";
|
|
6249
6344
|
async function runGhostCursorOperation(page, op, payload) {
|
|
6250
6345
|
const session = await page.context().newCDPSession(page);
|
|
@@ -6258,7 +6353,7 @@ async function runGhostCursorOperation(page, op, payload) {
|
|
|
6258
6353
|
}
|
|
6259
6354
|
}
|
|
6260
6355
|
async function resolveTargetBox(page, target) {
|
|
6261
|
-
const locator =
|
|
6356
|
+
const locator = isString(target) ? page.locator(target) : target;
|
|
6262
6357
|
return await locator.first().boundingBox();
|
|
6263
6358
|
}
|
|
6264
6359
|
async function showGhostCursor(options) {
|
|
@@ -6288,7 +6383,7 @@ async function clearGhostCursorCallouts(options) {
|
|
|
6288
6383
|
async function zoomGhostCursorCamera(options) {
|
|
6289
6384
|
let rect = null;
|
|
6290
6385
|
if (options.target !== null) {
|
|
6291
|
-
rect =
|
|
6386
|
+
rect = isString(options.target) || "boundingBox" in options.target ? await resolveTargetBox(options.page, options.target) : { x: options.target.x, y: options.target.y, width: 1, height: 1 };
|
|
6292
6387
|
}
|
|
6293
6388
|
const payload = {
|
|
6294
6389
|
rect,
|
|
@@ -6306,7 +6401,7 @@ async function setGhostCursorSpotlight(options) {
|
|
|
6306
6401
|
await runGhostCursorOperation(options.page, "setSpotlight", payload);
|
|
6307
6402
|
}
|
|
6308
6403
|
async function showGhostCursorKeys(options) {
|
|
6309
|
-
const keys =
|
|
6404
|
+
const keys = isString(options.keys) ? options.keys.split("+").map((k) => k.trim()).filter(Boolean) : options.keys;
|
|
6310
6405
|
const payload = {
|
|
6311
6406
|
keys,
|
|
6312
6407
|
...options.label ? { label: options.label } : {}
|
|
@@ -6326,12 +6421,12 @@ function inputDispatchMouseEventToGhostCursorAction(params) {
|
|
|
6326
6421
|
if (type !== "mouseMoved" && type !== "mousePressed" && type !== "mouseReleased") {
|
|
6327
6422
|
return;
|
|
6328
6423
|
}
|
|
6329
|
-
if (!
|
|
6424
|
+
if (!isNumber(params.x) || !isNumber(params.y)) {
|
|
6330
6425
|
return;
|
|
6331
6426
|
}
|
|
6332
6427
|
const button = parseButton(params.button);
|
|
6333
6428
|
return {
|
|
6334
|
-
type:
|
|
6429
|
+
type: value2(type).pipe(when2("mousePressed", () => "down"), when2("mouseReleased", () => "up"), when2("mouseMoved", () => "move"), exhaustive),
|
|
6335
6430
|
x: params.x,
|
|
6336
6431
|
y: params.y,
|
|
6337
6432
|
button
|
|
@@ -6351,7 +6446,7 @@ class GhostCursorTabs {
|
|
|
6351
6446
|
const mode = this.tabs.get(tabId)?.mode;
|
|
6352
6447
|
return mode === "persistent" || mode === undefined && this.options.shownByDefault(tabId);
|
|
6353
6448
|
}
|
|
6354
|
-
mouseEvent =
|
|
6449
|
+
mouseEvent = fnUntraced(function* (tabId, params) {
|
|
6355
6450
|
const action = inputDispatchMouseEventToGhostCursorAction(params);
|
|
6356
6451
|
if (!action)
|
|
6357
6452
|
return;
|
|
@@ -6360,7 +6455,7 @@ class GhostCursorTabs {
|
|
|
6360
6455
|
state.position = { x: action.x, y: action.y };
|
|
6361
6456
|
if (!this.visible(tabId))
|
|
6362
6457
|
return;
|
|
6363
|
-
yield*
|
|
6458
|
+
yield* gen({ self: this }, function* () {
|
|
6364
6459
|
if (!state.drawn) {
|
|
6365
6460
|
yield* this.call(tabId, "show", state.cursorOptions ?? {});
|
|
6366
6461
|
if (previous)
|
|
@@ -6368,9 +6463,9 @@ class GhostCursorTabs {
|
|
|
6368
6463
|
this.drawn(tabId, state);
|
|
6369
6464
|
}
|
|
6370
6465
|
yield* this.call(tabId, "applyMouseEvent", action, true);
|
|
6371
|
-
}).pipe(
|
|
6466
|
+
}).pipe(timeout("2 seconds"), ignore);
|
|
6372
6467
|
});
|
|
6373
|
-
documentCreated =
|
|
6468
|
+
documentCreated = fnUntraced(function* (tabId) {
|
|
6374
6469
|
const state = this.tabs.get(tabId);
|
|
6375
6470
|
if (!state)
|
|
6376
6471
|
return;
|
|
@@ -6383,16 +6478,16 @@ class GhostCursorTabs {
|
|
|
6383
6478
|
if (state.caption)
|
|
6384
6479
|
yield* this.call(tabId, "setCaption", state.caption);
|
|
6385
6480
|
});
|
|
6386
|
-
command =
|
|
6481
|
+
command = fnUntraced(function* (tabId, params) {
|
|
6387
6482
|
const operation = params?.op;
|
|
6388
|
-
if (!
|
|
6389
|
-
return yield*
|
|
6483
|
+
if (!isString(operation) || !ghostCursorOperations.has(operation)) {
|
|
6484
|
+
return yield* fail4(new Error(`Unknown ghost cursor operation: ${String(operation)}`));
|
|
6390
6485
|
}
|
|
6391
6486
|
const payload = params?.payload ?? null;
|
|
6392
6487
|
const state = this.state(tabId);
|
|
6393
6488
|
if (operation === "show") {
|
|
6394
6489
|
state.mode = "persistent";
|
|
6395
|
-
state.cursorOptions =
|
|
6490
|
+
state.cursorOptions = isObject(payload) && !Array.isArray(payload) ? payload : {};
|
|
6396
6491
|
yield* this.call(tabId, "show", state.cursorOptions);
|
|
6397
6492
|
if (state.position)
|
|
6398
6493
|
yield* this.call(tabId, "restore", state.position);
|
|
@@ -6407,7 +6502,7 @@ class GhostCursorTabs {
|
|
|
6407
6502
|
return {};
|
|
6408
6503
|
}
|
|
6409
6504
|
if (operation === "setCaption") {
|
|
6410
|
-
if (
|
|
6505
|
+
if (isObject(payload) && !Array.isArray(payload))
|
|
6411
6506
|
state.caption = payload;
|
|
6412
6507
|
else
|
|
6413
6508
|
delete state.caption;
|
|
@@ -6438,7 +6533,7 @@ class GhostCursorTabs {
|
|
|
6438
6533
|
current.drawn = false;
|
|
6439
6534
|
delete current.caption;
|
|
6440
6535
|
}
|
|
6441
|
-
hides.push(
|
|
6536
|
+
hides.push(runPromise(ignore(this.call(id, "hide", null, false, false).pipe(timeout("2 seconds")))).catch(() => {}));
|
|
6442
6537
|
}
|
|
6443
6538
|
if (this.shownByDefault.size === 0 && this.watchTimer) {
|
|
6444
6539
|
clearInterval(this.watchTimer);
|
|
@@ -6456,7 +6551,7 @@ class GhostCursorTabs {
|
|
|
6456
6551
|
}
|
|
6457
6552
|
return state;
|
|
6458
6553
|
}
|
|
6459
|
-
call =
|
|
6554
|
+
call = fnUntraced(function* (tabId, method, payload, awaitPromise = false, install = true) {
|
|
6460
6555
|
const frameId = this.options.mainFrameId(tabId);
|
|
6461
6556
|
if (!frameId)
|
|
6462
6557
|
return;
|
|
@@ -6465,8 +6560,8 @@ class GhostCursorTabs {
|
|
|
6465
6560
|
method: "Page.createIsolatedWorld",
|
|
6466
6561
|
params: { frameId, worldName: this.worldName }
|
|
6467
6562
|
});
|
|
6468
|
-
const contextId =
|
|
6469
|
-
if (!
|
|
6563
|
+
const contextId = isObject(world) && "executionContextId" in world ? world.executionContextId : undefined;
|
|
6564
|
+
if (!isNumber(contextId))
|
|
6470
6565
|
return;
|
|
6471
6566
|
const api = "globalThis.__opencodeBrowserGhostCursor";
|
|
6472
6567
|
const evaluate = (expression) => this.options.send({
|
|
@@ -6480,7 +6575,7 @@ class GhostCursorTabs {
|
|
|
6480
6575
|
return;
|
|
6481
6576
|
}
|
|
6482
6577
|
const result = yield* evaluate(`${api} ? ${invoke} : ${JSON.stringify(missingGhostCursor)}`);
|
|
6483
|
-
const value =
|
|
6578
|
+
const value = isObject(result) && "result" in result && isObject(result.result) && "value" in result.result ? result.result.value : undefined;
|
|
6484
6579
|
if (value !== missingGhostCursor)
|
|
6485
6580
|
return;
|
|
6486
6581
|
yield* evaluate(`${ghostCursorClientSource}
|
|
@@ -6498,7 +6593,6 @@ function parseButton(value) {
|
|
|
6498
6593
|
// src/snapshot/screenshot-diff.ts
|
|
6499
6594
|
import fs5 from "node:fs/promises";
|
|
6500
6595
|
import path3 from "node:path";
|
|
6501
|
-
import { Predicate as Predicate8 } from "effect";
|
|
6502
6596
|
import pixelmatch from "pixelmatch";
|
|
6503
6597
|
import { PNG } from "pngjs";
|
|
6504
6598
|
var maxImageBytes = 32 * 1024 * 1024;
|
|
@@ -6510,7 +6604,7 @@ function createScreenshotDiff(page) {
|
|
|
6510
6604
|
if (options.path !== undefined && (!path3.isAbsolute(options.path) || path3.extname(options.path).toLowerCase() !== ".png")) {
|
|
6511
6605
|
throw new Error("screenshotDiff output path must be an absolute .png path");
|
|
6512
6606
|
}
|
|
6513
|
-
const baseline =
|
|
6607
|
+
const baseline = isString(options.baseline) ? await readBaseline(options.baseline) : options.baseline;
|
|
6514
6608
|
const before = decodePng(baseline);
|
|
6515
6609
|
const current = await page.screenshot({ type: "png", scale: "css", fullPage: options.fullPage ?? false });
|
|
6516
6610
|
const after = decodePng(current);
|
|
@@ -6577,9 +6671,6 @@ function decodePng(buffer) {
|
|
|
6577
6671
|
return PNG.sync.read(buffer);
|
|
6578
6672
|
}
|
|
6579
6673
|
|
|
6580
|
-
// src/snapshot.ts
|
|
6581
|
-
import { Effect as Effect8, Predicate as Predicate12 } from "effect";
|
|
6582
|
-
|
|
6583
6674
|
// src/snapshot/browser-capture.ts
|
|
6584
6675
|
var captureSnapshotInBrowser = (rootOrSettings, locatorSettings) => {
|
|
6585
6676
|
const settings = locatorSettings ?? rootOrSettings;
|
|
@@ -7598,7 +7689,6 @@ var browserCapture = new Function("rootOrSettings", "locatorSettings", `const __
|
|
|
7598
7689
|
return (${captureSnapshotInBrowser.toString()})(rootOrSettings, locatorSettings)`);
|
|
7599
7690
|
|
|
7600
7691
|
// src/snapshot/diff-and-format.ts
|
|
7601
|
-
import { Predicate as Predicate9 } from "effect";
|
|
7602
7692
|
function snapshotRefIdentity(entry) {
|
|
7603
7693
|
if (!entry.selector || !entry.role)
|
|
7604
7694
|
return;
|
|
@@ -7608,7 +7698,7 @@ function findSnapshotLines(lines, query, requestedContext) {
|
|
|
7608
7698
|
const context = Math.max(0, Math.min(10, Math.floor(requestedContext ?? 2)));
|
|
7609
7699
|
const matches = [];
|
|
7610
7700
|
for (const [index, line] of lines.entries()) {
|
|
7611
|
-
const matched =
|
|
7701
|
+
const matched = isString(query) ? line.toLowerCase().includes(query.toLowerCase()) : (() => {
|
|
7612
7702
|
query.lastIndex = 0;
|
|
7613
7703
|
return query.test(line);
|
|
7614
7704
|
})();
|
|
@@ -7616,7 +7706,7 @@ function findSnapshotLines(lines, query, requestedContext) {
|
|
|
7616
7706
|
matches.push(index);
|
|
7617
7707
|
}
|
|
7618
7708
|
if (matches.length === 0) {
|
|
7619
|
-
return `No snapshot lines matched ${
|
|
7709
|
+
return `No snapshot lines matched ${isString(query) ? JSON.stringify(query) : query.toString()}.`;
|
|
7620
7710
|
}
|
|
7621
7711
|
const included = new Set;
|
|
7622
7712
|
for (const index of matches) {
|
|
@@ -7699,13 +7789,12 @@ function snapshotRefAriaRole(role) {
|
|
|
7699
7789
|
}
|
|
7700
7790
|
|
|
7701
7791
|
// src/snapshot/types.ts
|
|
7702
|
-
import { Predicate as Predicate10 } from "effect";
|
|
7703
7792
|
var defaultAriaSnapshotTimeoutMs = 5000;
|
|
7704
7793
|
var defaultSnapshotTimeoutMs = 1e4;
|
|
7705
7794
|
var snapshotRetryDelayMs = 25;
|
|
7706
7795
|
function createAriaSnapshotHelper(page) {
|
|
7707
7796
|
return async (target, options) => {
|
|
7708
|
-
const locator = target === undefined ? page.locator("body") :
|
|
7797
|
+
const locator = target === undefined ? page.locator("body") : isString(target) ? page.locator(target) : target;
|
|
7709
7798
|
return await ariaSnapshotWithoutTextControlValues(locator, {
|
|
7710
7799
|
timeout: options?.timeout ?? defaultAriaSnapshotTimeoutMs
|
|
7711
7800
|
});
|
|
@@ -7719,9 +7808,8 @@ function invalidateSnapshotDocument(registry) {
|
|
|
7719
7808
|
}
|
|
7720
7809
|
|
|
7721
7810
|
// src/snapshot/input-fill.ts
|
|
7722
|
-
import { Predicate as Predicate11 } from "effect";
|
|
7723
7811
|
async function fillInput(options) {
|
|
7724
|
-
if (
|
|
7812
|
+
if (isString(options.target)) {
|
|
7725
7813
|
await fillInputs(options.page, [{ selector: options.target, value: options.value }]);
|
|
7726
7814
|
return;
|
|
7727
7815
|
}
|
|
@@ -7750,7 +7838,7 @@ async function fillInputs(page, fields) {
|
|
|
7750
7838
|
try {
|
|
7751
7839
|
const resolvedFields = [];
|
|
7752
7840
|
for (const field of fields) {
|
|
7753
|
-
if (
|
|
7841
|
+
if (isString(field.selector)) {
|
|
7754
7842
|
resolvedFields.push({ target: field.selector, label: `selector: ${field.selector}`, value: field.value });
|
|
7755
7843
|
continue;
|
|
7756
7844
|
}
|
|
@@ -8189,8 +8277,8 @@ function createSnapshotHelpers(page, registry) {
|
|
|
8189
8277
|
const refRoots = new WeakMap;
|
|
8190
8278
|
const snapshot = async (options = {}) => {
|
|
8191
8279
|
const within = options.within;
|
|
8192
|
-
const withinSelector =
|
|
8193
|
-
const withinLocator = within !== undefined && !
|
|
8280
|
+
const withinSelector = isString(within) ? within : undefined;
|
|
8281
|
+
const withinLocator = within !== undefined && !isString(within) ? within : undefined;
|
|
8194
8282
|
const refRoot = withinLocator ? refRoots.get(withinLocator) : undefined;
|
|
8195
8283
|
const locator = withinLocator && !refRoot ? withinLocator : undefined;
|
|
8196
8284
|
let locatorScope;
|
|
@@ -8256,7 +8344,7 @@ function createSnapshotHelpers(page, registry) {
|
|
|
8256
8344
|
while (true) {
|
|
8257
8345
|
navigatedDuringCapture = false;
|
|
8258
8346
|
try {
|
|
8259
|
-
result = await
|
|
8347
|
+
result = await runPromise(runPlaywrightOperation({
|
|
8260
8348
|
label: "Compact snapshot",
|
|
8261
8349
|
timeoutMs: Math.max(100, deadline - Date.now()),
|
|
8262
8350
|
run: () => page.evaluate(browserCapture, settings)
|
|
@@ -8399,7 +8487,7 @@ function createSnapshotHelpers(page, registry) {
|
|
|
8399
8487
|
`);
|
|
8400
8488
|
};
|
|
8401
8489
|
const ref = (id) => {
|
|
8402
|
-
if (!
|
|
8490
|
+
if (!isString(id) || !id.trim()) {
|
|
8403
8491
|
throw new Error(`Unknown snapshot ref: ${String(id)}; call snapshot() to get current refs`);
|
|
8404
8492
|
}
|
|
8405
8493
|
const normalized = id.startsWith("@") ? id.slice(1) : id;
|
|
@@ -8602,7 +8690,7 @@ function buildSandboxGlobals(deps) {
|
|
|
8602
8690
|
timeoutMs,
|
|
8603
8691
|
target: { targetId },
|
|
8604
8692
|
...options?.start ? { start: options.start } : {},
|
|
8605
|
-
...options?.start ? { cancelStart: () =>
|
|
8693
|
+
...options?.start ? { cancelStart: () => runPromise(deps.disconnectSettled()) } : {}
|
|
8606
8694
|
});
|
|
8607
8695
|
await finishHandoff({
|
|
8608
8696
|
outcome,
|
|
@@ -8708,10 +8796,10 @@ function buildSandboxGlobals(deps) {
|
|
|
8708
8796
|
handoff,
|
|
8709
8797
|
demonstrate,
|
|
8710
8798
|
network: {
|
|
8711
|
-
start: (options) =>
|
|
8799
|
+
start: (options) => runPromise(deps.networkCapture.start(page, options)),
|
|
8712
8800
|
status: () => deps.networkCapture.status(),
|
|
8713
|
-
stop: (options) =>
|
|
8714
|
-
cancel: () =>
|
|
8801
|
+
stop: (options) => runPromise(deps.networkCapture.stop(options)),
|
|
8802
|
+
cancel: () => runPromise(deps.networkCapture.cancel())
|
|
8715
8803
|
},
|
|
8716
8804
|
pageConsole: {
|
|
8717
8805
|
start: (target) => start(target ?? page),
|
|
@@ -8725,7 +8813,6 @@ function buildSandboxGlobals(deps) {
|
|
|
8725
8813
|
}
|
|
8726
8814
|
|
|
8727
8815
|
// src/execute/secrets-bridge.ts
|
|
8728
|
-
import { Effect as Effect10 } from "effect";
|
|
8729
8816
|
var isOpaqueDocumentUrl = (url) => /^(?:about|data|blob):/i.test(url);
|
|
8730
8817
|
var frameOrigin = (url) => {
|
|
8731
8818
|
try {
|
|
@@ -8799,7 +8886,7 @@ class ExecuteSecretsBridge {
|
|
|
8799
8886
|
async typeSecret(page, target, name, options = {}) {
|
|
8800
8887
|
if (typeof name !== "string" || !name)
|
|
8801
8888
|
throw new Error("secrets.type(target, name): name must be the name of a stored secret");
|
|
8802
|
-
const secret = await
|
|
8889
|
+
const secret = await runPromise(readSecret(name)).catch((error) => {
|
|
8803
8890
|
throw new Error(`secrets.type: ${error instanceof Error ? error.message : "could not read the secret"}`);
|
|
8804
8891
|
});
|
|
8805
8892
|
const secretRules = parseOriginPatterns(secret.allowedOrigins);
|
|
@@ -8857,22 +8944,19 @@ class ExecuteSecretsBridge {
|
|
|
8857
8944
|
|
|
8858
8945
|
// src/network-capture.ts
|
|
8859
8946
|
import path6 from "node:path";
|
|
8860
|
-
import { Effect as Effect12, Semaphore } from "effect";
|
|
8861
8947
|
|
|
8862
8948
|
// src/network-capture/har-redaction.ts
|
|
8863
8949
|
import path5 from "node:path";
|
|
8864
|
-
import { Effect as Effect11, Predicate as Predicate13 } from "effect";
|
|
8865
8950
|
|
|
8866
8951
|
// src/network-capture/types.ts
|
|
8867
|
-
import { Schema as Schema6 } from "effect";
|
|
8868
8952
|
var defaultNetworkCaptureMaxDurationMs = 30 * 60000;
|
|
8869
8953
|
var maxAllowedDurationMs = 24 * 60 * 60000;
|
|
8870
8954
|
|
|
8871
|
-
class NetworkCaptureError extends
|
|
8872
|
-
message:
|
|
8873
|
-
operation:
|
|
8874
|
-
reason:
|
|
8875
|
-
cause:
|
|
8955
|
+
class NetworkCaptureError extends TaggedError2()("NetworkCapture.Error", {
|
|
8956
|
+
message: String3,
|
|
8957
|
+
operation: String3,
|
|
8958
|
+
reason: Literals(["already-active", "inactive", "invalid-options", "finalize-failed", "persistence-failed"]),
|
|
8959
|
+
cause: optionalKey(Defect())
|
|
8876
8960
|
}) {
|
|
8877
8961
|
}
|
|
8878
8962
|
var maxAllowedBodyBytes = 1e7;
|
|
@@ -8914,7 +8998,7 @@ function validateLimit(name, value, maximum) {
|
|
|
8914
8998
|
|
|
8915
8999
|
// src/network-capture/har-redaction.ts
|
|
8916
9000
|
function finishCapture(active, options, secrets, profileOptions) {
|
|
8917
|
-
return
|
|
9001
|
+
return gen(function* () {
|
|
8918
9002
|
const existingProfile = secrets ? yield* readOptional(secrets, profileOptions) : undefined;
|
|
8919
9003
|
const collector = new SecretCollector(existingProfile?.slots ?? []);
|
|
8920
9004
|
for (const value of active.extraRedactions)
|
|
@@ -8952,7 +9036,7 @@ function extractTopLevelJsonKeys(body) {
|
|
|
8952
9036
|
return;
|
|
8953
9037
|
try {
|
|
8954
9038
|
const parsed = JSON.parse(body.text);
|
|
8955
|
-
if (
|
|
9039
|
+
if (isObject(parsed) && !Array.isArray(parsed)) {
|
|
8956
9040
|
const keys = Object.keys(parsed).slice(0, 15);
|
|
8957
9041
|
return keys.length > 0 ? keys : undefined;
|
|
8958
9042
|
}
|
|
@@ -9140,11 +9224,11 @@ function redactStructuredBody(body, collector) {
|
|
|
9140
9224
|
function redactKnownScalars(value, collector) {
|
|
9141
9225
|
if (Array.isArray(value))
|
|
9142
9226
|
return value.map((item) => redactKnownScalars(item, collector));
|
|
9143
|
-
if (
|
|
9227
|
+
if (isObject(value)) {
|
|
9144
9228
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactKnownScalars(item, collector)]));
|
|
9145
9229
|
}
|
|
9146
9230
|
const exact = collector.redactExactValue(value, 8);
|
|
9147
|
-
if (exact !== value || !
|
|
9231
|
+
if (exact !== value || !isString(value))
|
|
9148
9232
|
return exact;
|
|
9149
9233
|
return collector.redactKnownSubstrings(value, minimumKnownBodySubstringLength);
|
|
9150
9234
|
}
|
|
@@ -9193,7 +9277,7 @@ function toHarEntry(entry) {
|
|
|
9193
9277
|
};
|
|
9194
9278
|
}
|
|
9195
9279
|
function writeArtifact(outputPath, value) {
|
|
9196
|
-
return
|
|
9280
|
+
return tryPromise({
|
|
9197
9281
|
try: () => writeJsonFileAtomically(path5.resolve(outputPath), value),
|
|
9198
9282
|
catch: (cause) => new NetworkCaptureError({ message: `Could not write network capture: ${outputPath}`, operation: "write", reason: "persistence-failed", cause })
|
|
9199
9283
|
});
|
|
@@ -9294,14 +9378,14 @@ class Recorder {
|
|
|
9294
9378
|
ended;
|
|
9295
9379
|
page;
|
|
9296
9380
|
pageEpoch = 0;
|
|
9297
|
-
transition =
|
|
9381
|
+
transition = makeUnsafe(1);
|
|
9298
9382
|
extraRedactions = new Set;
|
|
9299
9383
|
constructor(recorderOptions = {}) {
|
|
9300
9384
|
this.recorderOptions = recorderOptions;
|
|
9301
9385
|
}
|
|
9302
9386
|
start(page, options = {}) {
|
|
9303
9387
|
const capture = this;
|
|
9304
|
-
return this.transition.withPermit(
|
|
9388
|
+
return this.transition.withPermit(try_({
|
|
9305
9389
|
try: () => {
|
|
9306
9390
|
validateOptions(options);
|
|
9307
9391
|
if (capture.active) {
|
|
@@ -9341,7 +9425,7 @@ class Recorder {
|
|
|
9341
9425
|
capture.expiry = setTimeout(() => {
|
|
9342
9426
|
if (capture.active !== active)
|
|
9343
9427
|
return;
|
|
9344
|
-
|
|
9428
|
+
runFork(capture.end(`it reached its maximum duration of ${formatDuration(maxDurationMs)} (maxDurationMs)`));
|
|
9345
9429
|
}, maxDurationMs);
|
|
9346
9430
|
capture.expiry.unref?.();
|
|
9347
9431
|
active.endsAt = new Date(Date.now() + maxDurationMs).toISOString();
|
|
@@ -9388,14 +9472,14 @@ class Recorder {
|
|
|
9388
9472
|
};
|
|
9389
9473
|
}
|
|
9390
9474
|
end(reason) {
|
|
9391
|
-
return this.cancel().pipe(
|
|
9475
|
+
return this.cancel().pipe(tap(({ cancelled }) => sync2(() => {
|
|
9392
9476
|
if (cancelled)
|
|
9393
9477
|
this.ended = reason;
|
|
9394
9478
|
})));
|
|
9395
9479
|
}
|
|
9396
9480
|
stop(options = {}) {
|
|
9397
9481
|
const capture = this;
|
|
9398
|
-
return this.transition.withPermit(
|
|
9482
|
+
return this.transition.withPermit(gen(function* () {
|
|
9399
9483
|
const active = capture.active;
|
|
9400
9484
|
if (!active) {
|
|
9401
9485
|
return yield* new NetworkCaptureError({
|
|
@@ -9407,7 +9491,7 @@ class Recorder {
|
|
|
9407
9491
|
const boundPage = capture.page;
|
|
9408
9492
|
active.stopping = true;
|
|
9409
9493
|
capture.unbindPage();
|
|
9410
|
-
const operation =
|
|
9494
|
+
const operation = gen(function* () {
|
|
9411
9495
|
for (const pending of [...active.pending.values()]) {
|
|
9412
9496
|
if (!pending.finalized) {
|
|
9413
9497
|
pending.failure ??= "Capture stopped before request completed";
|
|
@@ -9415,7 +9499,7 @@ class Recorder {
|
|
|
9415
9499
|
}
|
|
9416
9500
|
capture.scheduleFinalize(pending);
|
|
9417
9501
|
}
|
|
9418
|
-
const settled = yield*
|
|
9502
|
+
const settled = yield* promise(() => settleFinalizers(active, 5000));
|
|
9419
9503
|
if (!settled) {
|
|
9420
9504
|
active.droppedEntryCount += active.pending.size;
|
|
9421
9505
|
capture.discardFinalizers(active);
|
|
@@ -9423,7 +9507,7 @@ class Recorder {
|
|
|
9423
9507
|
const secrets = options.secrets;
|
|
9424
9508
|
const profileOptions = capture.recorderOptions.authProfileBaseDir ? { baseDir: capture.recorderOptions.authProfileBaseDir } : {};
|
|
9425
9509
|
const finish = finishCapture(active, options, secrets, profileOptions);
|
|
9426
|
-
const finished = yield* (secrets ? withLock(secrets, profileOptions, finish) : finish).pipe(
|
|
9510
|
+
const finished = yield* (secrets ? withLock(secrets, profileOptions, finish) : finish).pipe(mapError((cause) => cause instanceof NetworkCaptureError ? cause : new NetworkCaptureError({ message: cause.message, operation: "auth-profile", reason: "persistence-failed", cause })));
|
|
9427
9511
|
const { endsAt: _endsAt, ...finalStatus } = capture.status();
|
|
9428
9512
|
capture.active = undefined;
|
|
9429
9513
|
clearTimeout(capture.expiry);
|
|
@@ -9439,17 +9523,17 @@ class Recorder {
|
|
|
9439
9523
|
observedSecretRefs: finished.observedSecretRefs,
|
|
9440
9524
|
...finished.endpoints.length > 0 ? { endpoints: finished.endpoints } : {}
|
|
9441
9525
|
};
|
|
9442
|
-
}).pipe(
|
|
9526
|
+
}).pipe(tapError(() => sync2(() => {
|
|
9443
9527
|
active.stopping = false;
|
|
9444
9528
|
if (capture.active === active)
|
|
9445
9529
|
capture.bindPage(boundPage);
|
|
9446
9530
|
})));
|
|
9447
9531
|
return yield* operation;
|
|
9448
|
-
})).pipe(
|
|
9532
|
+
})).pipe(uninterruptible);
|
|
9449
9533
|
}
|
|
9450
9534
|
cancel() {
|
|
9451
9535
|
const capture = this;
|
|
9452
|
-
return this.transition.withPermit(
|
|
9536
|
+
return this.transition.withPermit(gen(function* () {
|
|
9453
9537
|
const active = capture.active;
|
|
9454
9538
|
if (!active)
|
|
9455
9539
|
return { cancelled: false };
|
|
@@ -9457,14 +9541,14 @@ class Recorder {
|
|
|
9457
9541
|
active.cancelled = true;
|
|
9458
9542
|
capture.unbindPage();
|
|
9459
9543
|
capture.discardFinalizers(active);
|
|
9460
|
-
yield*
|
|
9544
|
+
yield* promise(() => settleFinalizers(active, 5000));
|
|
9461
9545
|
if (capture.active === active) {
|
|
9462
9546
|
capture.active = undefined;
|
|
9463
9547
|
clearTimeout(capture.expiry);
|
|
9464
9548
|
}
|
|
9465
9549
|
capture.ended = undefined;
|
|
9466
9550
|
return { cancelled: true };
|
|
9467
|
-
})).pipe(
|
|
9551
|
+
})).pipe(uninterruptible);
|
|
9468
9552
|
}
|
|
9469
9553
|
bindPage(page) {
|
|
9470
9554
|
const active = this.active;
|
|
@@ -9764,7 +9848,7 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9764
9848
|
});
|
|
9765
9849
|
}
|
|
9766
9850
|
execute(code, options = {}) {
|
|
9767
|
-
return
|
|
9851
|
+
return tryPromise({
|
|
9768
9852
|
try: async () => {
|
|
9769
9853
|
if (this.realm?.isStopped) {
|
|
9770
9854
|
this.realm = this.createRealm();
|
|
@@ -9803,7 +9887,7 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9803
9887
|
}
|
|
9804
9888
|
return cause instanceof Error ? cause : new Error("execute sandbox code", { cause });
|
|
9805
9889
|
}
|
|
9806
|
-
}).pipe(
|
|
9890
|
+
}).pipe(uninterruptible, tapError(() => promise(() => this.networkCapture.settleForOutput())), match3({
|
|
9807
9891
|
onFailure: (error) => {
|
|
9808
9892
|
const logSummary = error instanceof ExecuteCodeError ? error.logSummary : emptyExecuteLogSummary();
|
|
9809
9893
|
const aftermath = error instanceof ExecuteCodeError ? error.aftermath : undefined;
|
|
@@ -9831,7 +9915,7 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9831
9915
|
}
|
|
9832
9916
|
authenticatedJson(request) {
|
|
9833
9917
|
const sandbox = this;
|
|
9834
|
-
return
|
|
9918
|
+
return gen(function* () {
|
|
9835
9919
|
if (request.sensitive === true && sandbox.networkCapture.status().active) {
|
|
9836
9920
|
return AuthenticatedJsonOutcome.cases.SensitiveCaptureActive.make({});
|
|
9837
9921
|
}
|
|
@@ -9840,14 +9924,14 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9840
9924
|
const isAuthFailure = firstOutcome._tag === "OriginMismatch" || firstOutcome._tag === "HttpError" && (firstOutcome.status === 401 || firstOutcome.status === 403) || firstOutcome._tag !== "Success" && /\/(?:login|signin|sign-in|auth)\b/i.test(safePageUrl(page) ?? "");
|
|
9841
9925
|
if (request.handoffOnAuthFailure === true && sandbox.options.requestHandoff !== undefined && isAuthFailure) {
|
|
9842
9926
|
const requestHandoff = sandbox.options.requestHandoff;
|
|
9843
|
-
const targetId = yield*
|
|
9927
|
+
const targetId = yield* tryPromise(() => pageTargetId(page));
|
|
9844
9928
|
const message = request.handoffMessage?.trim() || `Sign in to ${request.origin}, then click Continue`;
|
|
9845
|
-
const outcome = yield*
|
|
9929
|
+
const outcome = yield* tryPromise(() => requestHandoff({
|
|
9846
9930
|
message,
|
|
9847
9931
|
timeoutMs: defaultHandoffTimeoutMs,
|
|
9848
9932
|
target: { targetId }
|
|
9849
9933
|
}));
|
|
9850
|
-
yield*
|
|
9934
|
+
yield* tryPromise(() => finishHandoff({
|
|
9851
9935
|
outcome,
|
|
9852
9936
|
message,
|
|
9853
9937
|
timeoutMs: defaultHandoffTimeoutMs,
|
|
@@ -9863,7 +9947,7 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9863
9947
|
});
|
|
9864
9948
|
}
|
|
9865
9949
|
return firstOutcome;
|
|
9866
|
-
}).pipe(
|
|
9950
|
+
}).pipe(uninterruptible);
|
|
9867
9951
|
}
|
|
9868
9952
|
async withChallenge(page, aftermath, failed) {
|
|
9869
9953
|
if (!failed && aftermath.navigations.length === 0 && aftermath.startUrl === aftermath.endUrl)
|
|
@@ -9905,7 +9989,7 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9905
9989
|
}
|
|
9906
9990
|
disconnectSettled() {
|
|
9907
9991
|
const sandbox = this;
|
|
9908
|
-
return
|
|
9992
|
+
return gen(function* () {
|
|
9909
9993
|
sandbox.realm?.dispose();
|
|
9910
9994
|
sandbox.realm = sandbox.allowNode ? undefined : sandbox.createRealm();
|
|
9911
9995
|
sandbox.state = sandbox.realm?.state ?? {};
|
|
@@ -9921,13 +10005,13 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9921
10005
|
yield* runSettledPlaywrightOperation({
|
|
9922
10006
|
label: "Disconnect sandbox browser after handoff cancellation",
|
|
9923
10007
|
run: () => browser.close()
|
|
9924
|
-
}).pipe(
|
|
10008
|
+
}).pipe(ignore);
|
|
9925
10009
|
}
|
|
9926
10010
|
});
|
|
9927
10011
|
}
|
|
9928
10012
|
closeSettled(options = {}) {
|
|
9929
10013
|
const sandbox = this;
|
|
9930
|
-
return
|
|
10014
|
+
return gen(function* () {
|
|
9931
10015
|
sandbox.realm?.dispose();
|
|
9932
10016
|
const page = sandbox.page;
|
|
9933
10017
|
const browser = sandbox.browser;
|
|
@@ -9943,19 +10027,19 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9943
10027
|
yield* runSettledPlaywrightOperation({
|
|
9944
10028
|
label: "Close sandbox page after adoption",
|
|
9945
10029
|
run: () => page.close()
|
|
9946
|
-
}).pipe(
|
|
10030
|
+
}).pipe(ignore);
|
|
9947
10031
|
}
|
|
9948
10032
|
if (browser) {
|
|
9949
10033
|
yield* runSettledPlaywrightOperation({
|
|
9950
10034
|
label: "Close sandbox browser connection after adoption",
|
|
9951
10035
|
run: () => browser.close()
|
|
9952
|
-
}).pipe(
|
|
10036
|
+
}).pipe(ignore);
|
|
9953
10037
|
}
|
|
9954
10038
|
});
|
|
9955
10039
|
}
|
|
9956
10040
|
adoptPage(targetId) {
|
|
9957
10041
|
const sandbox = this;
|
|
9958
|
-
return
|
|
10042
|
+
return gen(function* () {
|
|
9959
10043
|
const currentPage = sandbox.page;
|
|
9960
10044
|
const sameTarget = sandbox.defaultPageTargetId === targetId;
|
|
9961
10045
|
yield* sandbox.networkCapture.cancel();
|
|
@@ -9971,18 +10055,18 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
9971
10055
|
});
|
|
9972
10056
|
}
|
|
9973
10057
|
sandbox.unbindDefaultPage(targetId);
|
|
9974
|
-
}).pipe(
|
|
10058
|
+
}).pipe(uninterruptible);
|
|
9975
10059
|
}
|
|
9976
10060
|
async connectContext() {
|
|
9977
10061
|
if (!this.browser?.isConnected()) {
|
|
9978
10062
|
const hadBrowser = this.browser !== undefined;
|
|
9979
10063
|
const staleBrowser = this.browser;
|
|
9980
10064
|
if (staleBrowser) {
|
|
9981
|
-
await
|
|
10065
|
+
await runPromise(runPlaywrightOperation({
|
|
9982
10066
|
label: "Close stale browser connection before reconnecting",
|
|
9983
10067
|
timeoutMs: playwrightCloseTimeoutMs,
|
|
9984
10068
|
run: () => staleBrowser.close()
|
|
9985
|
-
}).pipe(
|
|
10069
|
+
}).pipe(ignore));
|
|
9986
10070
|
}
|
|
9987
10071
|
const adoptedDefaultTarget = this.defaultPageTargetId !== undefined && !this.ownsPage;
|
|
9988
10072
|
try {
|
|
@@ -10029,11 +10113,11 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
10029
10113
|
rebind: "repaired"
|
|
10030
10114
|
});
|
|
10031
10115
|
if (browser) {
|
|
10032
|
-
await
|
|
10116
|
+
await runPromise(runPlaywrightOperation({
|
|
10033
10117
|
label: "Close browser connection to repair the session page",
|
|
10034
10118
|
timeoutMs: playwrightCloseTimeoutMs,
|
|
10035
10119
|
run: () => browser.close()
|
|
10036
|
-
}).pipe(
|
|
10120
|
+
}).pipe(ignore));
|
|
10037
10121
|
}
|
|
10038
10122
|
}
|
|
10039
10123
|
async acquireSessionPage(targetSelection) {
|
|
@@ -10062,7 +10146,7 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
10062
10146
|
return { browser, context, page };
|
|
10063
10147
|
}
|
|
10064
10148
|
ensureSessionPage(label) {
|
|
10065
|
-
return
|
|
10149
|
+
return tryPromise({
|
|
10066
10150
|
try: async () => (await this.acquireSessionPage()).page,
|
|
10067
10151
|
catch: (cause) => cause instanceof Error ? cause : new Error(label, { cause })
|
|
10068
10152
|
});
|
|
@@ -10106,10 +10190,10 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
10106
10190
|
}
|
|
10107
10191
|
networkStart(options = {}) {
|
|
10108
10192
|
const sandbox = this;
|
|
10109
|
-
return
|
|
10193
|
+
return gen(function* () {
|
|
10110
10194
|
const page = yield* sandbox.ensureSessionPage("Set up page for network capture");
|
|
10111
10195
|
return yield* sandbox.networkCapture.start(page, options);
|
|
10112
|
-
}).pipe(
|
|
10196
|
+
}).pipe(uninterruptible);
|
|
10113
10197
|
}
|
|
10114
10198
|
networkStatus() {
|
|
10115
10199
|
return this.networkCapture.status();
|
|
@@ -10125,24 +10209,24 @@ class ExecuteSandbox extends DefaultPageHost {
|
|
|
10125
10209
|
}
|
|
10126
10210
|
authRefresh(options) {
|
|
10127
10211
|
const sandbox = this;
|
|
10128
|
-
return
|
|
10212
|
+
return gen(function* () {
|
|
10129
10213
|
const page = yield* sandbox.ensureSessionPage("Set up page for auth refresh");
|
|
10130
|
-
yield* read(options.name).pipe(
|
|
10214
|
+
yield* read(options.name).pipe(asVoid2);
|
|
10131
10215
|
yield* sandbox.networkCapture.start(page, {
|
|
10132
10216
|
...options.urlFilter ? { urlFilter: options.urlFilter } : {}
|
|
10133
10217
|
});
|
|
10134
|
-
return yield*
|
|
10135
|
-
yield*
|
|
10218
|
+
return yield* gen(function* () {
|
|
10219
|
+
yield* tryPromise({
|
|
10136
10220
|
try: () => page.reload({ waitUntil: "domcontentloaded", timeout: options.timeoutMs ?? 30000 }),
|
|
10137
10221
|
catch: (cause) => cause instanceof Error ? cause : new Error("Refresh auth profile", { cause })
|
|
10138
10222
|
});
|
|
10139
|
-
yield*
|
|
10223
|
+
yield* tryPromise({
|
|
10140
10224
|
try: () => page.waitForLoadState("networkidle", { timeout: Math.min(options.timeoutMs ?? 30000, 5000) }).catch(() => {}),
|
|
10141
10225
|
catch: (cause) => cause instanceof Error ? cause : new Error("Wait for auth refresh network", { cause })
|
|
10142
10226
|
});
|
|
10143
10227
|
return yield* sandbox.networkCapture.stop({ secrets: options.name, requireObservedSecrets: true });
|
|
10144
|
-
}).pipe(
|
|
10145
|
-
}).pipe(
|
|
10228
|
+
}).pipe(ensuring(sandbox.networkCapture.cancel()));
|
|
10229
|
+
}).pipe(uninterruptible);
|
|
10146
10230
|
}
|
|
10147
10231
|
warnSyntheticInput() {
|
|
10148
10232
|
this.addWarningOnce(syntheticInputWarning);
|
|
@@ -10273,11 +10357,9 @@ function mjpegMatroskaFrame(timestampMs, durationMs, frameLength) {
|
|
|
10273
10357
|
|
|
10274
10358
|
// src/recording-relay/screencast.ts
|
|
10275
10359
|
import { performance as performance2 } from "node:perf_hooks";
|
|
10276
|
-
import { Predicate as Predicate15 } from "effect";
|
|
10277
10360
|
|
|
10278
10361
|
// src/recording-relay/types.ts
|
|
10279
10362
|
import path8 from "node:path";
|
|
10280
|
-
import { Option, Predicate as Predicate14, Schema as Schema7 } from "effect";
|
|
10281
10363
|
var defaultMaxDurationMs = 15 * 60 * 1000;
|
|
10282
10364
|
var defaultCdpFrameRate = 60;
|
|
10283
10365
|
var maxRecordingFrameRate = 60;
|
|
@@ -10305,39 +10387,39 @@ function recordingStartParams(options) {
|
|
|
10305
10387
|
...options.audioBitsPerSecond === undefined ? {} : { audioBitsPerSecond: options.audioBitsPerSecond }
|
|
10306
10388
|
};
|
|
10307
10389
|
}
|
|
10308
|
-
var ExtensionStartResultSchema =
|
|
10309
|
-
|
|
10310
|
-
success:
|
|
10311
|
-
tabId:
|
|
10312
|
-
startedAt:
|
|
10313
|
-
mimeType:
|
|
10390
|
+
var ExtensionStartResultSchema = Union([
|
|
10391
|
+
Struct({
|
|
10392
|
+
success: Literal(true),
|
|
10393
|
+
tabId: Number2,
|
|
10394
|
+
startedAt: Number2,
|
|
10395
|
+
mimeType: optionalKey(String3)
|
|
10314
10396
|
}),
|
|
10315
|
-
|
|
10316
|
-
success:
|
|
10317
|
-
error:
|
|
10397
|
+
Struct({
|
|
10398
|
+
success: Literal(false),
|
|
10399
|
+
error: String3
|
|
10318
10400
|
})
|
|
10319
10401
|
]);
|
|
10320
|
-
var decodeExtensionStartResult =
|
|
10321
|
-
var ExtensionStopResultSchema =
|
|
10322
|
-
|
|
10323
|
-
success:
|
|
10324
|
-
tabId:
|
|
10325
|
-
duration:
|
|
10402
|
+
var decodeExtensionStartResult = decodeUnknownOption(ExtensionStartResultSchema);
|
|
10403
|
+
var ExtensionStopResultSchema = Union([
|
|
10404
|
+
Struct({
|
|
10405
|
+
success: Literal(true),
|
|
10406
|
+
tabId: Number2,
|
|
10407
|
+
duration: Number2
|
|
10326
10408
|
}),
|
|
10327
|
-
|
|
10328
|
-
success:
|
|
10329
|
-
error:
|
|
10409
|
+
Struct({
|
|
10410
|
+
success: Literal(false),
|
|
10411
|
+
error: String3
|
|
10330
10412
|
})
|
|
10331
10413
|
]);
|
|
10332
|
-
var decodeExtensionStopResult =
|
|
10414
|
+
var decodeExtensionStopResult = decodeUnknownOption(ExtensionStopResultSchema);
|
|
10333
10415
|
function parseExtensionStartResult(value) {
|
|
10334
|
-
return
|
|
10416
|
+
return getOrElse(decodeExtensionStartResult(value), () => ({
|
|
10335
10417
|
success: false,
|
|
10336
10418
|
error: "Invalid recording.start response from extension"
|
|
10337
10419
|
}));
|
|
10338
10420
|
}
|
|
10339
10421
|
function parseExtensionStopResult(value) {
|
|
10340
|
-
return
|
|
10422
|
+
return getOrElse(decodeExtensionStopResult(value), () => ({
|
|
10341
10423
|
success: false,
|
|
10342
10424
|
error: "Invalid recording.stop response from extension"
|
|
10343
10425
|
}));
|
|
@@ -10345,14 +10427,14 @@ function parseExtensionStopResult(value) {
|
|
|
10345
10427
|
function parseExtensionStatusResult(value) {
|
|
10346
10428
|
return {
|
|
10347
10429
|
isRecording: value.isRecording === true,
|
|
10348
|
-
...
|
|
10349
|
-
...
|
|
10430
|
+
...isNumber(value.tabId) ? { tabId: value.tabId } : {},
|
|
10431
|
+
...isNumber(value.startedAt) ? { startedAt: value.startedAt } : {}
|
|
10350
10432
|
};
|
|
10351
10433
|
}
|
|
10352
10434
|
function parseCancelResult(value) {
|
|
10353
10435
|
return {
|
|
10354
10436
|
success: value.success === true,
|
|
10355
|
-
...
|
|
10437
|
+
...isString(value.error) ? { error: value.error } : {}
|
|
10356
10438
|
};
|
|
10357
10439
|
}
|
|
10358
10440
|
|
|
@@ -10410,7 +10492,7 @@ function startScreenshotFallback(options) {
|
|
|
10410
10492
|
const running = () => !stopped && options.active() && !options.hasScreencastFrames();
|
|
10411
10493
|
const intervalMs = Math.round(1000 / Math.max(1, Math.min(options.frameRate, maxFallbackFrameRate)));
|
|
10412
10494
|
(async () => {
|
|
10413
|
-
await
|
|
10495
|
+
await sleep2(screencastFallbackGraceMs);
|
|
10414
10496
|
while (running()) {
|
|
10415
10497
|
const started = performance2.now();
|
|
10416
10498
|
const screenshot = await options.sendDebuggerCommand({
|
|
@@ -10423,20 +10505,20 @@ function startScreenshotFallback(options) {
|
|
|
10423
10505
|
const data = screenshot ? getString2(screenshot, "data") : undefined;
|
|
10424
10506
|
if (data !== undefined && running())
|
|
10425
10507
|
options.onFrame({ data, metadata: {} });
|
|
10426
|
-
await
|
|
10508
|
+
await sleep2(Math.max(0, intervalMs - (performance2.now() - started)));
|
|
10427
10509
|
}
|
|
10428
10510
|
})();
|
|
10429
10511
|
return () => {
|
|
10430
10512
|
stopped = true;
|
|
10431
10513
|
};
|
|
10432
10514
|
}
|
|
10433
|
-
function
|
|
10515
|
+
function sleep2(ms) {
|
|
10434
10516
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10435
10517
|
}
|
|
10436
10518
|
function cdpRecordingSize(metrics) {
|
|
10437
10519
|
const viewport = getObject2(metrics.cssVisualViewport) ?? getObject2(metrics.visualViewport);
|
|
10438
|
-
const viewportWidth =
|
|
10439
|
-
const viewportHeight =
|
|
10520
|
+
const viewportWidth = isNumber(viewport?.clientWidth) ? viewport.clientWidth : fallbackCdpWidth;
|
|
10521
|
+
const viewportHeight = isNumber(viewport?.clientHeight) ? viewport.clientHeight : fallbackCdpHeight;
|
|
10440
10522
|
return {
|
|
10441
10523
|
width: Math.max(2, Math.floor(viewportWidth) & ~1),
|
|
10442
10524
|
height: Math.max(2, Math.floor(viewportHeight) & ~1)
|
|
@@ -11104,25 +11186,15 @@ function resolveExactHandoffTarget(options) {
|
|
|
11104
11186
|
return target;
|
|
11105
11187
|
}
|
|
11106
11188
|
|
|
11107
|
-
// src/http-api.ts
|
|
11108
|
-
import { Effect as Effect23 } from "effect";
|
|
11109
|
-
|
|
11110
|
-
// src/http-api/errors.ts
|
|
11111
|
-
import { Effect as Effect20, Match as Match5, Predicate as Predicate16, Schema as Schema11 } from "effect";
|
|
11112
|
-
|
|
11113
|
-
// src/relay/shutdown.ts
|
|
11114
|
-
import { Data as Data2, Effect as Effect14, Exit, Latch, Match as Match4, Schema as Schema9 } from "effect";
|
|
11115
|
-
|
|
11116
11189
|
// src/relay/lifecycle-log.ts
|
|
11117
11190
|
import fs10 from "node:fs";
|
|
11118
11191
|
import path10 from "node:path";
|
|
11119
|
-
import { Schema as Schema8 } from "effect";
|
|
11120
11192
|
var requestFields = {
|
|
11121
11193
|
instanceId: RelayShutdownRequest.fields.instanceId,
|
|
11122
11194
|
requestId: RelayShutdownRequest.fields.requestId,
|
|
11123
11195
|
client: RelayShutdownRequest.fields.client
|
|
11124
11196
|
};
|
|
11125
|
-
var RelayLifecycleEvent =
|
|
11197
|
+
var RelayLifecycleEvent = TaggedUnion({
|
|
11126
11198
|
Requested: requestFields,
|
|
11127
11199
|
Cancelled: requestFields,
|
|
11128
11200
|
Stopping: requestFields,
|
|
@@ -11130,13 +11202,13 @@ var RelayLifecycleEvent = Schema8.TaggedUnion({
|
|
|
11130
11202
|
Ready: {
|
|
11131
11203
|
instanceId: RelayShutdownRequest.fields.instanceId,
|
|
11132
11204
|
buildId: RelayShutdownRequest.fields.client.fields.buildId,
|
|
11133
|
-
restartRequestId:
|
|
11205
|
+
restartRequestId: optionalKey(RelayShutdownRequest.fields.requestId)
|
|
11134
11206
|
}
|
|
11135
11207
|
});
|
|
11136
11208
|
function appendRelayLifecycleEvent(filePath, event) {
|
|
11137
11209
|
const directory = path10.dirname(filePath);
|
|
11138
11210
|
ensurePrivateDirectorySync(directory);
|
|
11139
|
-
const entry = `${JSON.stringify({ ...
|
|
11211
|
+
const entry = `${JSON.stringify({ ...encodeSync(RelayLifecycleEvent)(event), timestamp: new Date().toISOString() })}
|
|
11140
11212
|
`;
|
|
11141
11213
|
const file = fs10.openSync(filePath, "a+", 384);
|
|
11142
11214
|
try {
|
|
@@ -11162,11 +11234,11 @@ function appendRelayLifecycleEvent(filePath, event) {
|
|
|
11162
11234
|
}
|
|
11163
11235
|
|
|
11164
11236
|
// src/relay/shutdown.ts
|
|
11165
|
-
var State =
|
|
11237
|
+
var State = taggedEnum();
|
|
11166
11238
|
|
|
11167
|
-
class RelayShutdownError extends
|
|
11168
|
-
reason:
|
|
11169
|
-
message:
|
|
11239
|
+
class RelayShutdownError extends TaggedError2()("RelayShutdown.Error", {
|
|
11240
|
+
reason: Literals(["busy", "instance-changed", "not-managed"]),
|
|
11241
|
+
message: String3
|
|
11170
11242
|
}) {
|
|
11171
11243
|
}
|
|
11172
11244
|
|
|
@@ -11174,7 +11246,7 @@ class RelayShutdown {
|
|
|
11174
11246
|
options;
|
|
11175
11247
|
state = State.Running();
|
|
11176
11248
|
requests = 0;
|
|
11177
|
-
idle =
|
|
11249
|
+
idle = makeUnsafe5(true);
|
|
11178
11250
|
constructor(options) {
|
|
11179
11251
|
this.options = options;
|
|
11180
11252
|
}
|
|
@@ -11186,29 +11258,29 @@ class RelayShutdown {
|
|
|
11186
11258
|
}
|
|
11187
11259
|
drainAndSettle() {
|
|
11188
11260
|
const control = this;
|
|
11189
|
-
return
|
|
11190
|
-
yield*
|
|
11261
|
+
return gen(function* () {
|
|
11262
|
+
yield* all([control.options.drain, control.idle.await], { concurrency: "unbounded" });
|
|
11191
11263
|
yield* control.idle.await;
|
|
11192
11264
|
yield* control.options.settle;
|
|
11193
11265
|
});
|
|
11194
11266
|
}
|
|
11195
11267
|
close() {
|
|
11196
11268
|
const control = this;
|
|
11197
|
-
return
|
|
11269
|
+
return gen(function* () {
|
|
11198
11270
|
control.state = State.Closing();
|
|
11199
11271
|
yield* control.drainAndSettle();
|
|
11200
11272
|
});
|
|
11201
11273
|
}
|
|
11202
11274
|
track(effect) {
|
|
11203
11275
|
const control = this;
|
|
11204
|
-
return
|
|
11276
|
+
return acquireUseRelease(suspend2(() => {
|
|
11205
11277
|
if (!control.accepting)
|
|
11206
|
-
return
|
|
11207
|
-
return
|
|
11208
|
-
}), () => effect, (release) =>
|
|
11278
|
+
return fail4(control.busyError());
|
|
11279
|
+
return sync2(() => control.retain());
|
|
11280
|
+
}), () => effect, (release) => sync2(release));
|
|
11209
11281
|
}
|
|
11210
11282
|
trackTransport(effect) {
|
|
11211
|
-
return
|
|
11283
|
+
return acquireUseRelease(sync2(() => this.retain()), () => effect, (release) => sync2(release));
|
|
11212
11284
|
}
|
|
11213
11285
|
retain() {
|
|
11214
11286
|
this.requests += 1;
|
|
@@ -11220,7 +11292,7 @@ class RelayShutdown {
|
|
|
11220
11292
|
}
|
|
11221
11293
|
request(request) {
|
|
11222
11294
|
const control = this;
|
|
11223
|
-
return
|
|
11295
|
+
return uninterruptibleMask2((restore) => gen(function* () {
|
|
11224
11296
|
if (request.instanceId !== control.options.instanceId) {
|
|
11225
11297
|
return yield* new RelayShutdownError({ reason: "instance-changed", message: "Relay shutdown does not match the active managed instance" });
|
|
11226
11298
|
}
|
|
@@ -11234,9 +11306,9 @@ class RelayShutdown {
|
|
|
11234
11306
|
}
|
|
11235
11307
|
control.state = State.Draining();
|
|
11236
11308
|
const fields = { instanceId: request.instanceId, requestId: request.requestId, client: request.client };
|
|
11237
|
-
yield*
|
|
11309
|
+
yield* gen(function* () {
|
|
11238
11310
|
yield* control.options.audit(RelayLifecycleEvent.cases.Requested.make(fields));
|
|
11239
|
-
const drain =
|
|
11311
|
+
const drain = gen(function* () {
|
|
11240
11312
|
const busy = control.options.busy();
|
|
11241
11313
|
if (busy)
|
|
11242
11314
|
return yield* control.busyError(busy);
|
|
@@ -11245,7 +11317,7 @@ class RelayShutdown {
|
|
|
11245
11317
|
if (remaining)
|
|
11246
11318
|
return yield* control.busyError(remaining);
|
|
11247
11319
|
});
|
|
11248
|
-
yield* restore(drain.pipe(
|
|
11320
|
+
yield* restore(drain.pipe(timeoutOrElse({
|
|
11249
11321
|
duration: control.options.timeoutMs ?? 1e4,
|
|
11250
11322
|
orElse: () => control.busyError("timeout")
|
|
11251
11323
|
})));
|
|
@@ -11253,7 +11325,7 @@ class RelayShutdown {
|
|
|
11253
11325
|
return yield* control.busyError();
|
|
11254
11326
|
}
|
|
11255
11327
|
yield* control.options.audit(RelayLifecycleEvent.cases.Stopping.make(fields));
|
|
11256
|
-
yield* restore(
|
|
11328
|
+
yield* restore(void_3);
|
|
11257
11329
|
if (!State.$is("Draining")(control.state)) {
|
|
11258
11330
|
return yield* control.busyError();
|
|
11259
11331
|
}
|
|
@@ -11265,12 +11337,12 @@ class RelayShutdown {
|
|
|
11265
11337
|
return yield* control.busyError(busy);
|
|
11266
11338
|
control.state = State.Stopping();
|
|
11267
11339
|
control.options.stop();
|
|
11268
|
-
}).pipe(
|
|
11269
|
-
if (
|
|
11270
|
-
return
|
|
11340
|
+
}).pipe(onExit((exit) => {
|
|
11341
|
+
if (isSuccess2(exit) || !State.$is("Draining")(control.state))
|
|
11342
|
+
return void_3;
|
|
11271
11343
|
control.options.resume();
|
|
11272
11344
|
control.state = State.Running();
|
|
11273
|
-
return control.options.audit(RelayLifecycleEvent.cases.Cancelled.make(fields)).pipe(
|
|
11345
|
+
return control.options.audit(RelayLifecycleEvent.cases.Cancelled.make(fields)).pipe(catch_(() => sync2(() => {
|
|
11274
11346
|
console.error(`Relay cancellation audit failed for request ${request.requestId}`);
|
|
11275
11347
|
})));
|
|
11276
11348
|
}));
|
|
@@ -11280,19 +11352,12 @@ class RelayShutdown {
|
|
|
11280
11352
|
const reason = typeof busy === "object" ? busy.reason : busy;
|
|
11281
11353
|
const detail = typeof busy === "object" ? busy.detail : undefined;
|
|
11282
11354
|
const force = cliCommand("relay restart --force");
|
|
11283
|
-
const message =
|
|
11355
|
+
const message = value2(reason).pipe(when2("raw-clients", () => `Relay has ${detail ?? "raw CDP clients attached"}; disconnect them, or run \`${force}\` to disconnect them and restart`), when2("recordings", () => `Relay has ${detail ?? "active recordings or network captures"}; stop them, or run \`${force}\` to end them (discarding what they captured) and restart`), when2("timeout", () => "Relay restart timed out waiting for accepted work; the relay is still running"), when2("changed", () => "Relay changed while preparing restart; it is still running, retry the restart"), orElse(() => "Relay is draining for an explicit restart; retry after it completes"));
|
|
11284
11356
|
return new RelayShutdownError({ reason: "busy", message });
|
|
11285
11357
|
}
|
|
11286
11358
|
}
|
|
11287
11359
|
|
|
11288
|
-
// src/session-manager.ts
|
|
11289
|
-
import { Deferred as Deferred6, Effect as Effect19, Option as Option3, Semaphore as Semaphore3 } from "effect";
|
|
11290
|
-
|
|
11291
|
-
// src/session-manager/admission.ts
|
|
11292
|
-
import { Deferred as Deferred3, Effect as Effect16, Exit as Exit2, Latch as Latch2 } from "effect";
|
|
11293
|
-
|
|
11294
11360
|
// src/session-manager/types.ts
|
|
11295
|
-
import { Effect as Effect15, Schema as Schema10 } from "effect";
|
|
11296
11361
|
var defaultExecuteTimeoutMs = 60000;
|
|
11297
11362
|
|
|
11298
11363
|
class ExecutionDeadline {
|
|
@@ -11345,9 +11410,9 @@ class ExecutionDeadline {
|
|
|
11345
11410
|
}
|
|
11346
11411
|
}
|
|
11347
11412
|
|
|
11348
|
-
class SessionError extends
|
|
11349
|
-
message:
|
|
11350
|
-
reason:
|
|
11413
|
+
class SessionError extends TaggedError2()("OpenCodeBrowserSessions.SessionError", {
|
|
11414
|
+
message: String3,
|
|
11415
|
+
reason: Literals([
|
|
11351
11416
|
"already-exists",
|
|
11352
11417
|
"browser-unavailable",
|
|
11353
11418
|
"inactive",
|
|
@@ -11357,11 +11422,11 @@ class SessionError extends Schema10.TaggedError()("OpenCodeBrowserSessions.Sessi
|
|
|
11357
11422
|
"target-owned",
|
|
11358
11423
|
"timeout"
|
|
11359
11424
|
]),
|
|
11360
|
-
sessionId:
|
|
11425
|
+
sessionId: optionalKey(String3)
|
|
11361
11426
|
}) {
|
|
11362
11427
|
}
|
|
11363
11428
|
var sessionError = (reason, message, sessionId) => new SessionError({ message, reason, ...sessionId ? { sessionId } : {} });
|
|
11364
|
-
var trySession = (work) =>
|
|
11429
|
+
var trySession = (work) => try_({ try: work, catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)) });
|
|
11365
11430
|
var adoptionTipForUrl = (url) => {
|
|
11366
11431
|
const selector = targetUrlHintSelector(url);
|
|
11367
11432
|
return `Tip: an attached tab is open (${url}). Use session_adopt with targetUrl '${selector}' (CLI: ${cliCommand(`session adopt --target-url '${selector}'`)}) to drive it instead of this new tab.`;
|
|
@@ -11434,7 +11499,7 @@ class SessionAdmission {
|
|
|
11434
11499
|
pendingWork = 0;
|
|
11435
11500
|
pendingSessionWork = new Map;
|
|
11436
11501
|
identityChanges = new Map;
|
|
11437
|
-
idle =
|
|
11502
|
+
idle = makeUnsafe5(true);
|
|
11438
11503
|
constructor(sessions, hooks) {
|
|
11439
11504
|
this.sessions = sessions;
|
|
11440
11505
|
this.hooks = hooks;
|
|
@@ -11453,22 +11518,22 @@ class SessionAdmission {
|
|
|
11453
11518
|
}
|
|
11454
11519
|
}
|
|
11455
11520
|
awaitStableIdentity(id) {
|
|
11456
|
-
return
|
|
11521
|
+
return suspend2(() => {
|
|
11457
11522
|
const pending = id === undefined ? undefined : this.identityChanges.get(id);
|
|
11458
|
-
return pending ?
|
|
11523
|
+
return pending ? _await(pending).pipe(andThen(this.awaitStableIdentity(id))) : void_3;
|
|
11459
11524
|
});
|
|
11460
11525
|
}
|
|
11461
11526
|
withIdentityChange(id, effect) {
|
|
11462
|
-
return
|
|
11527
|
+
return suspend2(() => {
|
|
11463
11528
|
const pending = this.identityChanges.get(id);
|
|
11464
11529
|
if (pending)
|
|
11465
|
-
return
|
|
11466
|
-
const settled =
|
|
11530
|
+
return _await(pending).pipe(andThen(this.withIdentityChange(id, effect)));
|
|
11531
|
+
const settled = makeUnsafe3();
|
|
11467
11532
|
this.identityChanges.set(id, settled);
|
|
11468
|
-
return effect.pipe(
|
|
11533
|
+
return effect.pipe(onExit((exit) => suspend2(() => {
|
|
11469
11534
|
if (this.identityChanges.get(id) === settled)
|
|
11470
11535
|
this.identityChanges.delete(id);
|
|
11471
|
-
return
|
|
11536
|
+
return done(settled, asVoid(exit));
|
|
11472
11537
|
})));
|
|
11473
11538
|
});
|
|
11474
11539
|
}
|
|
@@ -11491,18 +11556,18 @@ class SessionAdmission {
|
|
|
11491
11556
|
};
|
|
11492
11557
|
}
|
|
11493
11558
|
withAdmission(sessionId, effect) {
|
|
11494
|
-
return
|
|
11559
|
+
return acquireUseRelease(try_({
|
|
11495
11560
|
try: () => {
|
|
11496
11561
|
this.assertAdmission();
|
|
11497
11562
|
return this.retainWork(sessionId);
|
|
11498
11563
|
},
|
|
11499
11564
|
catch: (cause) => cause instanceof Error ? cause : new Error("Admit session operation", { cause })
|
|
11500
|
-
}), () => this.awaitStableIdentity(sessionId).pipe(
|
|
11565
|
+
}), () => this.awaitStableIdentity(sessionId).pipe(andThen(effect)), (release) => sync2(release));
|
|
11501
11566
|
}
|
|
11502
11567
|
forkTracked(effect, sessionId) {
|
|
11503
|
-
return
|
|
11568
|
+
return uninterruptibleMask2(() => suspend2(() => {
|
|
11504
11569
|
const release = this.retainWork(sessionId);
|
|
11505
|
-
return effect.pipe(
|
|
11570
|
+
return effect.pipe(ensuring(sync2(release)), forkDetach({ startImmediately: true }));
|
|
11506
11571
|
}));
|
|
11507
11572
|
}
|
|
11508
11573
|
persistedSessions() {
|
|
@@ -11527,7 +11592,7 @@ class SessionAdmission {
|
|
|
11527
11592
|
});
|
|
11528
11593
|
}
|
|
11529
11594
|
flushPersistence() {
|
|
11530
|
-
return
|
|
11595
|
+
return tryPromise({
|
|
11531
11596
|
try: () => this.persistenceTail,
|
|
11532
11597
|
catch: (cause) => cause instanceof Error ? cause : new Error("Persist browse sessions", { cause })
|
|
11533
11598
|
});
|
|
@@ -11537,16 +11602,16 @@ class SessionAdmission {
|
|
|
11537
11602
|
return rollback ? this.flushPersistenceWithRollback(rollback) : this.flushPersistence();
|
|
11538
11603
|
}
|
|
11539
11604
|
flushPersistenceWithRollback(rollback) {
|
|
11540
|
-
return this.flushPersistence().pipe(
|
|
11605
|
+
return this.flushPersistence().pipe(catch_((error) => gen({ self: this }, function* () {
|
|
11541
11606
|
yield* rollback();
|
|
11542
11607
|
this.schedulePersistence();
|
|
11543
|
-
yield* this.flushPersistence().pipe(
|
|
11544
|
-
return yield*
|
|
11608
|
+
yield* this.flushPersistence().pipe(ignore);
|
|
11609
|
+
return yield* fail4(error);
|
|
11545
11610
|
})));
|
|
11546
11611
|
}
|
|
11547
11612
|
beginDrain() {
|
|
11548
11613
|
const self = this;
|
|
11549
|
-
return
|
|
11614
|
+
return gen(function* () {
|
|
11550
11615
|
if (self.admission === "open")
|
|
11551
11616
|
self.admission = "draining";
|
|
11552
11617
|
while (true) {
|
|
@@ -11573,12 +11638,11 @@ class SessionAdmission {
|
|
|
11573
11638
|
}
|
|
11574
11639
|
|
|
11575
11640
|
// src/session-manager/adoption.ts
|
|
11576
|
-
import { Deferred as Deferred4, Effect as Effect17, Fiber as Fiber3, Result } from "effect";
|
|
11577
11641
|
function adoptTargetForSession(context, options) {
|
|
11578
|
-
return context.admission.withAdmission(options.sessionId,
|
|
11642
|
+
return context.admission.withAdmission(options.sessionId, uninterruptibleMask2((restore) => gen(function* () {
|
|
11579
11643
|
const resolved = yield* context.resolveWorkSession(options.sessionId, options.createIfMissing);
|
|
11580
11644
|
const session = resolved.session;
|
|
11581
|
-
const
|
|
11645
|
+
const result2 = yield* make5();
|
|
11582
11646
|
let state = "pending";
|
|
11583
11647
|
let cancelled = false;
|
|
11584
11648
|
let reservation;
|
|
@@ -11587,7 +11651,7 @@ function adoptTargetForSession(context, options) {
|
|
|
11587
11651
|
const timeoutMs = context.hooks.lifecycleTimeoutMs ?? 1e4;
|
|
11588
11652
|
const timeoutError = sessionError("timeout", `Session adopt for ${session.id} timed out after ${timeoutMs}ms`, session.id);
|
|
11589
11653
|
const adoptionCancelled = () => cancelled;
|
|
11590
|
-
const cancel =
|
|
11654
|
+
const cancel = sync2(() => {
|
|
11591
11655
|
if (cancelled) {
|
|
11592
11656
|
return;
|
|
11593
11657
|
}
|
|
@@ -11596,7 +11660,7 @@ function adoptTargetForSession(context, options) {
|
|
|
11596
11660
|
context.notifyTargetOwnershipChange(context.targetOwnership.rollbackTargetOwnership(reservation));
|
|
11597
11661
|
}
|
|
11598
11662
|
});
|
|
11599
|
-
const operation =
|
|
11663
|
+
const operation = gen(function* () {
|
|
11600
11664
|
if (context.sessions.get(session.id) !== session) {
|
|
11601
11665
|
return yield* sessionError("inactive", `Session is no longer active: ${session.id}`, session.id);
|
|
11602
11666
|
}
|
|
@@ -11607,7 +11671,7 @@ function adoptTargetForSession(context, options) {
|
|
|
11607
11671
|
if (options.tabId !== undefined && browser?.key && context.hooks.targetBrowserKey?.(options.tabId) !== browser.key) {
|
|
11608
11672
|
return yield* sessionError("invalid-request", `Tab ${options.tabId} is not in session ${session.id}'s browser '${browser.id}'. Start a new session for its browser.`, session.id);
|
|
11609
11673
|
}
|
|
11610
|
-
reservation = yield*
|
|
11674
|
+
reservation = yield* try_({
|
|
11611
11675
|
try: () => context.targetOwnership.reserveTargetOwnership(options.targetId, session.id),
|
|
11612
11676
|
catch: (cause) => cause instanceof Error ? cause : new Error("Reserve target ownership", { cause })
|
|
11613
11677
|
});
|
|
@@ -11631,7 +11695,7 @@ function adoptTargetForSession(context, options) {
|
|
|
11631
11695
|
if (adoptionCancelled()) {
|
|
11632
11696
|
return yield* timeoutError;
|
|
11633
11697
|
}
|
|
11634
|
-
yield*
|
|
11698
|
+
yield* try_({
|
|
11635
11699
|
try: () => {
|
|
11636
11700
|
context.notifyTargetOwnershipChange(context.targetOwnership.commitTargetOwnership({
|
|
11637
11701
|
reservation: activeReservation,
|
|
@@ -11643,35 +11707,35 @@ function adoptTargetForSession(context, options) {
|
|
|
11643
11707
|
});
|
|
11644
11708
|
const releasedTargetIds = previousTargetId && previousTargetId !== options.targetId ? [previousTargetId] : [];
|
|
11645
11709
|
const value = { adoptedUrl: options.targetUrl, releasedTargetIds, session: createdSummary(session, resolved.created) };
|
|
11646
|
-
yield*
|
|
11710
|
+
yield* succeed4(result2, value);
|
|
11647
11711
|
return value;
|
|
11648
11712
|
});
|
|
11649
|
-
const transaction = session.executeSemaphore.withPermit(operation.pipe(
|
|
11650
|
-
onFailure: (error) =>
|
|
11713
|
+
const transaction = session.executeSemaphore.withPermit(operation.pipe(matchEffect({
|
|
11714
|
+
onFailure: (error) => gen(function* () {
|
|
11651
11715
|
if (reservation && state !== "committed") {
|
|
11652
11716
|
context.notifyTargetOwnershipChange(context.targetOwnership.rollbackTargetOwnership(reservation));
|
|
11653
11717
|
}
|
|
11654
11718
|
if (context.sessions.get(session.id) === session && (resolved.created || state !== "pending")) {
|
|
11655
|
-
const cleanup = yield*
|
|
11656
|
-
if (
|
|
11657
|
-
yield*
|
|
11719
|
+
const cleanup = yield* result(cleanupSettledAdoption(context, session, resolved.created, previousTarget, previousRelayClosed));
|
|
11720
|
+
if (isFailure(cleanup)) {
|
|
11721
|
+
yield* fail3(result2, cleanup.failure);
|
|
11658
11722
|
return;
|
|
11659
11723
|
}
|
|
11660
11724
|
}
|
|
11661
|
-
yield*
|
|
11725
|
+
yield* fail3(result2, error);
|
|
11662
11726
|
}),
|
|
11663
|
-
onSuccess: () =>
|
|
11727
|
+
onSuccess: () => void_3
|
|
11664
11728
|
})));
|
|
11665
11729
|
const worker = context.adoptSemaphore.withPermit(transaction);
|
|
11666
11730
|
yield* context.admission.forkTracked(worker, session.id);
|
|
11667
|
-
return yield* restore(
|
|
11731
|
+
return yield* restore(_await(result2).pipe(timeoutOrElse({
|
|
11668
11732
|
duration: timeoutMs,
|
|
11669
|
-
orElse: () => cancel.pipe(
|
|
11670
|
-
}),
|
|
11733
|
+
orElse: () => cancel.pipe(andThen(fail4(timeoutError)))
|
|
11734
|
+
}), onInterrupt(() => cancel)));
|
|
11671
11735
|
})));
|
|
11672
11736
|
}
|
|
11673
11737
|
function cleanupSettledAdoption(context, session, created, previousTarget, previousRelayClosed = false) {
|
|
11674
|
-
return
|
|
11738
|
+
return gen(function* () {
|
|
11675
11739
|
const activeTarget = session.target;
|
|
11676
11740
|
yield* context.releaseSessionTargetOwnership(session);
|
|
11677
11741
|
if (previousTarget && previousTarget.id !== activeTarget?.id) {
|
|
@@ -11713,28 +11777,27 @@ function sessionRelayTargetIds(hooks, session) {
|
|
|
11713
11777
|
}
|
|
11714
11778
|
function withLifecycleTimeout(hooks, effect, label) {
|
|
11715
11779
|
const timeoutMs = hooks.lifecycleTimeoutMs ?? 1e4;
|
|
11716
|
-
return effect.pipe(
|
|
11780
|
+
return effect.pipe(timeoutOrElse({
|
|
11717
11781
|
duration: timeoutMs,
|
|
11718
|
-
orElse: () =>
|
|
11782
|
+
orElse: () => fail4(sessionError("timeout", `${label} timed out after ${timeoutMs}ms`))
|
|
11719
11783
|
}));
|
|
11720
11784
|
}
|
|
11721
11785
|
function closeSessionSettledWithTimeout(admission, hooks, session, closeSettled) {
|
|
11722
|
-
return admission.forkTracked(closeSettled, session.id).pipe(
|
|
11786
|
+
return admission.forkTracked(closeSettled, session.id).pipe(flatMap3((worker) => withLifecycleTimeout(hooks, join(worker), `Close session ${session.id}`)), ignore);
|
|
11723
11787
|
}
|
|
11724
11788
|
|
|
11725
11789
|
// src/session-manager/execution.ts
|
|
11726
|
-
import { Deferred as Deferred5, Effect as Effect18, Fiber as Fiber4, Option as Option2 } from "effect";
|
|
11727
11790
|
function executeInSession(ctx, options) {
|
|
11728
|
-
return ctx.admission.withAdmission(options.sessionId,
|
|
11791
|
+
return ctx.admission.withAdmission(options.sessionId, uninterruptibleMask2((restore) => gen(function* () {
|
|
11729
11792
|
if (options.removeAfterExecute && options.sessionId) {
|
|
11730
11793
|
return yield* sessionError("invalid-request", "One-shot execute requires a fresh unnamed session");
|
|
11731
11794
|
}
|
|
11732
11795
|
const resolved = yield* ctx.resolveWorkSession(options.sessionId, options.createIfMissing, options.browser);
|
|
11733
11796
|
const session = resolved.session;
|
|
11734
|
-
const response = yield*
|
|
11797
|
+
const response = yield* make5();
|
|
11735
11798
|
let started = false;
|
|
11736
11799
|
let cancelled = false;
|
|
11737
|
-
const operation =
|
|
11800
|
+
const operation = gen(function* () {
|
|
11738
11801
|
if (cancelled) {
|
|
11739
11802
|
return yield* sessionError("inactive", `Session execute was cancelled before starting: ${session.id}`, session.id);
|
|
11740
11803
|
}
|
|
@@ -11745,14 +11808,14 @@ function executeInSession(ctx, options) {
|
|
|
11745
11808
|
started = true;
|
|
11746
11809
|
session.updatedAt = new Date().toISOString();
|
|
11747
11810
|
yield* ctx.setExecuting(session.id, true);
|
|
11748
|
-
const stopSignal =
|
|
11749
|
-
const released =
|
|
11811
|
+
const stopSignal = makeUnsafe3();
|
|
11812
|
+
const released = makeUnsafe3();
|
|
11750
11813
|
const effectiveTimeoutMs = options.timeoutMs ?? ctx.hooks.executeTimeoutMs ?? defaultExecuteTimeoutMs;
|
|
11751
11814
|
let deadline;
|
|
11752
11815
|
const stopRunning = (message, diagnostic) => {
|
|
11753
11816
|
deadline?.cancel();
|
|
11754
|
-
|
|
11755
|
-
|
|
11817
|
+
runSync(succeed4(stopSignal, { message, diagnostic }));
|
|
11818
|
+
runSync(succeed4(response, {
|
|
11756
11819
|
result: stoppedExecuteResult(message, diagnostic),
|
|
11757
11820
|
session: createdSummary(session, resolved.created)
|
|
11758
11821
|
}));
|
|
@@ -11770,25 +11833,25 @@ function executeInSession(ctx, options) {
|
|
|
11770
11833
|
ctx.runningExecutions.set(session.id, running);
|
|
11771
11834
|
let forced = false;
|
|
11772
11835
|
const startedAt = Date.now();
|
|
11773
|
-
const result = yield*
|
|
11774
|
-
const script = yield* session.sandbox.execute(options.code, { ...options.targetSelection ? { targetSelection: options.targetSelection } : {} }).pipe(
|
|
11775
|
-
const first = yield*
|
|
11836
|
+
const result = yield* gen(function* () {
|
|
11837
|
+
const script = yield* session.sandbox.execute(options.code, { ...options.targetSelection ? { targetSelection: options.targetSelection } : {} }).pipe(forkDetach({ startImmediately: true }));
|
|
11838
|
+
const first = yield* raceFirst(join(script).pipe(map5((value) => ({ settled: true, value }))), _await(stopSignal).pipe(map5((stop) => ({ settled: false, stop }))));
|
|
11776
11839
|
if (first.settled)
|
|
11777
11840
|
return first.value;
|
|
11778
11841
|
yield* ctx.endStoppedScript(session);
|
|
11779
|
-
const settled = yield*
|
|
11780
|
-
if (
|
|
11842
|
+
const settled = yield* join(script).pipe(exit, timeoutOption(ctx.stopGraceMs()));
|
|
11843
|
+
if (isNone(settled)) {
|
|
11781
11844
|
forced = true;
|
|
11782
11845
|
yield* ctx.endStoppedScript(session);
|
|
11783
11846
|
ctx.replaceSandbox(session);
|
|
11784
11847
|
}
|
|
11785
11848
|
return stoppedExecuteResult(first.stop.message, first.stop.diagnostic);
|
|
11786
|
-
}).pipe(
|
|
11849
|
+
}).pipe(ensuring(gen(function* () {
|
|
11787
11850
|
deadline?.cancel();
|
|
11788
11851
|
if (ctx.runningExecutions.get(session.id) === running)
|
|
11789
11852
|
ctx.runningExecutions.delete(session.id);
|
|
11790
11853
|
yield* ctx.setExecuting(session.id, false);
|
|
11791
|
-
|
|
11854
|
+
runSync(succeed4(released, { forced }));
|
|
11792
11855
|
})));
|
|
11793
11856
|
if (resolved.created && result.setupFailed) {
|
|
11794
11857
|
return yield* sessionError("setup-failed", result.text, session.id);
|
|
@@ -11810,18 +11873,18 @@ function executeInSession(ctx, options) {
|
|
|
11810
11873
|
yield* ctx.admission.commitPersistence();
|
|
11811
11874
|
return { result: resultWithHint, session: createdSummary(session, resolved.created) };
|
|
11812
11875
|
});
|
|
11813
|
-
const worker = session.executeSemaphore.withPermit(operation.pipe(
|
|
11814
|
-
onFailure: (error) => (resolved.created ? ctx.cleanupCreatedSession(session) :
|
|
11815
|
-
onSuccess: (value) => (options.removeAfterExecute ? ctx.cleanupCreatedSession(session) :
|
|
11876
|
+
const worker = session.executeSemaphore.withPermit(operation.pipe(matchEffect({
|
|
11877
|
+
onFailure: (error) => (resolved.created ? ctx.cleanupCreatedSession(session) : void_3).pipe(andThen(fail3(response, error))),
|
|
11878
|
+
onSuccess: (value) => (options.removeAfterExecute ? ctx.cleanupCreatedSession(session) : void_3).pipe(andThen(succeed4(response, value)))
|
|
11816
11879
|
})));
|
|
11817
11880
|
const workerFiber = yield* ctx.admission.forkTracked(worker, session.id);
|
|
11818
|
-
return yield* restore(
|
|
11881
|
+
return yield* restore(_await(response)).pipe(onInterrupt(() => suspend2(() => {
|
|
11819
11882
|
if (started) {
|
|
11820
11883
|
ctx.stopExecution(session.id, `Execute stopped because its caller disconnected.`, "client-disconnected");
|
|
11821
|
-
return
|
|
11884
|
+
return void_3;
|
|
11822
11885
|
}
|
|
11823
11886
|
cancelled = true;
|
|
11824
|
-
return
|
|
11887
|
+
return interrupt3(workerFiber).pipe(asVoid2, andThen(resolved.created ? session.executeSemaphore.withPermit(ctx.cleanupCreatedSession(session)) : void_3));
|
|
11825
11888
|
})));
|
|
11826
11889
|
})));
|
|
11827
11890
|
}
|
|
@@ -11834,7 +11897,7 @@ class OpenCodeBrowserSessions {
|
|
|
11834
11897
|
hooks;
|
|
11835
11898
|
executing = new Set;
|
|
11836
11899
|
runningExecutions = new Map;
|
|
11837
|
-
adoptSemaphore =
|
|
11900
|
+
adoptSemaphore = makeUnsafe(1);
|
|
11838
11901
|
targetOwnership;
|
|
11839
11902
|
admission;
|
|
11840
11903
|
adoptionContext;
|
|
@@ -11953,11 +12016,11 @@ class OpenCodeBrowserSessions {
|
|
|
11953
12016
|
return session;
|
|
11954
12017
|
}
|
|
11955
12018
|
create(id, options) {
|
|
11956
|
-
return this.admission.withAdmission(id, this.createPersistedSession(id, options).pipe(
|
|
12019
|
+
return this.admission.withAdmission(id, this.createPersistedSession(id, options).pipe(uninterruptible));
|
|
11957
12020
|
}
|
|
11958
12021
|
ensure(id, options) {
|
|
11959
12022
|
const manager = this;
|
|
11960
|
-
return this.admission.withAdmission(id,
|
|
12023
|
+
return this.admission.withAdmission(id, gen(function* () {
|
|
11961
12024
|
const existing = manager.sessions.get(id);
|
|
11962
12025
|
if (existing) {
|
|
11963
12026
|
yield* trySession(() => manager.resolveBrowser(undefined, existing));
|
|
@@ -11968,14 +12031,14 @@ class OpenCodeBrowserSessions {
|
|
|
11968
12031
|
}
|
|
11969
12032
|
const session = yield* manager.createPersistedSession(id, options);
|
|
11970
12033
|
return sessionSummary(session);
|
|
11971
|
-
}).pipe(
|
|
12034
|
+
}).pipe(uninterruptible));
|
|
11972
12035
|
}
|
|
11973
|
-
createPersistedSession =
|
|
12036
|
+
createPersistedSession = fnUntraced(function* (id, options) {
|
|
11974
12037
|
const manager = this;
|
|
11975
12038
|
const sessionId = id ?? generateSessionId2(manager.sessions);
|
|
11976
|
-
return yield* manager.admission.withIdentityChange(sessionId,
|
|
12039
|
+
return yield* manager.admission.withIdentityChange(sessionId, gen(function* () {
|
|
11977
12040
|
const session = yield* trySession(() => manager.createAcceptedSession(sessionId, options));
|
|
11978
|
-
yield* manager.admission.flushPersistenceWithRollback(() =>
|
|
12041
|
+
yield* manager.admission.flushPersistenceWithRollback(() => gen(function* () {
|
|
11979
12042
|
if (manager.sessions.get(session.id) === session)
|
|
11980
12043
|
manager.sessions.delete(session.id);
|
|
11981
12044
|
yield* manager.closeOpenCodeBrowserSession(session);
|
|
@@ -12000,7 +12063,7 @@ class OpenCodeBrowserSessions {
|
|
|
12000
12063
|
}
|
|
12001
12064
|
setAllowedOrigins(id, patterns) {
|
|
12002
12065
|
const manager = this;
|
|
12003
|
-
return
|
|
12066
|
+
return gen(function* () {
|
|
12004
12067
|
const session = manager.sessions.get(id);
|
|
12005
12068
|
if (!session)
|
|
12006
12069
|
return yield* sessionError("not-found", `Session not found: ${id}`, id);
|
|
@@ -12028,15 +12091,15 @@ class OpenCodeBrowserSessions {
|
|
|
12028
12091
|
}
|
|
12029
12092
|
stopForLifecycle(id, operation) {
|
|
12030
12093
|
const manager = this;
|
|
12031
|
-
return
|
|
12094
|
+
return gen(function* () {
|
|
12032
12095
|
const message = `Session ${id} was ${operation === "delete" ? "deleted" : "reset"} while this ran, so it was stopped.`;
|
|
12033
12096
|
const handoff = manager.cancelSessionHandoff(id);
|
|
12034
12097
|
const running = manager.runningExecutions.get(id);
|
|
12035
12098
|
if (!running)
|
|
12036
12099
|
return handoff ? { execute: false, handoff, forced: false } : undefined;
|
|
12037
12100
|
running.stop(message, `session-${operation}`);
|
|
12038
|
-
const released = yield*
|
|
12039
|
-
return { execute: true, handoff, forced:
|
|
12101
|
+
const released = yield* _await(running.released).pipe(timeoutOption(manager.stopGraceMs() + 2000));
|
|
12102
|
+
return { execute: true, handoff, forced: isNone(released) || released.value.forced };
|
|
12040
12103
|
});
|
|
12041
12104
|
}
|
|
12042
12105
|
cancelSessionHandoff(id) {
|
|
@@ -12076,8 +12139,8 @@ class OpenCodeBrowserSessions {
|
|
|
12076
12139
|
networkEnd(id, reason) {
|
|
12077
12140
|
const session = this.sessions.get(id);
|
|
12078
12141
|
if (!session)
|
|
12079
|
-
return
|
|
12080
|
-
return session.sandbox.networkEnd(reason).pipe(
|
|
12142
|
+
return succeed6(false);
|
|
12143
|
+
return session.sandbox.networkEnd(reason).pipe(map5(({ cancelled }) => cancelled));
|
|
12081
12144
|
}
|
|
12082
12145
|
collectSessionIds(matches) {
|
|
12083
12146
|
const affectedSessionIds = [];
|
|
@@ -12115,19 +12178,19 @@ class OpenCodeBrowserSessions {
|
|
|
12115
12178
|
}
|
|
12116
12179
|
delete(id) {
|
|
12117
12180
|
const manager = this;
|
|
12118
|
-
return this.admission.withAdmission(id,
|
|
12181
|
+
return this.admission.withAdmission(id, gen(function* () {
|
|
12119
12182
|
const session = manager.sessions.get(id);
|
|
12120
12183
|
if (!session) {
|
|
12121
12184
|
return { deleted: false };
|
|
12122
12185
|
}
|
|
12123
12186
|
const stopped = yield* manager.stopForLifecycle(id, "delete");
|
|
12124
|
-
const deleted = yield* manager.withLifecyclePermit(session, "delete", manager.admission.withIdentityChange(id,
|
|
12187
|
+
const deleted = yield* manager.withLifecyclePermit(session, "delete", manager.admission.withIdentityChange(id, gen(function* () {
|
|
12125
12188
|
if (manager.sessions.get(id) !== session) {
|
|
12126
12189
|
return false;
|
|
12127
12190
|
}
|
|
12128
12191
|
yield* manager.closeSessionRelayTargets(session);
|
|
12129
12192
|
manager.sessions.delete(id);
|
|
12130
|
-
yield* manager.admission.commitPersistence(() =>
|
|
12193
|
+
yield* manager.admission.commitPersistence(() => sync2(() => {
|
|
12131
12194
|
manager.sessions.set(id, session);
|
|
12132
12195
|
}));
|
|
12133
12196
|
yield* manager.releaseSessionTargetOwnership(session);
|
|
@@ -12139,13 +12202,13 @@ class OpenCodeBrowserSessions {
|
|
|
12139
12202
|
}
|
|
12140
12203
|
reset(id) {
|
|
12141
12204
|
const manager = this;
|
|
12142
|
-
return this.admission.withAdmission(id,
|
|
12205
|
+
return this.admission.withAdmission(id, gen(function* () {
|
|
12143
12206
|
const existing = manager.sessions.get(id);
|
|
12144
12207
|
if (!existing) {
|
|
12145
12208
|
return;
|
|
12146
12209
|
}
|
|
12147
12210
|
const stopped = yield* manager.stopForLifecycle(id, "reset");
|
|
12148
|
-
const session = yield* manager.withLifecyclePermit(existing, "reset", manager.admission.withIdentityChange(id,
|
|
12211
|
+
const session = yield* manager.withLifecyclePermit(existing, "reset", manager.admission.withIdentityChange(id, gen(function* () {
|
|
12149
12212
|
if (manager.sessions.get(id) !== existing) {
|
|
12150
12213
|
return yield* sessionError("inactive", `Session is no longer active: ${id}`, id);
|
|
12151
12214
|
}
|
|
@@ -12156,7 +12219,7 @@ class OpenCodeBrowserSessions {
|
|
|
12156
12219
|
if (existing.browser)
|
|
12157
12220
|
session.browser = existing.browser;
|
|
12158
12221
|
manager.sessions.set(id, session);
|
|
12159
|
-
yield* manager.admission.commitPersistence(() =>
|
|
12222
|
+
yield* manager.admission.commitPersistence(() => sync2(() => {
|
|
12160
12223
|
manager.sessions.set(id, existing);
|
|
12161
12224
|
}));
|
|
12162
12225
|
yield* manager.releaseSessionTargetOwnership(existing);
|
|
@@ -12175,7 +12238,7 @@ class OpenCodeBrowserSessions {
|
|
|
12175
12238
|
}
|
|
12176
12239
|
networkStatus(id) {
|
|
12177
12240
|
const session = this.sessions.get(id);
|
|
12178
|
-
return session ?
|
|
12241
|
+
return session ? succeed6(session.sandbox.networkStatus()) : fail4(sessionError("not-found", `Session not found: ${id}`, id));
|
|
12179
12242
|
}
|
|
12180
12243
|
networkStop(id, options = {}) {
|
|
12181
12244
|
return this.withSessionOperation(id, "network stop", (session) => session.sandbox.networkStop(options));
|
|
@@ -12188,11 +12251,11 @@ class OpenCodeBrowserSessions {
|
|
|
12188
12251
|
}
|
|
12189
12252
|
withSessionOperation(id, operation, use, beforePermit) {
|
|
12190
12253
|
const manager = this;
|
|
12191
|
-
return this.admission.withAdmission(id,
|
|
12254
|
+
return this.admission.withAdmission(id, suspend2(() => {
|
|
12192
12255
|
const session = manager.sessions.get(id);
|
|
12193
12256
|
if (!session)
|
|
12194
|
-
return
|
|
12195
|
-
return (beforePermit ? beforePermit(session) :
|
|
12257
|
+
return fail4(sessionError("not-found", `Session not found: ${id}`, id));
|
|
12258
|
+
return (beforePermit ? beforePermit(session) : void_3).pipe(andThen(manager.withLifecyclePermit(session, operation, gen(function* () {
|
|
12196
12259
|
if (manager.sessions.get(id) !== session) {
|
|
12197
12260
|
return yield* sessionError("inactive", `Session is no longer active: ${id}`, id);
|
|
12198
12261
|
}
|
|
@@ -12204,13 +12267,13 @@ class OpenCodeBrowserSessions {
|
|
|
12204
12267
|
authenticatedJson(request) {
|
|
12205
12268
|
const manager = this;
|
|
12206
12269
|
const { sessionId, ...pageRequest } = request;
|
|
12207
|
-
return this.withSessionOperation(sessionId, "authenticated request", (session) =>
|
|
12270
|
+
return this.withSessionOperation(sessionId, "authenticated request", (session) => gen(function* () {
|
|
12208
12271
|
yield* manager.setExecuting(session.id, true);
|
|
12209
|
-
const result = yield* session.sandbox.authenticatedJson(pageRequest).pipe(
|
|
12272
|
+
const result = yield* session.sandbox.authenticatedJson(pageRequest).pipe(ensuring(manager.setExecuting(session.id, false)));
|
|
12210
12273
|
session.updatedAt = new Date().toISOString();
|
|
12211
12274
|
yield* manager.admission.commitPersistence();
|
|
12212
12275
|
return result;
|
|
12213
|
-
}), (session) => session.readOnly && request.method !== "GET" ?
|
|
12276
|
+
}), (session) => session.readOnly && request.method !== "GET" ? fail4(sessionError("invalid-request", `Read-only session ${sessionId} cannot make ${request.method} authenticated requests`, sessionId)) : void_3);
|
|
12214
12277
|
}
|
|
12215
12278
|
execute(options) {
|
|
12216
12279
|
return executeInSession(this.executionContext, options);
|
|
@@ -12236,17 +12299,17 @@ class OpenCodeBrowserSessions {
|
|
|
12236
12299
|
}
|
|
12237
12300
|
closeAll() {
|
|
12238
12301
|
const manager = this;
|
|
12239
|
-
return
|
|
12302
|
+
return gen(function* () {
|
|
12240
12303
|
manager.admission.markClosed();
|
|
12241
|
-
yield* manager.admission.beginDrain().pipe(
|
|
12242
|
-
yield* manager.adoptSemaphore.withPermit(
|
|
12243
|
-
yield*
|
|
12304
|
+
yield* manager.admission.beginDrain().pipe(ignore);
|
|
12305
|
+
yield* manager.adoptSemaphore.withPermit(gen(function* () {
|
|
12306
|
+
yield* forEach(Array.from(manager.sessions.values()), (session) => {
|
|
12244
12307
|
return session.executeSemaphore.withPermit(manager.disconnectOpenCodeBrowserSession(session));
|
|
12245
12308
|
}, { concurrency: "unbounded", discard: true });
|
|
12246
|
-
yield* manager.admission.flushPersistence().pipe(
|
|
12309
|
+
yield* manager.admission.flushPersistence().pipe(ignore);
|
|
12247
12310
|
manager.sessions.clear();
|
|
12248
12311
|
}));
|
|
12249
|
-
}).pipe(
|
|
12312
|
+
}).pipe(uninterruptible);
|
|
12250
12313
|
}
|
|
12251
12314
|
summary(id) {
|
|
12252
12315
|
const session = this.sessions.get(id);
|
|
@@ -12254,7 +12317,7 @@ class OpenCodeBrowserSessions {
|
|
|
12254
12317
|
}
|
|
12255
12318
|
reapIdleSessions(options) {
|
|
12256
12319
|
const manager = this;
|
|
12257
|
-
return
|
|
12320
|
+
return gen(function* () {
|
|
12258
12321
|
const now = options.now ?? Date.now();
|
|
12259
12322
|
const reaped = [];
|
|
12260
12323
|
for (const session of Array.from(manager.sessions.values())) {
|
|
@@ -12271,7 +12334,7 @@ class OpenCodeBrowserSessions {
|
|
|
12271
12334
|
if (Array.from(targetIds).some((id) => options.isTargetBusy?.(id)))
|
|
12272
12335
|
continue;
|
|
12273
12336
|
const autoNamed = /^mcp-[0-9a-f]{6,}$/i.test(session.id) || /^[a-z]+-[a-z]+-\d{3}$/.test(session.id) || session.id.startsWith("oc-");
|
|
12274
|
-
const done = yield* (autoNamed ? manager.delete(session.id).pipe(
|
|
12337
|
+
const done = yield* (autoNamed ? manager.delete(session.id).pipe(map5((r) => r.deleted)) : manager.reset(session.id).pipe(map5(Boolean))).pipe(orElseSucceed(() => false));
|
|
12275
12338
|
if (done)
|
|
12276
12339
|
reaped.push(session.id);
|
|
12277
12340
|
}
|
|
@@ -12279,30 +12342,30 @@ class OpenCodeBrowserSessions {
|
|
|
12279
12342
|
});
|
|
12280
12343
|
}
|
|
12281
12344
|
sweepOrphanedRelayTargets() {
|
|
12282
|
-
return
|
|
12345
|
+
return forEach(this.hooks.getTargets?.() ?? [], (target) => target.owner === "relay" && target.opencodeBrowserSessionId && !this.sessions.has(target.opencodeBrowserSessionId) ? this.closeRelayTarget(target.targetInfo.targetId).pipe(ignore) : void_3, { discard: true });
|
|
12283
12346
|
}
|
|
12284
12347
|
persist() {
|
|
12285
12348
|
return this.admission.flushPersistence();
|
|
12286
12349
|
}
|
|
12287
12350
|
setExecuting(id, executing) {
|
|
12288
|
-
return
|
|
12351
|
+
return suspend2(() => {
|
|
12289
12352
|
if (executing)
|
|
12290
12353
|
this.executing.add(id);
|
|
12291
12354
|
else
|
|
12292
12355
|
this.executing.delete(id);
|
|
12293
12356
|
try {
|
|
12294
|
-
return this.hooks.onExecuteStateChange?.(id, executing) ??
|
|
12357
|
+
return this.hooks.onExecuteStateChange?.(id, executing) ?? void_3;
|
|
12295
12358
|
} catch (error) {
|
|
12296
12359
|
console.error("Session execute-state hook failed", error);
|
|
12297
12360
|
}
|
|
12298
|
-
return
|
|
12361
|
+
return void_3;
|
|
12299
12362
|
});
|
|
12300
12363
|
}
|
|
12301
12364
|
recordExecute(record) {
|
|
12302
12365
|
const hook = this.hooks.onExecuteRecord;
|
|
12303
12366
|
if (!hook)
|
|
12304
|
-
return
|
|
12305
|
-
return
|
|
12367
|
+
return void_3;
|
|
12368
|
+
return tryPromise({
|
|
12306
12369
|
try: async () => {
|
|
12307
12370
|
const release = this.admission.retainWork(record.sessionId);
|
|
12308
12371
|
try {
|
|
@@ -12312,12 +12375,12 @@ class OpenCodeBrowserSessions {
|
|
|
12312
12375
|
}
|
|
12313
12376
|
},
|
|
12314
12377
|
catch: (cause) => cause
|
|
12315
|
-
}).pipe(
|
|
12378
|
+
}).pipe(timeoutOrElse({
|
|
12316
12379
|
duration: this.hooks.journalTimeoutMs ?? 2000,
|
|
12317
|
-
orElse: () =>
|
|
12318
|
-
}),
|
|
12380
|
+
orElse: () => fail4(new Error(`Session journal write timed out after ${this.hooks.journalTimeoutMs ?? 2000}ms`))
|
|
12381
|
+
}), catch_((error) => sync2(() => {
|
|
12319
12382
|
console.error("Session execute-record hook failed", error);
|
|
12320
|
-
})),
|
|
12383
|
+
})), asVoid2);
|
|
12321
12384
|
}
|
|
12322
12385
|
createOpenCodeBrowserSession(id, readOnly, timestamps) {
|
|
12323
12386
|
const now = new Date().toISOString();
|
|
@@ -12331,13 +12394,13 @@ class OpenCodeBrowserSessions {
|
|
|
12331
12394
|
if (session)
|
|
12332
12395
|
this.updateTarget(session, target);
|
|
12333
12396
|
}),
|
|
12334
|
-
executeSemaphore:
|
|
12397
|
+
executeSemaphore: makeUnsafe(1)
|
|
12335
12398
|
};
|
|
12336
12399
|
return session;
|
|
12337
12400
|
}
|
|
12338
12401
|
endStoppedScript(session) {
|
|
12339
12402
|
this.cancelSessionHandoff(session.id);
|
|
12340
|
-
return session.sandbox.abortExecution().pipe(
|
|
12403
|
+
return session.sandbox.abortExecution().pipe(timeoutOption(this.stopGraceMs()), catchCause((cause) => sync2(() => console.error("Abort stopped execute", cause))), asVoid2);
|
|
12341
12404
|
}
|
|
12342
12405
|
replaceSandbox(session) {
|
|
12343
12406
|
if (this.sessions.get(session.id) !== session)
|
|
@@ -12372,10 +12435,10 @@ class OpenCodeBrowserSessions {
|
|
|
12372
12435
|
return closeSessionSettledWithTimeout(this.admission, this.hooks, session, this.closeOpenCodeBrowserSessionSettled(session));
|
|
12373
12436
|
}
|
|
12374
12437
|
disconnectOpenCodeBrowserSession(session) {
|
|
12375
|
-
return
|
|
12438
|
+
return acquireUseRelease(sync2(() => this.admission.retainWork(session.id)), () => session.sandbox.disconnectSettled().pipe(ignore), (release) => sync2(release));
|
|
12376
12439
|
}
|
|
12377
12440
|
closeOpenCodeBrowserSessionSettled(session) {
|
|
12378
|
-
return session.sandbox.closeSettled({ clearState: true }).pipe(
|
|
12441
|
+
return session.sandbox.closeSettled({ clearState: true }).pipe(ignore);
|
|
12379
12442
|
}
|
|
12380
12443
|
releaseSessionTargetOwnership(session) {
|
|
12381
12444
|
const ids = sessionRelayTargetIds(this.hooks, session);
|
|
@@ -12384,14 +12447,14 @@ class OpenCodeBrowserSessions {
|
|
|
12384
12447
|
for (const id of ids)
|
|
12385
12448
|
this.notifyTargetOwnershipChange(this.targetOwnership.releaseTargetOwnership(id, session.id));
|
|
12386
12449
|
delete session.target;
|
|
12387
|
-
return
|
|
12450
|
+
return void_3;
|
|
12388
12451
|
}
|
|
12389
12452
|
closeSessionRelayTargets(session) {
|
|
12390
|
-
return
|
|
12453
|
+
return gen({ self: this }, function* () {
|
|
12391
12454
|
const closed = new Set;
|
|
12392
12455
|
const defaultId = session.target?.owner === "relay" ? session.target.id : undefined;
|
|
12393
12456
|
if (defaultId) {
|
|
12394
|
-
yield* this.closeRelayTarget(defaultId).pipe(
|
|
12457
|
+
yield* this.closeRelayTarget(defaultId).pipe(ignore);
|
|
12395
12458
|
closed.add(defaultId);
|
|
12396
12459
|
}
|
|
12397
12460
|
while (true) {
|
|
@@ -12399,14 +12462,14 @@ class OpenCodeBrowserSessions {
|
|
|
12399
12462
|
if (pending.length === 0)
|
|
12400
12463
|
return;
|
|
12401
12464
|
for (const targetId of pending) {
|
|
12402
|
-
yield* this.closeRelayTarget(targetId).pipe(
|
|
12465
|
+
yield* this.closeRelayTarget(targetId).pipe(ignore);
|
|
12403
12466
|
closed.add(targetId);
|
|
12404
12467
|
}
|
|
12405
12468
|
}
|
|
12406
12469
|
});
|
|
12407
12470
|
}
|
|
12408
12471
|
closeRelayTarget(targetId) {
|
|
12409
|
-
return this.hooks.onReleaseRelayTarget?.(targetId) ??
|
|
12472
|
+
return this.hooks.onReleaseRelayTarget?.(targetId) ?? void_3;
|
|
12410
12473
|
}
|
|
12411
12474
|
notifyTargetOwnershipChange(change) {
|
|
12412
12475
|
if (change.targetIds.length === 0 && change.tabIds.length === 0)
|
|
@@ -12419,30 +12482,30 @@ class OpenCodeBrowserSessions {
|
|
|
12419
12482
|
}
|
|
12420
12483
|
cleanupCreatedSession(session) {
|
|
12421
12484
|
const manager = this;
|
|
12422
|
-
return
|
|
12485
|
+
return gen(function* () {
|
|
12423
12486
|
if (manager.sessions.get(session.id) !== session)
|
|
12424
12487
|
return;
|
|
12425
12488
|
yield* manager.closeSessionRelayTargets(session);
|
|
12426
12489
|
manager.sessions.delete(session.id);
|
|
12427
12490
|
yield* manager.releaseSessionTargetOwnership(session);
|
|
12428
12491
|
yield* manager.closeOpenCodeBrowserSession(session);
|
|
12429
|
-
yield* manager.admission.commitPersistence().pipe(
|
|
12492
|
+
yield* manager.admission.commitPersistence().pipe(ignore);
|
|
12430
12493
|
});
|
|
12431
12494
|
}
|
|
12432
12495
|
withLifecyclePermit(session, operation, effect) {
|
|
12433
|
-
return
|
|
12496
|
+
return suspend2(() => {
|
|
12434
12497
|
let state = "waiting";
|
|
12435
12498
|
const timeout = sessionError("timeout", `Session ${operation} timed out waiting for active execute in ${session.id}`, session.id);
|
|
12436
|
-
return
|
|
12499
|
+
return raceFirst(session.executeSemaphore.withPermit(suspend2(() => {
|
|
12437
12500
|
if (state === "timed-out")
|
|
12438
|
-
return
|
|
12501
|
+
return fail4(timeout);
|
|
12439
12502
|
state = "started";
|
|
12440
12503
|
return effect;
|
|
12441
|
-
}).pipe(
|
|
12504
|
+
}).pipe(uninterruptible)), sleep(this.hooks.lifecycleTimeoutMs ?? 1e4).pipe(flatMap3(() => {
|
|
12442
12505
|
if (state === "started")
|
|
12443
|
-
return
|
|
12506
|
+
return never;
|
|
12444
12507
|
state = "timed-out";
|
|
12445
|
-
return
|
|
12508
|
+
return fail4(timeout);
|
|
12446
12509
|
})));
|
|
12447
12510
|
});
|
|
12448
12511
|
}
|
|
@@ -12466,7 +12529,7 @@ function runRequestEffect(response, effect) {
|
|
|
12466
12529
|
controller.abort();
|
|
12467
12530
|
};
|
|
12468
12531
|
response.once("close", onClose);
|
|
12469
|
-
|
|
12532
|
+
runPromise(effect, { signal: controller.signal }).catch((error) => {
|
|
12470
12533
|
if (response.destroyed || response.writableEnded)
|
|
12471
12534
|
return;
|
|
12472
12535
|
const routeError = relayHttpError(error);
|
|
@@ -12495,7 +12558,7 @@ function targetSummaries(registry) {
|
|
|
12495
12558
|
});
|
|
12496
12559
|
}
|
|
12497
12560
|
function decodeRequest(schema, body, label) {
|
|
12498
|
-
return
|
|
12561
|
+
return decodeUnknownEffect2(schema)(body).pipe(mapError((cause) => new HttpRouteError2({
|
|
12499
12562
|
message: `Invalid ${label} request: ${cause.message}`,
|
|
12500
12563
|
status: 400,
|
|
12501
12564
|
code: "invalid-request"
|
|
@@ -12505,7 +12568,7 @@ function optionalInteger(value, field) {
|
|
|
12505
12568
|
if (value === undefined) {
|
|
12506
12569
|
return;
|
|
12507
12570
|
}
|
|
12508
|
-
if (!
|
|
12571
|
+
if (!isNumber(value) || !Number.isInteger(value)) {
|
|
12509
12572
|
throw new HttpRouteError2({ message: `${field} must be an integer`, status: 400, code: "invalid-request" });
|
|
12510
12573
|
}
|
|
12511
12574
|
return value;
|
|
@@ -12519,32 +12582,32 @@ function relayHttpError(error) {
|
|
|
12519
12582
|
return error;
|
|
12520
12583
|
}
|
|
12521
12584
|
if (error instanceof SessionError) {
|
|
12522
|
-
const [status, code] =
|
|
12585
|
+
const [status, code] = value2(error.reason).pipe(when2("already-exists", () => [409, "session-already-exists"]), when2("browser-unavailable", () => [503, "browser-unavailable"]), when2("inactive", () => [409, "session-inactive"]), when2("invalid-request", () => [400, "invalid-request"]), when2("not-found", () => [404, "session-not-found"]), when2("target-owned", () => [409, "target-owned"]), when2("timeout", () => [409, "session-timeout"]), when2("setup-failed", () => [500, "setup-failed"]), exhaustive);
|
|
12523
12586
|
return routeError(error.message, status, code);
|
|
12524
12587
|
}
|
|
12525
12588
|
if (error instanceof NetworkCaptureError) {
|
|
12526
|
-
const [status, code] =
|
|
12589
|
+
const [status, code] = value2(error.reason).pipe(when2("invalid-options", () => [400, "invalid-request"]), when2("already-active", () => [409, "capture-conflict"]), when2("inactive", () => [409, "capture-conflict"]), orElse(() => [500, "internal"]));
|
|
12527
12590
|
return routeError(error.message, status, code);
|
|
12528
12591
|
}
|
|
12529
12592
|
if (error instanceof AuthProfileError) {
|
|
12530
|
-
const [status, code] =
|
|
12593
|
+
const [status, code] = value2(error.reason).pipe(when2("invalid-name", () => [400, "invalid-request"]), when2("not-found", () => [404, "auth-profile-not-found"]), orElse(() => [500, "internal"]));
|
|
12531
12594
|
return routeError(error.message, status, code);
|
|
12532
12595
|
}
|
|
12533
12596
|
if (error instanceof AuthenticatedOriginError) {
|
|
12534
12597
|
return routeError(error.message, error.reason === "invalid-request" ? 400 : 500, error.reason === "invalid-request" ? "invalid-request" : "setup-failed");
|
|
12535
12598
|
}
|
|
12536
12599
|
if (error instanceof TargetSelectionError) {
|
|
12537
|
-
const [status, code] =
|
|
12600
|
+
const [status, code] = value2(error.reason).pipe(when2("invalid", () => [400, "invalid-request"]), when2("not-found", () => [404, "target-not-found"]), when2("ambiguous", () => [409, "target-ambiguous"]), exhaustive);
|
|
12538
12601
|
return routeError(error.message, status, code);
|
|
12539
12602
|
}
|
|
12540
12603
|
if (error instanceof TargetOwnershipError) {
|
|
12541
|
-
const [status, code] =
|
|
12604
|
+
const [status, code] = value2(error.reason).pipe(when2("not-found", () => [404, "target-not-found"]), when2("owned", () => [409, "target-owned"]), when2("generation-changed", () => [409, "target-changed"]), exhaustive);
|
|
12542
12605
|
return routeError(error.message, status, code);
|
|
12543
12606
|
}
|
|
12544
12607
|
return routeError(error instanceof Error ? error.message : String(error), 500, "internal");
|
|
12545
12608
|
}
|
|
12546
12609
|
function originPatterns(raw) {
|
|
12547
|
-
return
|
|
12610
|
+
return try_({
|
|
12548
12611
|
try: () => normalizeOriginPatterns(raw),
|
|
12549
12612
|
catch: (cause) => cause instanceof OriginPatternError ? new Error(cause.message) : new Error("Invalid origin patterns", { cause })
|
|
12550
12613
|
});
|
|
@@ -12554,9 +12617,6 @@ function sendNotFound(response) {
|
|
|
12554
12617
|
response.end("Not found");
|
|
12555
12618
|
}
|
|
12556
12619
|
|
|
12557
|
-
// src/http-api/recording-routes.ts
|
|
12558
|
-
import { Effect as Effect21, Predicate as Predicate17 } from "effect";
|
|
12559
|
-
|
|
12560
12620
|
// src/recording-relay.ts
|
|
12561
12621
|
import crypto7 from "node:crypto";
|
|
12562
12622
|
import fs12 from "node:fs/promises";
|
|
@@ -13366,7 +13426,7 @@ function recordingStatusCode(result) {
|
|
|
13366
13426
|
return 500;
|
|
13367
13427
|
}
|
|
13368
13428
|
function handleRecordingRequest(options) {
|
|
13369
|
-
return
|
|
13429
|
+
return gen(function* () {
|
|
13370
13430
|
if (options.pathname === "/recording/start" && options.request.method === "POST") {
|
|
13371
13431
|
const body = yield* readJsonBody2(options.request);
|
|
13372
13432
|
const { tabId, sessionId, ...recordingOptions } = yield* decodeRequest(RecordingStartRequest, body, "recording start");
|
|
@@ -13407,7 +13467,7 @@ function handleRecordingRequest(options) {
|
|
|
13407
13467
|
});
|
|
13408
13468
|
}
|
|
13409
13469
|
function handleFlightRecorderRequest(options) {
|
|
13410
|
-
return
|
|
13470
|
+
return gen(function* () {
|
|
13411
13471
|
if (options.pathname === "/flight-recorder/start" && options.request.method === "POST") {
|
|
13412
13472
|
const request = yield* decodeRequest(FlightRecorderStartRequest, yield* readJsonBody2(options.request), "flight recorder start");
|
|
13413
13473
|
const target = resolveAttachedRecordingTarget({ registry: options.registry, tabId: request.tabId, sessionId: request.sessionId });
|
|
@@ -13440,14 +13500,14 @@ function handleFlightRecorderRequest(options) {
|
|
|
13440
13500
|
if (options.pathname === "/flight-recorder/cancel" && options.request.method === "POST") {
|
|
13441
13501
|
const request = yield* decodeRequest(RecordingTargetRequest, yield* readJsonBody2(options.request), "flight recorder cancel");
|
|
13442
13502
|
const target = recordingTargetFromValues({ registry: options.registry, tabId: request.tabId, sessionId: request.sessionId });
|
|
13443
|
-
sendJson2(options.response, yield*
|
|
13503
|
+
sendJson2(options.response, yield* promise(() => options.flightRecorder.cancel(target)));
|
|
13444
13504
|
return;
|
|
13445
13505
|
}
|
|
13446
13506
|
sendNotFound(options.response);
|
|
13447
13507
|
});
|
|
13448
13508
|
}
|
|
13449
13509
|
function readRecordingStatus(registry, read) {
|
|
13450
|
-
return
|
|
13510
|
+
return tryPromise({
|
|
13451
13511
|
try: async () => await read(),
|
|
13452
13512
|
catch: (cause) => {
|
|
13453
13513
|
if (!(cause instanceof AmbiguousRecordingTargetError))
|
|
@@ -13462,7 +13522,7 @@ function readRecordingStatus(registry, read) {
|
|
|
13462
13522
|
});
|
|
13463
13523
|
}
|
|
13464
13524
|
function tryRecordingPromise(label, tryFn) {
|
|
13465
|
-
return
|
|
13525
|
+
return tryPromise({
|
|
13466
13526
|
try: tryFn,
|
|
13467
13527
|
catch: (cause) => new Error(formatCauseMessage({ label, cause }), { cause })
|
|
13468
13528
|
});
|
|
@@ -13471,7 +13531,7 @@ function formatCauseMessage(options) {
|
|
|
13471
13531
|
if (options.cause instanceof Error && options.cause.message) {
|
|
13472
13532
|
return `${options.label}: ${options.cause.message}`;
|
|
13473
13533
|
}
|
|
13474
|
-
if (
|
|
13534
|
+
if (isString(options.cause) && options.cause) {
|
|
13475
13535
|
return `${options.label}: ${options.cause}`;
|
|
13476
13536
|
}
|
|
13477
13537
|
return options.label;
|
|
@@ -13485,7 +13545,7 @@ function resolveAttachedRecordingTarget(options) {
|
|
|
13485
13545
|
}
|
|
13486
13546
|
return { tabId, sessionId: target.sessionId, owner: target.owner };
|
|
13487
13547
|
}
|
|
13488
|
-
const sessionId =
|
|
13548
|
+
const sessionId = isString(options.sessionId) && options.sessionId ? options.sessionId : undefined;
|
|
13489
13549
|
if (sessionId) {
|
|
13490
13550
|
const target = options.registry.getRootTargetBySessionId(sessionId);
|
|
13491
13551
|
if (!target) {
|
|
@@ -13508,7 +13568,7 @@ function resolveAttachedRecordingTarget(options) {
|
|
|
13508
13568
|
}
|
|
13509
13569
|
function recordingTargetFromValues(options) {
|
|
13510
13570
|
const tabId = optionalInteger(options.tabId, "tabId");
|
|
13511
|
-
const sessionId =
|
|
13571
|
+
const sessionId = isString(options.sessionId) && options.sessionId ? options.sessionId : undefined;
|
|
13512
13572
|
const target = sessionId ? options.registry.getRootTargetBySessionId(sessionId) : undefined;
|
|
13513
13573
|
return {
|
|
13514
13574
|
...tabId === undefined ? {} : { tabId },
|
|
@@ -13525,9 +13585,8 @@ function recordingTargetFromQuery(options) {
|
|
|
13525
13585
|
}
|
|
13526
13586
|
|
|
13527
13587
|
// src/http-api/session-routes.ts
|
|
13528
|
-
import { Effect as Effect22 } from "effect";
|
|
13529
13588
|
function handleClientRequest(options) {
|
|
13530
|
-
return
|
|
13589
|
+
return gen(function* () {
|
|
13531
13590
|
if (options.pathname === "/v1/sessions/ensure" && options.request.method === "POST") {
|
|
13532
13591
|
const request = yield* decodeRequest(SessionEnsureRequest, yield* readJsonBody2(options.request), "session ensure");
|
|
13533
13592
|
const session = yield* options.sessions.ensure(request.id, {
|
|
@@ -13547,7 +13606,7 @@ function handleClientRequest(options) {
|
|
|
13547
13606
|
});
|
|
13548
13607
|
}
|
|
13549
13608
|
function handleNetworkRequest(options) {
|
|
13550
|
-
return
|
|
13609
|
+
return gen(function* () {
|
|
13551
13610
|
if (options.pathname === "/network/start" && options.request.method === "POST") {
|
|
13552
13611
|
const request = yield* decodeRequest(NetworkStartRequest, yield* readJsonBody2(options.request), "network start");
|
|
13553
13612
|
const { sessionId, ...captureOptions } = request;
|
|
@@ -13576,7 +13635,7 @@ function handleNetworkRequest(options) {
|
|
|
13576
13635
|
});
|
|
13577
13636
|
}
|
|
13578
13637
|
function handleAuthRequest(options) {
|
|
13579
|
-
return
|
|
13638
|
+
return gen(function* () {
|
|
13580
13639
|
if (options.pathname === "/auth/status" && options.request.method === "POST") {
|
|
13581
13640
|
const request = yield* decodeRequest(AuthProfileRequest, yield* readJsonBody2(options.request), "auth status");
|
|
13582
13641
|
sendJson2(options.response, yield* status(request.name));
|
|
@@ -13597,7 +13656,7 @@ function handleAuthRequest(options) {
|
|
|
13597
13656
|
});
|
|
13598
13657
|
}
|
|
13599
13658
|
function handleCliRequest(options) {
|
|
13600
|
-
return
|
|
13659
|
+
return gen(function* () {
|
|
13601
13660
|
if (options.pathname === "/cli/sessions" && options.request.method === "GET") {
|
|
13602
13661
|
sendJson2(options.response, { sessions: options.sessions.listSummaries() });
|
|
13603
13662
|
return;
|
|
@@ -13710,7 +13769,7 @@ function createHttpRequestHandler(options) {
|
|
|
13710
13769
|
sendJson2(response, { error: hostError }, 403);
|
|
13711
13770
|
return;
|
|
13712
13771
|
}
|
|
13713
|
-
const compatibility = { extensionProtocol: { min: minExtensionProtocolVersion, version: extensionProtocolVersion }, pairing:
|
|
13772
|
+
const compatibility = { extensionProtocol: { min: minExtensionProtocolVersion, version: extensionProtocolVersion }, pairing: context2 };
|
|
13714
13773
|
const installHint = request.headers["x-browse-install"] === "probe" && Date.now() < installWaitingUntil ? { installWaiting: true } : {};
|
|
13715
13774
|
const fetchSiteError = validateBrowserFetchSite2(request);
|
|
13716
13775
|
if (fetchSiteError) {
|
|
@@ -13725,7 +13784,7 @@ function createHttpRequestHandler(options) {
|
|
|
13725
13784
|
}
|
|
13726
13785
|
const observational = request.method === "GET" || pathname === "/network/status" || pathname === "/auth/status";
|
|
13727
13786
|
const run = (effect, settle = false) => {
|
|
13728
|
-
runRequestEffect(response, observational ? effect : options.shutdown.track(settle ?
|
|
13787
|
+
runRequestEffect(response, observational ? effect : options.shutdown.track(settle ? uninterruptible(effect) : effect));
|
|
13729
13788
|
};
|
|
13730
13789
|
if (pathname === "/" || pathname === "/version") {
|
|
13731
13790
|
sendJson2(response, {
|
|
@@ -13742,7 +13801,7 @@ function createHttpRequestHandler(options) {
|
|
|
13742
13801
|
return;
|
|
13743
13802
|
}
|
|
13744
13803
|
if (pathname === "/shutdown" && request.method === "POST") {
|
|
13745
|
-
runRequestEffect(response,
|
|
13804
|
+
runRequestEffect(response, gen(function* () {
|
|
13746
13805
|
const body = yield* decodeRequest(RelayShutdownRequest, yield* readJsonBody2(request), "relay shutdown");
|
|
13747
13806
|
yield* options.shutdown.request(body);
|
|
13748
13807
|
sendJson2(response, { stopping: true });
|
|
@@ -13764,13 +13823,13 @@ function createHttpRequestHandler(options) {
|
|
|
13764
13823
|
return;
|
|
13765
13824
|
}
|
|
13766
13825
|
if (pathname === "/extension/reload" && request.method === "POST") {
|
|
13767
|
-
run(
|
|
13826
|
+
run(gen(function* () {
|
|
13768
13827
|
sendJson2(response, yield* options.reloadExtension());
|
|
13769
13828
|
}));
|
|
13770
13829
|
return;
|
|
13771
13830
|
}
|
|
13772
13831
|
if (pathname === "/extension/revoke" && request.method === "POST") {
|
|
13773
|
-
run(
|
|
13832
|
+
run(gen(function* () {
|
|
13774
13833
|
const key = getString2(getObject2(yield* readJsonBody2(request)), "key");
|
|
13775
13834
|
if (key === undefined)
|
|
13776
13835
|
return sendJson2(response, { error: "key is required", code: "invalid-request" }, 400);
|
|
@@ -13779,7 +13838,7 @@ function createHttpRequestHandler(options) {
|
|
|
13779
13838
|
return;
|
|
13780
13839
|
}
|
|
13781
13840
|
if (pathname === "/tabs/cleanup" && request.method === "POST") {
|
|
13782
|
-
run(
|
|
13841
|
+
run(gen(function* () {
|
|
13783
13842
|
const rawIdleMinutes = getNumber2(getObject2(yield* readJsonBody2(request)), "idleMinutes");
|
|
13784
13843
|
const idleMinutes = rawIdleMinutes === undefined ? undefined : Math.max(0, rawIdleMinutes);
|
|
13785
13844
|
sendJson2(response, yield* options.cleanupTabs(idleMinutes));
|
|
@@ -13787,13 +13846,13 @@ function createHttpRequestHandler(options) {
|
|
|
13787
13846
|
return;
|
|
13788
13847
|
}
|
|
13789
13848
|
if (pathname === "/settings" && request.method === "GET") {
|
|
13790
|
-
run(
|
|
13849
|
+
run(gen(function* () {
|
|
13791
13850
|
sendJson2(response, yield* options.settings.get());
|
|
13792
13851
|
}));
|
|
13793
13852
|
return;
|
|
13794
13853
|
}
|
|
13795
13854
|
if (pathname === "/settings" && request.method === "POST") {
|
|
13796
|
-
run(
|
|
13855
|
+
run(gen(function* () {
|
|
13797
13856
|
const body = getObject2(yield* readJsonBody2(request));
|
|
13798
13857
|
sendJson2(response, yield* options.settings.set(body && "set" in body ? body.set : undefined));
|
|
13799
13858
|
}));
|
|
@@ -13877,7 +13936,7 @@ function state(tabId) {
|
|
|
13877
13936
|
function forgetTab(tabId) {
|
|
13878
13937
|
tabs.delete(tabId);
|
|
13879
13938
|
}
|
|
13880
|
-
var
|
|
13939
|
+
var sleep3 = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
13881
13940
|
var random = (min, max) => min + Math.random() * (max - min);
|
|
13882
13941
|
var noise = (scale) => (Math.random() + Math.random() + Math.random() - 1.5) * scale;
|
|
13883
13942
|
var minimumJerk = (t) => t * t * t * (10 - 15 * t + 6 * t * t);
|
|
@@ -13962,11 +14021,11 @@ async function paceMouse(tabId, tab, params, send, root) {
|
|
|
13962
14021
|
if (type === "mouseMoved" || (type === "mousePressed" || type === "mouseReleased") && tab.pointer && Math.hypot(tab.pointer.x - x, tab.pointer.y - y) >= 2)
|
|
13963
14022
|
await travel(tabId, tab, { x, y }, params, send, root);
|
|
13964
14023
|
if (type === "mousePressed") {
|
|
13965
|
-
await
|
|
14024
|
+
await sleep3(random(45, 130) - (Date.now() - tab.arrivedAt));
|
|
13966
14025
|
tab.pressedAt = Date.now();
|
|
13967
14026
|
}
|
|
13968
14027
|
if (type === "mouseReleased" && tab.pressedAt !== undefined) {
|
|
13969
|
-
await
|
|
14028
|
+
await sleep3(random(60, 140) - (Date.now() - tab.pressedAt));
|
|
13970
14029
|
delete tab.pressedAt;
|
|
13971
14030
|
}
|
|
13972
14031
|
tab.pointer = { x, y };
|
|
@@ -14043,7 +14102,7 @@ async function scrollToward(tabId, tab, params, send) {
|
|
|
14043
14102
|
break;
|
|
14044
14103
|
remaining = next;
|
|
14045
14104
|
if (Math.abs(remaining) > 2)
|
|
14046
|
-
await
|
|
14105
|
+
await sleep3(random(90, 240));
|
|
14047
14106
|
}
|
|
14048
14107
|
relayLog("input.scroll", { tabId, distance: Math.round(first), scrolled: Math.round(scrolled), ms: Date.now() - started });
|
|
14049
14108
|
}
|
|
@@ -14065,20 +14124,19 @@ async function pace(gap, sendSample) {
|
|
|
14065
14124
|
const sent = Date.now();
|
|
14066
14125
|
await Promise.race([sendSample().catch(() => {
|
|
14067
14126
|
return;
|
|
14068
|
-
}),
|
|
14069
|
-
await
|
|
14127
|
+
}), sleep3(gap)]);
|
|
14128
|
+
await sleep3(gap - (Date.now() - sent));
|
|
14070
14129
|
}
|
|
14071
14130
|
async function paceKey(tab, params) {
|
|
14072
14131
|
if (params.type !== "keyDown" && params.type !== "rawKeyDown")
|
|
14073
14132
|
return;
|
|
14074
14133
|
const gap = random(30, 110) - (Date.now() - tab.lastKeyAt);
|
|
14075
14134
|
if (gap > 0 && tab.lastKeyAt > 0 && Date.now() - tab.lastKeyAt < 1000)
|
|
14076
|
-
await
|
|
14135
|
+
await sleep3(gap);
|
|
14077
14136
|
tab.lastKeyAt = Date.now();
|
|
14078
14137
|
}
|
|
14079
14138
|
|
|
14080
14139
|
// src/cdp/protected-frames.ts
|
|
14081
|
-
import { Predicate as Predicate18 } from "effect";
|
|
14082
14140
|
var navigationIntentMethods = new Set(["Page.frameRequestedNavigation", "Page.frameScheduledNavigation", "Page.frameStartedNavigating"]);
|
|
14083
14141
|
|
|
14084
14142
|
class ProtectedFrameTracker {
|
|
@@ -14125,13 +14183,13 @@ class ProtectedFrameTracker {
|
|
|
14125
14183
|
this.release(tabId, frameId);
|
|
14126
14184
|
return { kind: "suppress" };
|
|
14127
14185
|
}
|
|
14128
|
-
if (method === "Page.frameNavigated" && frameUrl !== undefined && !isRestrictedUrl2(frameUrl) &&
|
|
14186
|
+
if (method === "Page.frameNavigated" && frameUrl !== undefined && !isRestrictedUrl2(frameUrl) && isString(parentId)) {
|
|
14129
14187
|
this.release(tabId, frameId);
|
|
14130
14188
|
return { kind: "restore", frameId, parentFrameId: parentId };
|
|
14131
14189
|
}
|
|
14132
14190
|
return { kind: "suppress" };
|
|
14133
14191
|
}
|
|
14134
|
-
const restricted = intentUrl !== undefined ? isRestrictedUrl2(intentUrl) && options.isChildFrame(frameId) : method === "Page.frameNavigated" &&
|
|
14192
|
+
const restricted = intentUrl !== undefined ? isRestrictedUrl2(intentUrl) && options.isChildFrame(frameId) : method === "Page.frameNavigated" && isString(parentId) && isRestrictedUrl2(frameUrl);
|
|
14135
14193
|
if (!restricted)
|
|
14136
14194
|
return { kind: "forward" };
|
|
14137
14195
|
this.mark(tabId, frameId);
|
|
@@ -14140,7 +14198,6 @@ class ProtectedFrameTracker {
|
|
|
14140
14198
|
}
|
|
14141
14199
|
|
|
14142
14200
|
// src/relay/cdp-dispatch.ts
|
|
14143
|
-
import { Effect as Effect25 } from "effect";
|
|
14144
14201
|
import { WebSocket as WebSocket2 } from "ws";
|
|
14145
14202
|
|
|
14146
14203
|
// src/cdp/guardrails.ts
|
|
@@ -14479,8 +14536,7 @@ function replayTargetCreated(options) {
|
|
|
14479
14536
|
|
|
14480
14537
|
// src/relay/process-guard.ts
|
|
14481
14538
|
import http4 from "node:http";
|
|
14482
|
-
|
|
14483
|
-
var installRelayProcessGuard = Effect24.acquireRelease(Effect24.sync(() => {
|
|
14539
|
+
var installRelayProcessGuard = acquireRelease(sync2(() => {
|
|
14484
14540
|
const onUncaughtException = (error, origin) => {
|
|
14485
14541
|
if (handleExecuteRealmException(error))
|
|
14486
14542
|
return;
|
|
@@ -14495,7 +14551,7 @@ var installRelayProcessGuard = Effect24.acquireRelease(Effect24.sync(() => {
|
|
|
14495
14551
|
process.on("unhandledRejection", onUnhandledRejection);
|
|
14496
14552
|
return { onUncaughtException, onUnhandledRejection };
|
|
14497
14553
|
}), (handlers) => {
|
|
14498
|
-
return
|
|
14554
|
+
return sync2(() => {
|
|
14499
14555
|
process.off("uncaughtException", handlers.onUncaughtException);
|
|
14500
14556
|
process.off("unhandledRejection", handlers.onUnhandledRejection);
|
|
14501
14557
|
});
|
|
@@ -14642,27 +14698,27 @@ function createCdpDispatcher(options) {
|
|
|
14642
14698
|
const route = cdpRouter.session(socket, sessionId);
|
|
14643
14699
|
return route && cdpRuntime.frameTreeResponse(socket, route, () => clientRoutesSession(socket, sessionId));
|
|
14644
14700
|
}
|
|
14645
|
-
const applyGhostCursorMouseEvent =
|
|
14701
|
+
const applyGhostCursorMouseEvent = fnUntraced(function* (mouseOptions) {
|
|
14646
14702
|
if (mouseOptions.message.method !== "Input.dispatchMouseEvent")
|
|
14647
14703
|
return;
|
|
14648
14704
|
yield* ghostCursor.mouseEvent(mouseOptions.tabId, mouseOptions.message.params);
|
|
14649
14705
|
});
|
|
14650
14706
|
function captureScreenshotNudged(command) {
|
|
14651
|
-
const nudges =
|
|
14707
|
+
const nudges = gen(function* () {
|
|
14652
14708
|
for (const delayMs of [600, 1000, 1500, 2000, 3000, 5000, 8000]) {
|
|
14653
|
-
yield*
|
|
14709
|
+
yield* sleep(delayMs);
|
|
14654
14710
|
relayLog("screenshot.nudge", { tabId: command.tabId, afterMs: delayMs });
|
|
14655
|
-
yield*
|
|
14711
|
+
yield* forkDetach(sendDebuggerCommand({
|
|
14656
14712
|
tabId: command.tabId,
|
|
14657
14713
|
method: "Page.captureScreenshot",
|
|
14658
14714
|
params: { format: "jpeg", quality: 1, clip: { x: 0, y: 0, width: 1, height: 1, scale: 1 } }
|
|
14659
|
-
}).pipe(
|
|
14715
|
+
}).pipe(ignore));
|
|
14660
14716
|
}
|
|
14661
|
-
return yield*
|
|
14717
|
+
return yield* never;
|
|
14662
14718
|
});
|
|
14663
|
-
return
|
|
14719
|
+
return raceFirst(sendDebuggerCommand(command), nudges);
|
|
14664
14720
|
}
|
|
14665
|
-
const navigateTopFrameToDataUrl =
|
|
14721
|
+
const navigateTopFrameToDataUrl = fnUntraced(function* (tabId, params) {
|
|
14666
14722
|
const url = getString2(params, "url")?.trim();
|
|
14667
14723
|
if (!url || !/^data:/i.test(url))
|
|
14668
14724
|
return;
|
|
@@ -14670,7 +14726,7 @@ function createCdpDispatcher(options) {
|
|
|
14670
14726
|
const requestedFrameId = getString2(params, "frameId");
|
|
14671
14727
|
if (!frameId || requestedFrameId !== undefined && requestedFrameId !== frameId || blankNavigationWaiters.has(tabId))
|
|
14672
14728
|
return;
|
|
14673
|
-
const html = yield*
|
|
14729
|
+
const html = yield* tryPromise(() => dataUrlDocument2(url)).pipe(orElseSucceed(() => {
|
|
14674
14730
|
return;
|
|
14675
14731
|
}));
|
|
14676
14732
|
if (html === undefined)
|
|
@@ -14683,32 +14739,32 @@ function createCdpDispatcher(options) {
|
|
|
14683
14739
|
committedLoaders.add(loaderId);
|
|
14684
14740
|
loaderCommitted(loaderId);
|
|
14685
14741
|
});
|
|
14686
|
-
return yield*
|
|
14742
|
+
return yield* gen(function* () {
|
|
14687
14743
|
const navigated = yield* sendDebuggerCommand({ tabId, method: "Page.navigate", params: { url: "about:blank", frameId } });
|
|
14688
14744
|
const loaderId = getString2(navigated, "loaderId");
|
|
14689
14745
|
if (getString2(navigated, "errorText") || loaderId === undefined)
|
|
14690
14746
|
return navigated;
|
|
14691
14747
|
if (!committedLoaders.has(loaderId)) {
|
|
14692
|
-
const committed = yield*
|
|
14748
|
+
const committed = yield* promise(() => new Promise((resolve) => {
|
|
14693
14749
|
loaderCommitted = (id) => {
|
|
14694
14750
|
if (id === loaderId)
|
|
14695
14751
|
resolve();
|
|
14696
14752
|
};
|
|
14697
|
-
})).pipe(
|
|
14753
|
+
})).pipe(timeoutOption(1e4));
|
|
14698
14754
|
if (committed._tag === "None")
|
|
14699
14755
|
return navigated;
|
|
14700
14756
|
}
|
|
14701
14757
|
yield* sendDebuggerCommand({ tabId, method: "Page.setDocumentContent", params: { frameId, html } });
|
|
14702
14758
|
relayLog("navigate.data", { tabId, chars: html.length });
|
|
14703
14759
|
return { frameId: getString2(navigated, "frameId") ?? frameId, loaderId };
|
|
14704
|
-
}).pipe(
|
|
14760
|
+
}).pipe(ensuring(sync2(() => {
|
|
14705
14761
|
blankNavigationWaiters.delete(tabId);
|
|
14706
14762
|
})));
|
|
14707
14763
|
});
|
|
14708
|
-
const routeCdpCommand =
|
|
14764
|
+
const routeCdpCommand = fn("Relay.routeCdpCommand")(function* (socket, message) {
|
|
14709
14765
|
const clientOpenCodeBrowserSessionId = cdpClients.sessionId(socket);
|
|
14710
14766
|
if (!shutdownControl.accepting && (!cdpClients.isSandbox(socket) || !clientOpenCodeBrowserSessionId || !sessions.hasPendingWork(clientOpenCodeBrowserSessionId))) {
|
|
14711
|
-
return yield*
|
|
14767
|
+
return yield* fail4(new Error("Relay is draining for an explicit restart"));
|
|
14712
14768
|
}
|
|
14713
14769
|
const guardMessage = guardCdpMethod({
|
|
14714
14770
|
method: message.method,
|
|
@@ -14718,7 +14774,7 @@ function createCdpDispatcher(options) {
|
|
|
14718
14774
|
pageUrls: () => cdpRouter.visibleRoots(socket).map((target) => target.targetInfo.url)
|
|
14719
14775
|
});
|
|
14720
14776
|
if (guardMessage) {
|
|
14721
|
-
return yield*
|
|
14777
|
+
return yield* fail4(new Error(guardMessage));
|
|
14722
14778
|
}
|
|
14723
14779
|
if (message.method === "Page.navigate" || message.method === "Target.createTarget") {
|
|
14724
14780
|
const tabSessionId = message.method === "Page.navigate" ? (() => {
|
|
@@ -14728,13 +14784,13 @@ function createCdpDispatcher(options) {
|
|
|
14728
14784
|
return;
|
|
14729
14785
|
}
|
|
14730
14786
|
})() : undefined;
|
|
14731
|
-
const refusal = yield*
|
|
14787
|
+
const refusal = yield* promise(() => navigationGuard.checkCommand({
|
|
14732
14788
|
method: message.method,
|
|
14733
14789
|
params: message.params,
|
|
14734
14790
|
sessionId: tabSessionId ?? clientOpenCodeBrowserSessionId
|
|
14735
14791
|
}));
|
|
14736
14792
|
if (refusal)
|
|
14737
|
-
return yield*
|
|
14793
|
+
return yield* fail4(new Error(refusal));
|
|
14738
14794
|
}
|
|
14739
14795
|
if (message.method === "Browser.getVersion") {
|
|
14740
14796
|
return {
|
|
@@ -14757,7 +14813,7 @@ function createCdpDispatcher(options) {
|
|
|
14757
14813
|
if (message.method === "Target.setAutoAttach" && !message.sessionId) {
|
|
14758
14814
|
cdpClients.setAutoAttachParams(socket, message.params);
|
|
14759
14815
|
for (const target of cdpRouter.visibleRoots(socket)) {
|
|
14760
|
-
yield*
|
|
14816
|
+
yield* ignore(sendDebuggerCommand({ tabId: target.tabId, method: "Target.setAutoAttach", params: message.params ?? {} }));
|
|
14761
14817
|
cdpClients.announce(socket, target);
|
|
14762
14818
|
}
|
|
14763
14819
|
return {};
|
|
@@ -14765,7 +14821,7 @@ function createCdpDispatcher(options) {
|
|
|
14765
14821
|
if (message.method === "Target.setAutoAttach" && message.sessionId && registry.targets.has(message.sessionId)) {
|
|
14766
14822
|
const target = cdpRouter.rootForSession(socket, message.sessionId);
|
|
14767
14823
|
if (!target) {
|
|
14768
|
-
return yield*
|
|
14824
|
+
return yield* fail4(new Error(`Target not found: ${message.sessionId}`));
|
|
14769
14825
|
}
|
|
14770
14826
|
const result = yield* sendDebuggerCommand({ tabId: target.tabId, method: "Target.setAutoAttach", params: message.params ?? {} });
|
|
14771
14827
|
replayChildTargetsForParent({ socket, parentSessionId: target.sessionId, registry, clients: cdpClients });
|
|
@@ -14791,7 +14847,7 @@ function createCdpDispatcher(options) {
|
|
|
14791
14847
|
replayChildFrameNavigation({ socket, registry, target });
|
|
14792
14848
|
return { sessionId: target.sessionId };
|
|
14793
14849
|
}
|
|
14794
|
-
return yield*
|
|
14850
|
+
return yield* fail4(new Error(`Target not found: ${targetId}`));
|
|
14795
14851
|
}
|
|
14796
14852
|
if (message.method === "Target.getTargetInfo") {
|
|
14797
14853
|
const targetId = getString2(message.params, "targetId") ?? "";
|
|
@@ -14802,7 +14858,7 @@ function createCdpDispatcher(options) {
|
|
|
14802
14858
|
if (!target) {
|
|
14803
14859
|
if (!targetId && !message.sessionId)
|
|
14804
14860
|
return {};
|
|
14805
|
-
return yield*
|
|
14861
|
+
return yield* fail4(new Error(`Target not found: ${targetId || message.sessionId || "unknown"}`));
|
|
14806
14862
|
}
|
|
14807
14863
|
return { targetInfo: target.targetInfo };
|
|
14808
14864
|
}
|
|
@@ -14839,7 +14895,7 @@ function createCdpDispatcher(options) {
|
|
|
14839
14895
|
return {};
|
|
14840
14896
|
}
|
|
14841
14897
|
const normalizedMessage = removeDefaultLightColorSchemeEmulation(message);
|
|
14842
|
-
const route = yield*
|
|
14898
|
+
const route = yield* try_({
|
|
14843
14899
|
try: () => cdpRouter.resolveCommandRoute(socket, message),
|
|
14844
14900
|
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
14845
14901
|
});
|
|
@@ -14864,28 +14920,28 @@ function createCdpDispatcher(options) {
|
|
|
14864
14920
|
};
|
|
14865
14921
|
const sessionId = message.sessionId;
|
|
14866
14922
|
const announced = sessionId !== undefined && cdpClients.hasSession(socket, sessionId);
|
|
14867
|
-
yield*
|
|
14868
|
-
applyGhostCursorMouseEvent({ tabId, message }).pipe(
|
|
14869
|
-
|
|
14923
|
+
yield* all([
|
|
14924
|
+
applyGhostCursorMouseEvent({ tabId, message }).pipe(ignore),
|
|
14925
|
+
promise(() => beforeInput(tabId, command.method, command.params, route.chromeSessionId === undefined, (method, params) => runPromise(sendDebuggerCommand({ ...command, method, params })), browsers.browserTab(tabId)?.link.humanInput).catch(() => {
|
|
14870
14926
|
return;
|
|
14871
14927
|
}))
|
|
14872
14928
|
], { concurrency: "unbounded", discard: true });
|
|
14873
|
-
const result = yield* (message.method === "Runtime.enable" && sessionId ? cdpRuntime.enable(route, command.params, () => clientRoutesSession(socket, sessionId) && (!announced || cdpClients.hasSession(socket, sessionId)), announced ? socket : undefined) : command.method === "Page.captureScreenshot" && route.chromeSessionId === undefined ? captureScreenshotNudged(command) : sendDebuggerCommand(command)).pipe(
|
|
14929
|
+
const result = yield* (message.method === "Runtime.enable" && sessionId ? cdpRuntime.enable(route, command.params, () => clientRoutesSession(socket, sessionId) && (!announced || cdpClients.hasSession(socket, sessionId)), announced ? socket : undefined) : command.method === "Page.captureScreenshot" && route.chromeSessionId === undefined ? captureScreenshotNudged(command) : sendDebuggerCommand(command)).pipe(tapError((error) => sync2(() => {
|
|
14874
14930
|
if (runtimeFailureKind(error) === "cross-extension-page")
|
|
14875
14931
|
setProtectedUi(tabId, true);
|
|
14876
|
-
})),
|
|
14932
|
+
})), tap(() => sync2(() => setProtectedUi(tabId, false))));
|
|
14877
14933
|
return scopeCdpResult({ method: message.method, result, pageUrls: () => cdpRouter.visibleRoots(socket).map((target) => target.targetInfo.url) });
|
|
14878
14934
|
});
|
|
14879
|
-
return
|
|
14935
|
+
return fnUntraced(function* (socket, raw) {
|
|
14880
14936
|
const message = parseJsonObject(raw);
|
|
14881
14937
|
if (!isCdpRequest(message)) {
|
|
14882
|
-
return yield*
|
|
14938
|
+
return yield* fail4(new Error("Invalid CDP request"));
|
|
14883
14939
|
}
|
|
14884
14940
|
const frameTreeSent = message.method === "Page.getFrameTree" ? admitFrameTreeResponse(socket, message.sessionId) : undefined;
|
|
14885
14941
|
debugLog?.(`cdp<- ${cdpClients.sessionId(socket) ?? "raw"} #${message.id} ${message.method} ${message.sessionId ?? ""}`);
|
|
14886
|
-
yield*
|
|
14942
|
+
yield* matchEffect(routeCdpCommand(socket, message), {
|
|
14887
14943
|
onFailure: (error) => {
|
|
14888
|
-
return
|
|
14944
|
+
return sync2(() => {
|
|
14889
14945
|
const runtimeEvaluation = isRuntimeEvaluationMethod(message.method);
|
|
14890
14946
|
const errorDetail = runtimeEvaluation ? runtimeFailureKind(error) : error.message;
|
|
14891
14947
|
debugLog?.(`cdp-> ${cdpClients.sessionId(socket) ?? "raw"} #${message.id} ${message.method} ERROR ${errorDetail}`);
|
|
@@ -14900,7 +14956,7 @@ function createCdpDispatcher(options) {
|
|
|
14900
14956
|
});
|
|
14901
14957
|
},
|
|
14902
14958
|
onSuccess: (result) => {
|
|
14903
|
-
return
|
|
14959
|
+
return sync2(() => {
|
|
14904
14960
|
debugLog?.(`cdp-> ${cdpClients.sessionId(socket) ?? "raw"} #${message.id} ${message.method} ok`);
|
|
14905
14961
|
const resultObject = getObject2(result);
|
|
14906
14962
|
const exceptionDetails = isRuntimeEvaluationMethod(message.method) ? getObject2(resultObject?.exceptionDetails) : undefined;
|
|
@@ -14920,48 +14976,46 @@ function createCdpDispatcher(options) {
|
|
|
14920
14976
|
}
|
|
14921
14977
|
|
|
14922
14978
|
// src/relay/commands.ts
|
|
14923
|
-
import { Effect as Effect26, Result as Result2 } from "effect";
|
|
14924
14979
|
function createExtensionCommands(options) {
|
|
14925
14980
|
const { browsers } = options;
|
|
14926
|
-
const sendToBrowser = (link, command) => link.rpc.send(command).pipe(
|
|
14927
|
-
const tabId = getNumber2(
|
|
14928
|
-
return tabId === undefined ?
|
|
14981
|
+
const sendToBrowser = (link, command) => link.rpc.send(command).pipe(map5((result2) => {
|
|
14982
|
+
const tabId = getNumber2(result2, "tabId");
|
|
14983
|
+
return tabId === undefined ? result2 : { ...result2, tabId: browsers.relayTabId(link, tabId) };
|
|
14929
14984
|
}));
|
|
14930
14985
|
const sendToExtension = (command, link) => {
|
|
14931
14986
|
const tabId = getNumber2(command.params, "tabId");
|
|
14932
14987
|
if (tabId !== undefined) {
|
|
14933
14988
|
const owner = browsers.browserTab(tabId);
|
|
14934
14989
|
if (!owner)
|
|
14935
|
-
return
|
|
14990
|
+
return fail4(new Error("browse extension is not connected"));
|
|
14936
14991
|
return sendToBrowser(owner.link, { ...command, params: { ...command.params, tabId: owner.tabId } });
|
|
14937
14992
|
}
|
|
14938
14993
|
const target = link ?? browsers.defaultLink();
|
|
14939
|
-
return target ? sendToBrowser(target, command) :
|
|
14994
|
+
return target ? sendToBrowser(target, command) : fail4(new Error("browse extension is not connected"));
|
|
14940
14995
|
};
|
|
14941
14996
|
const sendExtensionBestEffort = (command, link) => {
|
|
14942
|
-
|
|
14997
|
+
runPromise(ignore(sendToExtension(command, link))).catch(() => {});
|
|
14943
14998
|
};
|
|
14944
14999
|
const sendToEveryBrowser = (command) => {
|
|
14945
15000
|
for (const link of browsers.list())
|
|
14946
15001
|
if (link.rpc.connected)
|
|
14947
15002
|
sendExtensionBestEffort(command, link);
|
|
14948
15003
|
};
|
|
14949
|
-
const sendToEach = (command) =>
|
|
15004
|
+
const sendToEach = (command) => gen(function* () {
|
|
14950
15005
|
const links = browsers.list().filter((link) => link.rpc.connected);
|
|
14951
15006
|
if (links.length === 0)
|
|
14952
|
-
return yield*
|
|
14953
|
-
const results = yield*
|
|
14954
|
-
const succeeded = results.find(
|
|
15007
|
+
return yield* fail4(new Error("browse extension is not connected"));
|
|
15008
|
+
const results = yield* forEach(links, (link) => result(sendToBrowser(link, command)), { concurrency: "unbounded" });
|
|
15009
|
+
const succeeded = results.find(isSuccess);
|
|
14955
15010
|
if (succeeded)
|
|
14956
15011
|
return succeeded.success;
|
|
14957
|
-
const failed = results.find(
|
|
14958
|
-
return yield*
|
|
15012
|
+
const failed = results.find(isFailure);
|
|
15013
|
+
return yield* fail4(failed?.failure ?? new Error("browse extension is not connected"));
|
|
14959
15014
|
});
|
|
14960
15015
|
return { sendToExtension, sendExtensionBestEffort, sendToEveryBrowser, sendToEach };
|
|
14961
15016
|
}
|
|
14962
15017
|
|
|
14963
15018
|
// src/relay/extension-events.ts
|
|
14964
|
-
import { Effect as Effect27 } from "effect";
|
|
14965
15019
|
function createExtensionEventHandler(options) {
|
|
14966
15020
|
const {
|
|
14967
15021
|
registry,
|
|
@@ -15033,23 +15087,23 @@ function createExtensionEventHandler(options) {
|
|
|
15033
15087
|
function resumeIfWaitingForDebugger(tabId, childSessionId, params, label) {
|
|
15034
15088
|
if (params?.waitingForDebugger !== true)
|
|
15035
15089
|
return;
|
|
15036
|
-
|
|
15090
|
+
runPromise(sendDebuggerCommand({
|
|
15037
15091
|
tabId,
|
|
15038
15092
|
sessionId: childSessionId,
|
|
15039
15093
|
method: "Runtime.runIfWaitingForDebugger",
|
|
15040
15094
|
params: {}
|
|
15041
|
-
}).pipe(
|
|
15095
|
+
}).pipe(ignore)).catch((error) => {
|
|
15042
15096
|
console.error(label, error);
|
|
15043
15097
|
});
|
|
15044
15098
|
}
|
|
15045
15099
|
function releaseOpenedPageSession(tabId, parentSessionId, childSessionId, params) {
|
|
15046
15100
|
resumeIfWaitingForDebugger(tabId, childSessionId, params, "Failed to resume opened page");
|
|
15047
|
-
const release =
|
|
15101
|
+
const release = runPromise(sendDebuggerCommand({
|
|
15048
15102
|
tabId,
|
|
15049
15103
|
...parentSessionId === undefined ? {} : { sessionId: parentSessionId },
|
|
15050
15104
|
method: "Target.detachFromTarget",
|
|
15051
15105
|
params: { sessionId: childSessionId }
|
|
15052
|
-
}).pipe(
|
|
15106
|
+
}).pipe(ignore)).catch(() => {});
|
|
15053
15107
|
const releases = openedPageReleases.get(tabId) ?? new Set;
|
|
15054
15108
|
releases.add(release);
|
|
15055
15109
|
openedPageReleases.set(tabId, releases);
|
|
@@ -15069,7 +15123,7 @@ function createExtensionEventHandler(options) {
|
|
|
15069
15123
|
const autoAttachParams = Array.from(cdpClients).filter((client) => cdpClients.sessionId(client) === sessionId).map((client) => cdpClients.autoAttachParams(client)).find((params) => params !== undefined);
|
|
15070
15124
|
relayLog("tab.opened", { tabId, openerTabId, session: sessionId });
|
|
15071
15125
|
announceTab(tabId);
|
|
15072
|
-
const attach = () =>
|
|
15126
|
+
const attach = () => runPromise(rootLifecycle.attach({
|
|
15073
15127
|
tabId,
|
|
15074
15128
|
owner: "relay",
|
|
15075
15129
|
opencodeBrowserSessionId: sessionId,
|
|
@@ -15128,7 +15182,7 @@ function createExtensionEventHandler(options) {
|
|
|
15128
15182
|
return;
|
|
15129
15183
|
}
|
|
15130
15184
|
if (extensionMethod === "ready") {
|
|
15131
|
-
|
|
15185
|
+
runPromise(rootLifecycle.settle(generation)).then((settled) => {
|
|
15132
15186
|
if (browsers.ofSocket(socket) !== link)
|
|
15133
15187
|
return;
|
|
15134
15188
|
const failed = settled ? [] : rootLifecycle.failedTabIds(generation);
|
|
@@ -15178,7 +15232,7 @@ function createExtensionEventHandler(options) {
|
|
|
15178
15232
|
browsersChanged();
|
|
15179
15233
|
if (!sweptBrowsers.has(link.key)) {
|
|
15180
15234
|
sweptBrowsers.add(link.key);
|
|
15181
|
-
|
|
15235
|
+
runPromise(sessions.sweepOrphanedRelayTargets());
|
|
15182
15236
|
}
|
|
15183
15237
|
} else {
|
|
15184
15238
|
socket.close(1011, "Target inventory reconciliation failed");
|
|
@@ -15410,11 +15464,11 @@ function createExtensionEventHandler(options) {
|
|
|
15410
15464
|
if (frameId !== undefined)
|
|
15411
15465
|
mainFrameIdsByTab.set(tabId, frameId);
|
|
15412
15466
|
forgetProtectedFrames(tabId);
|
|
15413
|
-
|
|
15467
|
+
runPromise(ignore(ghostCursor.documentCreated(tabId))).catch(() => {});
|
|
15414
15468
|
contextDebugLog?.(`main-frame-navigated frame=${boundedToken(frameId)} loader=${boundedToken(getString2(frame, "loaderId"))} ${targetDiagnosticIdentity(registry, target)} ${summarizeDiagnosticUrl(frameUrl)}`);
|
|
15415
15469
|
registry.updateTargetUrl(tabId, frameUrl);
|
|
15416
15470
|
blankNavigationWaiters.get(tabId)?.(frameUrl, getString2(frame, "loaderId"));
|
|
15417
|
-
|
|
15471
|
+
runPromise(ignore(sendDebuggerCommand({ tabId, method: "Emulation.setFocusEmulationEnabled", params: { enabled: tabActivity.isFocused(tabId) } }))).catch(() => {});
|
|
15418
15472
|
}
|
|
15419
15473
|
if (frameId !== undefined && frameParentId !== undefined && params) {
|
|
15420
15474
|
registry.rememberFrameEvent({ tabId, frameId, navigated: params });
|
|
@@ -15833,7 +15887,6 @@ class ExtensionHandshake {
|
|
|
15833
15887
|
}
|
|
15834
15888
|
|
|
15835
15889
|
// src/relay/housekeeping.ts
|
|
15836
|
-
import { Clock, Effect as Effect28 } from "effect";
|
|
15837
15890
|
function createRelayHousekeeping(options) {
|
|
15838
15891
|
const {
|
|
15839
15892
|
instanceId,
|
|
@@ -15873,8 +15926,8 @@ function createRelayHousekeeping(options) {
|
|
|
15873
15926
|
const notice = updates.current;
|
|
15874
15927
|
sendToEveryBrowser({ method: "update.notice", ...notice ? { params: { summary: notice.summary, command: notice.command } } : {} });
|
|
15875
15928
|
};
|
|
15876
|
-
const releaseRelayTarget =
|
|
15877
|
-
const deadline = (yield*
|
|
15929
|
+
const releaseRelayTarget = fnUntraced(function* (targetId) {
|
|
15930
|
+
const deadline = (yield* currentTimeMillis2) + releaseTargetGraceMs;
|
|
15878
15931
|
while (true) {
|
|
15879
15932
|
const target = registry.targetsByTargetId.get(targetId);
|
|
15880
15933
|
const link = target ? browsers.browserTab(target.tabId)?.link : undefined;
|
|
@@ -15886,16 +15939,16 @@ function createRelayHousekeeping(options) {
|
|
|
15886
15939
|
}
|
|
15887
15940
|
if (link?.rpc.connected && link.rpc.protocolLegacy === false)
|
|
15888
15941
|
return;
|
|
15889
|
-
const remaining = deadline - (yield*
|
|
15942
|
+
const remaining = deadline - (yield* currentTimeMillis2);
|
|
15890
15943
|
if (remaining <= 0)
|
|
15891
15944
|
return;
|
|
15892
|
-
yield*
|
|
15945
|
+
yield* sleep(Math.min(50, remaining));
|
|
15893
15946
|
}
|
|
15894
15947
|
});
|
|
15895
15948
|
const stop = () => {
|
|
15896
15949
|
setImmediate(() => process.kill(process.pid, "SIGTERM"));
|
|
15897
15950
|
};
|
|
15898
|
-
const settleRootWork =
|
|
15951
|
+
const settleRootWork = fnUntraced(function* () {
|
|
15899
15952
|
yield* rootLifecycle.settle();
|
|
15900
15953
|
yield* sessions.beginDrain();
|
|
15901
15954
|
});
|
|
@@ -15916,16 +15969,16 @@ function createRelayHousekeeping(options) {
|
|
|
15916
15969
|
];
|
|
15917
15970
|
return busy.length ? { reason: "recordings", detail: busy.slice(0, 5).join(", ") + (busy.length > 5 ? ` and ${busy.length - 5} more` : "") } : undefined;
|
|
15918
15971
|
},
|
|
15919
|
-
release:
|
|
15972
|
+
release: gen(function* () {
|
|
15920
15973
|
relayLog("relay.restart.force", {});
|
|
15921
15974
|
for (const capture of sessions.activeNetworkCaptures())
|
|
15922
15975
|
yield* sessions.networkEnd(capture.sessionId, "the relay was restarted with --force");
|
|
15923
|
-
yield*
|
|
15924
|
-
yield*
|
|
15976
|
+
yield* promise(() => recordingRelay.cleanupAll("The relay was restarted with --force").catch(() => {}));
|
|
15977
|
+
yield* promise(() => flightRecorder.cleanupAll().catch(() => {}));
|
|
15925
15978
|
for (const client of Array.from(cdpClients))
|
|
15926
15979
|
if (!cdpClients.isSandbox(client))
|
|
15927
15980
|
client.close(1001, "Relay restarting");
|
|
15928
|
-
yield*
|
|
15981
|
+
yield* sleep(200);
|
|
15929
15982
|
}),
|
|
15930
15983
|
settle: settleRootWork(),
|
|
15931
15984
|
quiescent: () => rootLifecycle.isIdle() && sessions.isDrained(),
|
|
@@ -15947,7 +16000,7 @@ function createRelayHousekeeping(options) {
|
|
|
15947
16000
|
if (now - since < clientGoneGraceMs)
|
|
15948
16001
|
continue;
|
|
15949
16002
|
const id = session.id;
|
|
15950
|
-
|
|
16003
|
+
runPromise(gen(function* () {
|
|
15951
16004
|
if (yield* sessions.networkEnd(id, `its client (process ${pid}) exited`))
|
|
15952
16005
|
relayLog("session.capture.ended", { session: id, reason: "client gone", pid });
|
|
15953
16006
|
if (!/^mcp-[0-9a-f]{8}$/.test(id) || sessions.isExecuting(id) || sessions.hasPendingWork(id))
|
|
@@ -15960,7 +16013,7 @@ function createRelayHousekeeping(options) {
|
|
|
15960
16013
|
relayLog("session.collected", { session: id, reason: "client gone", pid, tabs: tabs.length });
|
|
15961
16014
|
clientGoneSince.delete(id);
|
|
15962
16015
|
yield* sessions.delete(id);
|
|
15963
|
-
}).pipe(
|
|
16016
|
+
}).pipe(ignore)).catch(() => {});
|
|
15964
16017
|
}
|
|
15965
16018
|
for (const id of clientGoneSince.keys())
|
|
15966
16019
|
if (!sessions.sessions.has(id))
|
|
@@ -15973,7 +16026,7 @@ function createRelayHousekeeping(options) {
|
|
|
15973
16026
|
function isTabBusy(tabId) {
|
|
15974
16027
|
return handoffs.pendingForTab(tabId) !== undefined || isTabRecorded(tabId) || Array.from(sessions.sessions.keys()).some((id) => sessions.isExecuting(id) || sessions.hasPendingWork(id) || sessions.sessions.get(id)?.sandbox.networkStatus().active);
|
|
15975
16028
|
}
|
|
15976
|
-
const detachIdleUserTarget =
|
|
16029
|
+
const detachIdleUserTarget = fnUntraced(function* (targetId) {
|
|
15977
16030
|
const target = registry.targetsByTargetId.get(targetId);
|
|
15978
16031
|
if (!target || target.owner !== "user" || isTabBusy(target.tabId))
|
|
15979
16032
|
return;
|
|
@@ -15997,13 +16050,13 @@ function createRelayHousekeeping(options) {
|
|
|
15997
16050
|
idleReapTimer = setInterval(() => {
|
|
15998
16051
|
if (!shutdown.accepting)
|
|
15999
16052
|
return;
|
|
16000
|
-
|
|
16053
|
+
runPromise(sessions.reapIdleSessions({
|
|
16001
16054
|
maxIdleMs: idleTabTtlMs,
|
|
16002
16055
|
isTargetBusy: (targetId) => {
|
|
16003
16056
|
const target = registry.targetsByTargetId.get(targetId);
|
|
16004
16057
|
return target ? isTabBusy(target.tabId) : false;
|
|
16005
16058
|
}
|
|
16006
|
-
}).pipe(
|
|
16059
|
+
}).pipe(andThen(() => forEach(tabActivity.idleUserTargets(registry.listRootTargets(), Date.now(), idleTabTtlMs), (target) => detachIdleUserTarget(target.targetInfo.targetId).pipe(ignore), { discard: true })))).catch(() => {});
|
|
16007
16060
|
}, 60000);
|
|
16008
16061
|
idleReapTimer.unref();
|
|
16009
16062
|
}
|
|
@@ -16024,7 +16077,6 @@ function createRelayHousekeeping(options) {
|
|
|
16024
16077
|
}
|
|
16025
16078
|
|
|
16026
16079
|
// src/relay/settings-sync.ts
|
|
16027
|
-
import { Effect as Effect29 } from "effect";
|
|
16028
16080
|
function createRelaySettingsSync(options) {
|
|
16029
16081
|
const { browsers, handshake, ghostCursor, ghostCursorEnv, agentWindow, sendToExtension, sendExtensionBestEffort } = options;
|
|
16030
16082
|
const withRelaySettings = (settings) => ({ ...settings, autoUpdate: storedMode() });
|
|
@@ -16038,12 +16090,12 @@ function createRelaySettingsSync(options) {
|
|
|
16038
16090
|
...human === undefined ? {} : { humanInput: { variable: "OPENCODE_BROWSER_HUMAN_INPUT", value: human ? "on" : "off" } }
|
|
16039
16091
|
};
|
|
16040
16092
|
};
|
|
16041
|
-
const extensionSettings = (method, values) =>
|
|
16093
|
+
const extensionSettings = (method, values) => gen(function* () {
|
|
16042
16094
|
if (!browsers.connected) {
|
|
16043
16095
|
const rejected = handshake.rejectedProtocol();
|
|
16044
|
-
return yield*
|
|
16096
|
+
return yield* fail4(new Error(rejected ? handshake.mismatchAdvice(rejected.version) : "The browse extension isn't connected, so its settings can't be read or changed. Ask the user to open the browser with the extension; its options page shows whether it's connected."));
|
|
16045
16097
|
}
|
|
16046
|
-
const result = yield* sendToExtension({ method, ...values ? { params: { values } } : {} }).pipe(
|
|
16098
|
+
const result = yield* sendToExtension({ method, ...values ? { params: { values } } : {} }).pipe(mapError((error) => /Invalid extension command/.test(error.message) ? new Error(`The connected browse extension is too old to read or change settings from here. Update browse (\`${installCommand()}\`) and reload the extension, or change them on its options page.`) : error));
|
|
16047
16099
|
return settingsFromExtension(result);
|
|
16048
16100
|
});
|
|
16049
16101
|
const settingOverrides = () => {
|
|
@@ -16081,9 +16133,9 @@ function createRelaySettingsSync(options) {
|
|
|
16081
16133
|
sendRelaySettings();
|
|
16082
16134
|
};
|
|
16083
16135
|
const httpSettings = {
|
|
16084
|
-
get: () => extensionSettings("settings.get").pipe(
|
|
16085
|
-
set: (input) =>
|
|
16086
|
-
const requested = yield*
|
|
16136
|
+
get: () => extensionSettings("settings.get").pipe(map5((stored) => describeSettings(withRelaySettings(stored), userSettingOverrides()))),
|
|
16137
|
+
set: (input) => gen(function* () {
|
|
16138
|
+
const requested = yield* try_({
|
|
16087
16139
|
try: () => parseSettingChanges(input),
|
|
16088
16140
|
catch: (error) => error instanceof SettingsInputError ? new HttpRouteError2({ message: error.message, status: 400, code: "invalid-request" }) : new Error(String(error))
|
|
16089
16141
|
});
|
|
@@ -16110,7 +16162,6 @@ function createRelaySettingsSync(options) {
|
|
|
16110
16162
|
}
|
|
16111
16163
|
|
|
16112
16164
|
// src/relay/tab-presentation.ts
|
|
16113
|
-
import { Effect as Effect30 } from "effect";
|
|
16114
16165
|
var attachedBadge = { text: "ON", color: "#7c3aed", title: "Detach from browse" };
|
|
16115
16166
|
var executingBadge = { text: "RUN", color: "#f59e0b", title: "browse is running a script. Click to stop it and take this tab back." };
|
|
16116
16167
|
var waitingBadge = (message) => ({ text: "WAIT", color: "#2563eb", title: `browse is waiting for you: ${message} Click here when you're done.` });
|
|
@@ -16153,10 +16204,10 @@ function createTabPresentation(options) {
|
|
|
16153
16204
|
return sendToExtension({
|
|
16154
16205
|
method: "pageStatus.set",
|
|
16155
16206
|
params: { tabId: target.tabId, status }
|
|
16156
|
-
}).pipe(
|
|
16207
|
+
}).pipe(asVoid2);
|
|
16157
16208
|
}
|
|
16158
16209
|
function sendPageStatus(target, state, statusOptions = {}) {
|
|
16159
|
-
|
|
16210
|
+
runPromise(ignore(sendPageStatusEffect(target, state, statusOptions))).catch(() => {});
|
|
16160
16211
|
}
|
|
16161
16212
|
function refreshPageStatus(tabId) {
|
|
16162
16213
|
const target = registry.tabTargets.get(tabId);
|
|
@@ -16185,7 +16236,7 @@ function createTabPresentation(options) {
|
|
|
16185
16236
|
const target = registry.tabTargets.get(tabId);
|
|
16186
16237
|
const sessionId = target ? pageStatusSessionId(target) : undefined;
|
|
16187
16238
|
const params = next === "tabs.group" && sessionId ? { tabId, sessionId } : { tabId };
|
|
16188
|
-
await
|
|
16239
|
+
await runPromise(ignore(sendToExtension({ method: next, params })));
|
|
16189
16240
|
}
|
|
16190
16241
|
})().finally(() => {
|
|
16191
16242
|
if (tabGroupingWorkers.get(tabId) !== worker)
|
|
@@ -16222,7 +16273,7 @@ function createTabPresentation(options) {
|
|
|
16222
16273
|
}
|
|
16223
16274
|
async function setActivityForTargetAcknowledged(target, state, badge, statusOptions = {}) {
|
|
16224
16275
|
sendExtensionBestEffort({ method: "action.setBadge", params: { tabId: target.tabId, ...badge } });
|
|
16225
|
-
await
|
|
16276
|
+
await runPromise(sendPageStatusEffect(target, state, statusOptions));
|
|
16226
16277
|
}
|
|
16227
16278
|
function removeActiveHandoffTab(sessionId, tabId) {
|
|
16228
16279
|
const tabIds = activeHandoffTabs.get(sessionId);
|
|
@@ -16305,7 +16356,7 @@ function createTabPresentation(options) {
|
|
|
16305
16356
|
}
|
|
16306
16357
|
activeHandoffTabs.delete(sessionId);
|
|
16307
16358
|
}
|
|
16308
|
-
return executing ?
|
|
16359
|
+
return executing ? void_3 : tabActivity.finish(sessionId);
|
|
16309
16360
|
}
|
|
16310
16361
|
function handleToolbarClick(tabId) {
|
|
16311
16362
|
const sessions = getSessions();
|
|
@@ -16329,7 +16380,7 @@ function createTabPresentation(options) {
|
|
|
16329
16380
|
sessions.stopExecution(sessionId, `The user took tab ${tabId} back (browse toolbar button) while this script ran, so it was stopped. Don't use that tab again unless the user lends it back; ask the user how to continue.`);
|
|
16330
16381
|
}
|
|
16331
16382
|
}
|
|
16332
|
-
|
|
16383
|
+
runPromise(toggleTab(tabId)).catch((error) => {
|
|
16333
16384
|
console.error("Toolbar toggle failed", error);
|
|
16334
16385
|
});
|
|
16335
16386
|
}
|
|
@@ -16373,7 +16424,6 @@ function createTabPresentation(options) {
|
|
|
16373
16424
|
|
|
16374
16425
|
// src/relay/transport-server.ts
|
|
16375
16426
|
import http5 from "node:http";
|
|
16376
|
-
import { Effect as Effect31 } from "effect";
|
|
16377
16427
|
import { WebSocketServer } from "ws";
|
|
16378
16428
|
function createRelayTransportServers(deps) {
|
|
16379
16429
|
const {
|
|
@@ -16490,7 +16540,7 @@ function createRelayTransportServers(deps) {
|
|
|
16490
16540
|
cdpClients.register(socket, opencodeBrowserSessionId, headerValue2(request.headers["opencode-browser-client-kind"]) === "sandbox" ? "sandbox" : "raw");
|
|
16491
16541
|
debugLog?.(`client+ ${opencodeBrowserSessionId ?? "raw"} total=${cdpClients.size}`);
|
|
16492
16542
|
socket.on("message", (data) => {
|
|
16493
|
-
|
|
16543
|
+
runPromise(shutdownControl.trackTransport(handleCdpMessage(socket, data.toString()))).catch((error) => {
|
|
16494
16544
|
sendCdpResponse2(socket, {
|
|
16495
16545
|
id: 0,
|
|
16496
16546
|
error: { message: error instanceof Error ? error.message : String(error) }
|
|
@@ -16505,7 +16555,7 @@ function createRelayTransportServers(deps) {
|
|
|
16505
16555
|
cdpRuntime.disconnect(socket);
|
|
16506
16556
|
const idleGeneration = cdpClients.unregister(socket);
|
|
16507
16557
|
if (idleGeneration !== undefined && !shutdownControl.stopping) {
|
|
16508
|
-
|
|
16558
|
+
runPromise(shutdownControl.trackTransport(cdpRuntime.disableIdle(() => cdpClients.isCurrentIdleGeneration(idleGeneration))).pipe(ignore)).catch((error) => {
|
|
16509
16559
|
console.error("Failed to reset idle runtime domains", error);
|
|
16510
16560
|
});
|
|
16511
16561
|
}
|
|
@@ -16515,16 +16565,15 @@ function createRelayTransportServers(deps) {
|
|
|
16515
16565
|
}
|
|
16516
16566
|
|
|
16517
16567
|
// src/cdp/root-target-lifecycle.ts
|
|
16518
|
-
import { Effect as Effect32, Exit as Exit3, Latch as Latch3, Schedule, Schema as Schema12, Scope, Semaphore as Semaphore4 } from "effect";
|
|
16519
16568
|
var DEFAULT_INVENTORY_COMMAND_TIMEOUT_MS = 5000;
|
|
16520
16569
|
|
|
16521
|
-
class InventoryTimeout extends
|
|
16522
|
-
message:
|
|
16570
|
+
class InventoryTimeout extends TaggedError2()("RootTargetLifecycle.InventoryTimeout", {
|
|
16571
|
+
message: String3
|
|
16523
16572
|
}) {
|
|
16524
16573
|
}
|
|
16525
16574
|
|
|
16526
|
-
class GenerationChanged extends
|
|
16527
|
-
message:
|
|
16575
|
+
class GenerationChanged extends TaggedError2()("RootTargetLifecycle.GenerationChanged", {
|
|
16576
|
+
message: String3
|
|
16528
16577
|
}) {
|
|
16529
16578
|
}
|
|
16530
16579
|
|
|
@@ -16532,14 +16581,14 @@ class RootTargetLifecycle {
|
|
|
16532
16581
|
options;
|
|
16533
16582
|
scope;
|
|
16534
16583
|
run;
|
|
16535
|
-
static make =
|
|
16536
|
-
const scope = yield*
|
|
16537
|
-
const run =
|
|
16538
|
-
return yield*
|
|
16584
|
+
static make = fn("RootTargetLifecycle.make")(function* (options) {
|
|
16585
|
+
const scope = yield* make6();
|
|
16586
|
+
const run = runForkWith(yield* context());
|
|
16587
|
+
return yield* acquireRelease(sync2(() => new RootTargetLifecycle(options, scope, run)), (lifecycle) => lifecycle.close());
|
|
16539
16588
|
});
|
|
16540
16589
|
tabs = new Map;
|
|
16541
16590
|
workers = new Map;
|
|
16542
|
-
idle =
|
|
16591
|
+
idle = makeUnsafe5(true);
|
|
16543
16592
|
active = 0;
|
|
16544
16593
|
closing = false;
|
|
16545
16594
|
failedTabs = new Map;
|
|
@@ -16558,8 +16607,8 @@ class RootTargetLifecycle {
|
|
|
16558
16607
|
work.revision += 1;
|
|
16559
16608
|
this.failedTabs.delete(tabId);
|
|
16560
16609
|
}
|
|
16561
|
-
attach =
|
|
16562
|
-
reconcile =
|
|
16610
|
+
attach = fn("RootTargetLifecycle.attach")((options) => this.withPermit(options.tabId, options.expectedExtensionGeneration, (transition) => this.attachUnlocked(transition, options)));
|
|
16611
|
+
reconcile = fn("RootTargetLifecycle.reconcile")((tabId, generation) => this.withPermit(tabId, generation, (transition) => this.reconcileUnlocked(transition)));
|
|
16563
16612
|
queue(options) {
|
|
16564
16613
|
if (this.closing)
|
|
16565
16614
|
return;
|
|
@@ -16585,20 +16634,20 @@ class RootTargetLifecycle {
|
|
|
16585
16634
|
work.users += 1;
|
|
16586
16635
|
const worker = {
|
|
16587
16636
|
generation,
|
|
16588
|
-
done:
|
|
16637
|
+
done: makeUnsafe5(),
|
|
16589
16638
|
attachIfMissing: options.attachIfMissing,
|
|
16590
16639
|
renewed: options.renewed === true,
|
|
16591
16640
|
pending: false,
|
|
16592
16641
|
verificationRetries: options.verificationRetries
|
|
16593
16642
|
};
|
|
16594
16643
|
this.workers.set(key, worker);
|
|
16595
|
-
const pass =
|
|
16644
|
+
const pass = gen(function* () {
|
|
16596
16645
|
worker.pending = false;
|
|
16597
16646
|
const mayAttach = worker.attachIfMissing;
|
|
16598
16647
|
worker.attachIfMissing = false;
|
|
16599
16648
|
const renewed = worker.renewed;
|
|
16600
16649
|
worker.renewed = false;
|
|
16601
|
-
yield*
|
|
16650
|
+
yield* suspend2(() => {
|
|
16602
16651
|
if (revision !== work.revision)
|
|
16603
16652
|
return new GenerationChanged({ message: "Tab detached during root reconciliation" });
|
|
16604
16653
|
return lifecycle.withPermit(options.tabId, generation, (transition) => {
|
|
@@ -16609,15 +16658,15 @@ class RootTargetLifecycle {
|
|
|
16609
16658
|
owner: root.owner,
|
|
16610
16659
|
alreadyAttached: true,
|
|
16611
16660
|
...root.opencodeBrowserSessionId ? { opencodeBrowserSessionId: root.opencodeBrowserSessionId } : {}
|
|
16612
|
-
}).pipe(
|
|
16661
|
+
}).pipe(asVoid2);
|
|
16613
16662
|
}
|
|
16614
16663
|
if (lifecycle.options.registry.routingRootTarget(options.tabId))
|
|
16615
16664
|
return lifecycle.reconcileUnlocked(transition);
|
|
16616
|
-
return mayAttach ? lifecycle.attachUnlocked(transition, { tabId: options.tabId, owner: "user", alreadyAttached: true }).pipe(
|
|
16665
|
+
return mayAttach ? lifecycle.attachUnlocked(transition, { tabId: options.tabId, owner: "user", alreadyAttached: true }).pipe(asVoid2) : void_3;
|
|
16617
16666
|
}, lifecycle.options.inventoryCommandTimeoutMs ?? DEFAULT_INVENTORY_COMMAND_TIMEOUT_MS);
|
|
16618
|
-
}).pipe(
|
|
16667
|
+
}).pipe(tapError((error) => sync2(() => lifecycle.options.reportError(options.errorMessage, error))), retry({
|
|
16619
16668
|
times: 2,
|
|
16620
|
-
schedule:
|
|
16669
|
+
schedule: spaced(0).pipe(modifyDelay(({ attempt }) => succeed6(100 * attempt))),
|
|
16621
16670
|
while: (error) => !(error instanceof GenerationChanged) && !(error instanceof InventoryTimeout) && !lifecycle.closing && generation === lifecycle.options.extension.generation(options.tabId) && revision === work.revision
|
|
16622
16671
|
}));
|
|
16623
16672
|
if (lifecycle.closing || generation !== lifecycle.options.extension.generation(options.tabId))
|
|
@@ -16628,12 +16677,12 @@ class RootTargetLifecycle {
|
|
|
16628
16677
|
return delay;
|
|
16629
16678
|
}
|
|
16630
16679
|
return worker.pending ? 0 : null;
|
|
16631
|
-
}).pipe(
|
|
16680
|
+
}).pipe(repeat({
|
|
16632
16681
|
while: (delay) => delay !== null && !lifecycle.closing,
|
|
16633
|
-
schedule:
|
|
16634
|
-
}),
|
|
16682
|
+
schedule: spaced(0).pipe(setInputType(), modifyDelay(({ input }) => succeed6(input ?? 0)))
|
|
16683
|
+
}), onExit((exit) => sync2(() => {
|
|
16635
16684
|
if (generation === lifecycle.options.extension.generation(options.tabId) && revision === work.revision) {
|
|
16636
|
-
if (
|
|
16685
|
+
if (isFailure2(exit))
|
|
16637
16686
|
lifecycle.failedTabs.set(options.tabId, generation);
|
|
16638
16687
|
else
|
|
16639
16688
|
lifecycle.failedTabs.delete(options.tabId);
|
|
@@ -16643,12 +16692,12 @@ class RootTargetLifecycle {
|
|
|
16643
16692
|
lifecycle.tabs.delete(options.tabId);
|
|
16644
16693
|
worker.done.openUnsafe();
|
|
16645
16694
|
})));
|
|
16646
|
-
this.run(pass.pipe(
|
|
16695
|
+
this.run(pass.pipe(forkIn(this.scope)));
|
|
16647
16696
|
}
|
|
16648
|
-
settle =
|
|
16697
|
+
settle = fn("RootTargetLifecycle.settle")(function* (generation) {
|
|
16649
16698
|
while (true) {
|
|
16650
16699
|
const workers = Array.from(this.workers.values()).filter((worker) => generation === undefined || worker.generation === generation);
|
|
16651
|
-
yield*
|
|
16700
|
+
yield* forEach(workers, (worker) => worker.done.await, { concurrency: "unbounded", discard: true });
|
|
16652
16701
|
if (generation === undefined)
|
|
16653
16702
|
yield* this.idle.await;
|
|
16654
16703
|
if (!Array.from(this.workers.values()).some((worker) => generation === undefined || worker.generation === generation))
|
|
@@ -16659,24 +16708,24 @@ class RootTargetLifecycle {
|
|
|
16659
16708
|
failedTabIds(generation) {
|
|
16660
16709
|
return Array.from(this.failedTabs).filter(([, failedGeneration]) => failedGeneration === generation).map(([tabId]) => tabId);
|
|
16661
16710
|
}
|
|
16662
|
-
close =
|
|
16711
|
+
close = fn("RootTargetLifecycle.close")(function* () {
|
|
16663
16712
|
this.closing = true;
|
|
16664
16713
|
yield* this.settle();
|
|
16665
|
-
yield*
|
|
16714
|
+
yield* close(this.scope, void_2);
|
|
16666
16715
|
this.tabs.clear();
|
|
16667
16716
|
});
|
|
16668
16717
|
withPermit(tabId, expectedGeneration, use, timeoutMs) {
|
|
16669
16718
|
const lifecycle = this;
|
|
16670
|
-
return
|
|
16719
|
+
return suspend2(() => {
|
|
16671
16720
|
if (lifecycle.closing)
|
|
16672
|
-
return
|
|
16721
|
+
return fail4(new Error("Relay is closing"));
|
|
16673
16722
|
const generation = expectedGeneration ?? lifecycle.options.extension.generation(tabId);
|
|
16674
16723
|
const work = lifecycle.tabWork(tabId);
|
|
16675
16724
|
const revision = work.revision;
|
|
16676
16725
|
work.users += 1;
|
|
16677
16726
|
lifecycle.active += 1;
|
|
16678
16727
|
lifecycle.idle.closeUnsafe();
|
|
16679
|
-
return work.semaphore.withPermit(
|
|
16728
|
+
return work.semaphore.withPermit(suspend2(() => {
|
|
16680
16729
|
const transition = {
|
|
16681
16730
|
tabId,
|
|
16682
16731
|
generation,
|
|
@@ -16686,8 +16735,8 @@ class RootTargetLifecycle {
|
|
|
16686
16735
|
root: lifecycle.options.registry.tabTargets.get(tabId),
|
|
16687
16736
|
staged: lifecycle.options.registry.stagedRootTarget(tabId)
|
|
16688
16737
|
};
|
|
16689
|
-
return lifecycle.check(transition).pipe(
|
|
16690
|
-
})).pipe(
|
|
16738
|
+
return lifecycle.check(transition).pipe(andThen(() => use(transition)));
|
|
16739
|
+
})).pipe(ensuring(sync2(() => {
|
|
16691
16740
|
if (--work.users === 0 && !lifecycle.options.registry.routingRootTarget(tabId))
|
|
16692
16741
|
lifecycle.tabs.delete(tabId);
|
|
16693
16742
|
if (--lifecycle.active === 0)
|
|
@@ -16696,11 +16745,11 @@ class RootTargetLifecycle {
|
|
|
16696
16745
|
});
|
|
16697
16746
|
}
|
|
16698
16747
|
tabWork(tabId) {
|
|
16699
|
-
const work = this.tabs.get(tabId) ?? { semaphore:
|
|
16748
|
+
const work = this.tabs.get(tabId) ?? { semaphore: makeUnsafe(1), revision: 0, users: 0 };
|
|
16700
16749
|
this.tabs.set(tabId, work);
|
|
16701
16750
|
return work;
|
|
16702
16751
|
}
|
|
16703
|
-
check =
|
|
16752
|
+
check = fnUntraced(function* (transition) {
|
|
16704
16753
|
const { registry, extension } = this.options;
|
|
16705
16754
|
const root = registry.tabTargets.get(transition.tabId);
|
|
16706
16755
|
const staged = registry.stagedRootTarget(transition.tabId);
|
|
@@ -16708,29 +16757,29 @@ class RootTargetLifecycle {
|
|
|
16708
16757
|
return yield* new GenerationChanged({ message: "Root target generation changed during reconciliation" });
|
|
16709
16758
|
});
|
|
16710
16759
|
step(transition, effect) {
|
|
16711
|
-
return this.check(transition).pipe(
|
|
16760
|
+
return this.check(transition).pipe(andThen(effect), catch_((error) => this.check(transition).pipe(andThen(fail4(error)))), tap(() => this.check(transition)));
|
|
16712
16761
|
}
|
|
16713
16762
|
command(transition, method, params = {}) {
|
|
16714
16763
|
const send = this.options.extension.send({ method: "debugger.sendCommand", params: { tabId: transition.tabId, method, params } });
|
|
16715
16764
|
const timeoutMs = transition.timeoutMs;
|
|
16716
|
-
return this.step(transition, timeoutMs === undefined ? send : send.pipe(
|
|
16765
|
+
return this.step(transition, timeoutMs === undefined ? send : send.pipe(timeoutOrElse({
|
|
16717
16766
|
duration: timeoutMs,
|
|
16718
|
-
orElse: () =>
|
|
16767
|
+
orElse: () => fail4(new InventoryTimeout({ message: `${method} did not answer within ${timeoutMs} ms while reconciling tab ${transition.tabId}` }))
|
|
16719
16768
|
})));
|
|
16720
16769
|
}
|
|
16721
|
-
attachUnlocked =
|
|
16770
|
+
attachUnlocked = fnUntraced(function* (transition, options) {
|
|
16722
16771
|
const { tabId } = transition;
|
|
16723
16772
|
if (!options.alreadyAttached)
|
|
16724
16773
|
yield* this.step(transition, this.options.extension.send({ method: "debugger.attach", params: { tabId } }));
|
|
16725
16774
|
yield* this.command(transition, "Page.enable");
|
|
16726
|
-
yield*
|
|
16775
|
+
yield* gen({ self: this }, function* () {
|
|
16727
16776
|
yield* this.command(transition, "Emulation.setFocusEmulationEnabled", { enabled: false });
|
|
16728
|
-
}).pipe(
|
|
16777
|
+
}).pipe(ignore);
|
|
16729
16778
|
yield* this.check(transition);
|
|
16730
16779
|
const result = yield* this.command(transition, "Target.getTargetInfo");
|
|
16731
16780
|
const reportedTargetInfo = getTargetInfo2(result.targetInfo);
|
|
16732
16781
|
if (!reportedTargetInfo)
|
|
16733
|
-
return yield*
|
|
16782
|
+
return yield* fail4(new Error("Target.getTargetInfo did not return targetInfo"));
|
|
16734
16783
|
const targetInfo = options.openerTargetId && !reportedTargetInfo.openerId ? { ...reportedTargetInfo, openerId: options.openerTargetId } : reportedTargetInfo;
|
|
16735
16784
|
const restored = options.opencodeBrowserSessionId ? undefined : this.options.sessions.persistedTargetOwner(targetInfo.targetId);
|
|
16736
16785
|
const opencodeBrowserSessionId = options.opencodeBrowserSessionId ?? restored?.sessionId;
|
|
@@ -16744,17 +16793,17 @@ class RootTargetLifecycle {
|
|
|
16744
16793
|
transition.staged = target;
|
|
16745
16794
|
return yield* this.finish(transition, target, options.autoAttachParams);
|
|
16746
16795
|
});
|
|
16747
|
-
reconcileUnlocked =
|
|
16796
|
+
reconcileUnlocked = fnUntraced(function* (transition) {
|
|
16748
16797
|
const { registry } = this.options;
|
|
16749
16798
|
const expected = registry.tabTargets.get(transition.tabId);
|
|
16750
16799
|
const staged = registry.stagedRootTarget(transition.tabId);
|
|
16751
16800
|
if (!expected && !staged)
|
|
16752
16801
|
return;
|
|
16753
|
-
const result = yield* this.command(transition, "Target.getTargetInfo").pipe(
|
|
16802
|
+
const result = yield* this.command(transition, "Target.getTargetInfo").pipe(retry({ times: 1, schedule: spaced(50), while: (error) => !(error instanceof GenerationChanged) }));
|
|
16754
16803
|
yield* this.check(transition);
|
|
16755
16804
|
const targetInfo = getTargetInfo2(result.targetInfo);
|
|
16756
16805
|
if (!targetInfo) {
|
|
16757
|
-
return yield*
|
|
16806
|
+
return yield* fail4(new Error(staged ? "Unable to verify staged root target" : "Unable to verify committed root target"));
|
|
16758
16807
|
}
|
|
16759
16808
|
if (staged?.targetInfo.targetId === targetInfo.targetId) {
|
|
16760
16809
|
yield* this.finish(transition, staged);
|
|
@@ -16772,16 +16821,16 @@ class RootTargetLifecycle {
|
|
|
16772
16821
|
...owner.opencodeBrowserSessionId ? { opencodeBrowserSessionId: owner.opencodeBrowserSessionId } : {}
|
|
16773
16822
|
});
|
|
16774
16823
|
});
|
|
16775
|
-
finish =
|
|
16824
|
+
finish = fnUntraced(function* (transition, target, autoAttachParams) {
|
|
16776
16825
|
const { registry, handoffs, sessions, clients, presentation } = this.options;
|
|
16777
16826
|
yield* this.command(transition, "Target.setAutoAttach", autoAttachParams ?? { autoAttach: true, waitForDebuggerOnStart: false, flatten: true });
|
|
16778
16827
|
const result = yield* this.command(transition, "Target.getTargetInfo");
|
|
16779
16828
|
if (getTargetInfo2(result.targetInfo)?.targetId !== target.targetInfo.targetId) {
|
|
16780
|
-
return yield*
|
|
16829
|
+
return yield* fail4(new Error(`Root target changed while preparing ${target.targetInfo.targetId}`));
|
|
16781
16830
|
}
|
|
16782
16831
|
const change = registry.commitStagedRootTarget(target.tabId, target.sessionId);
|
|
16783
16832
|
if (!change)
|
|
16784
|
-
return yield*
|
|
16833
|
+
return yield* fail4(new Error(`Staged root target changed before commit: ${target.targetInfo.targetId}`));
|
|
16785
16834
|
transition.root = change.target;
|
|
16786
16835
|
transition.staged = undefined;
|
|
16787
16836
|
if (change.kind === "replaced") {
|
|
@@ -16803,7 +16852,7 @@ class RootTargetLifecycle {
|
|
|
16803
16852
|
presentation.announceChild(change.target.sessionId, child);
|
|
16804
16853
|
}
|
|
16805
16854
|
}
|
|
16806
|
-
yield* this.step(transition, this.options.extension.send({ method: "action.setAttached", params: { tabId: target.tabId, attached: true } }).pipe(
|
|
16855
|
+
yield* this.step(transition, this.options.extension.send({ method: "action.setAttached", params: { tabId: target.tabId, attached: true } }).pipe(ignore));
|
|
16807
16856
|
return change.target;
|
|
16808
16857
|
});
|
|
16809
16858
|
}
|
|
@@ -16813,24 +16862,23 @@ import fs14 from "node:fs/promises";
|
|
|
16813
16862
|
import os6 from "node:os";
|
|
16814
16863
|
import path13 from "node:path";
|
|
16815
16864
|
import crypto9 from "node:crypto";
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
owner: Schema13.Literals(["relay", "user"])
|
|
16865
|
+
var PersistedSession = Struct({
|
|
16866
|
+
id: String3,
|
|
16867
|
+
createdAt: String3,
|
|
16868
|
+
updatedAt: String3,
|
|
16869
|
+
readOnly: Boolean3,
|
|
16870
|
+
allowedOrigins: optionalKey(ArraySchema(String3)),
|
|
16871
|
+
browser: optionalKey(String3),
|
|
16872
|
+
browserKey: optionalKey(String3),
|
|
16873
|
+
target: optionalKey(Struct({
|
|
16874
|
+
id: String3,
|
|
16875
|
+
owner: Literals(["relay", "user"])
|
|
16828
16876
|
})),
|
|
16829
|
-
clientPid:
|
|
16877
|
+
clientPid: optionalKey(Int2)
|
|
16830
16878
|
});
|
|
16831
|
-
var Catalog =
|
|
16832
|
-
version:
|
|
16833
|
-
sessions:
|
|
16879
|
+
var Catalog = Struct({
|
|
16880
|
+
version: Literal(1),
|
|
16881
|
+
sessions: ArraySchema(PersistedSession)
|
|
16834
16882
|
});
|
|
16835
16883
|
function defaultSessionCatalogPath(port, home = os6.homedir()) {
|
|
16836
16884
|
return path13.join(dataRoot(home), "relays", String(port), "sessions.json");
|
|
@@ -16851,7 +16899,7 @@ class SessionCatalog {
|
|
|
16851
16899
|
throw new Error(`Could not read browse session catalog at ${this.filePath}`, { cause: error });
|
|
16852
16900
|
}
|
|
16853
16901
|
try {
|
|
16854
|
-
const sessions =
|
|
16902
|
+
const sessions = decodeUnknownSync(Catalog)(JSON.parse(text)).sessions;
|
|
16855
16903
|
const invalid = sessions.find((session) => !isValidSessionId2(session.id));
|
|
16856
16904
|
if (invalid)
|
|
16857
16905
|
throw new Error(`Invalid persisted session id: ${invalid.id}`);
|
|
@@ -17045,13 +17093,11 @@ class SessionNavigationGuard {
|
|
|
17045
17093
|
}
|
|
17046
17094
|
|
|
17047
17095
|
// src/cdp/tab-activity.ts
|
|
17048
|
-
import { Effect as Effect33, Semaphore as Semaphore5 } from "effect";
|
|
17049
|
-
|
|
17050
17096
|
class TabActivity {
|
|
17051
17097
|
setFocus;
|
|
17052
17098
|
lastUsed = new Map;
|
|
17053
17099
|
focusedBy = new Map;
|
|
17054
|
-
focusLock =
|
|
17100
|
+
focusLock = makeUnsafe(1);
|
|
17055
17101
|
constructor(setFocus) {
|
|
17056
17102
|
this.setFocus = setFocus;
|
|
17057
17103
|
}
|
|
@@ -17066,18 +17112,18 @@ class TabActivity {
|
|
|
17066
17112
|
return this.focusedBy.has(tabId);
|
|
17067
17113
|
}
|
|
17068
17114
|
focus(tabId, sessionId) {
|
|
17069
|
-
return this.focusLock.withPermit(
|
|
17115
|
+
return this.focusLock.withPermit(gen({ self: this }, function* () {
|
|
17070
17116
|
const owners = this.focusedBy.get(tabId) ?? new Set;
|
|
17071
17117
|
if (owners.has(sessionId))
|
|
17072
17118
|
return;
|
|
17073
17119
|
if (owners.size === 0)
|
|
17074
|
-
yield* this.setFocus(tabId, true).pipe(
|
|
17120
|
+
yield* this.setFocus(tabId, true).pipe(ignore);
|
|
17075
17121
|
owners.add(sessionId);
|
|
17076
17122
|
this.focusedBy.set(tabId, owners);
|
|
17077
17123
|
}));
|
|
17078
17124
|
}
|
|
17079
17125
|
finish(sessionId) {
|
|
17080
|
-
return this.focusLock.withPermit(
|
|
17126
|
+
return this.focusLock.withPermit(gen({ self: this }, function* () {
|
|
17081
17127
|
for (const [tabId, owners] of this.focusedBy) {
|
|
17082
17128
|
if (!owners.delete(sessionId))
|
|
17083
17129
|
continue;
|
|
@@ -17085,7 +17131,7 @@ class TabActivity {
|
|
|
17085
17131
|
if (owners.size > 0)
|
|
17086
17132
|
continue;
|
|
17087
17133
|
this.focusedBy.delete(tabId);
|
|
17088
|
-
yield* this.setFocus(tabId, false).pipe(
|
|
17134
|
+
yield* this.setFocus(tabId, false).pipe(ignore);
|
|
17089
17135
|
}
|
|
17090
17136
|
}));
|
|
17091
17137
|
}
|
|
@@ -17095,11 +17141,11 @@ class TabActivity {
|
|
|
17095
17141
|
}
|
|
17096
17142
|
|
|
17097
17143
|
// src/relay.ts
|
|
17098
|
-
var startRelay =
|
|
17144
|
+
var startRelay = fn("Relay.start")(function* (options = {}) {
|
|
17099
17145
|
yield* installRelayProcessGuard;
|
|
17100
|
-
return yield*
|
|
17146
|
+
return yield* acquireRelease(makeRelay(options), (server) => server.close());
|
|
17101
17147
|
});
|
|
17102
|
-
var makeRelay =
|
|
17148
|
+
var makeRelay = fnUntraced(function* (options = {}) {
|
|
17103
17149
|
const listenHost = relayHostname();
|
|
17104
17150
|
const host = "127.0.0.1";
|
|
17105
17151
|
const port = options.port ?? defaultPort2;
|
|
@@ -17112,7 +17158,7 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17112
17158
|
const sessionCatalog = options.sessionCatalogPath === null ? undefined : new SessionCatalog(options.sessionCatalogPath ?? defaultSessionCatalogPath(port));
|
|
17113
17159
|
let catalogWritesEnabled = false;
|
|
17114
17160
|
const registry = new TargetRegistry;
|
|
17115
|
-
const authToken = yield*
|
|
17161
|
+
const authToken = yield* try_({
|
|
17116
17162
|
try: () => {
|
|
17117
17163
|
ensurePrivateDirectory();
|
|
17118
17164
|
return relayToken();
|
|
@@ -17121,10 +17167,10 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17121
17167
|
});
|
|
17122
17168
|
const browsers = new BrowserLinks;
|
|
17123
17169
|
const { sendToExtension, sendExtensionBestEffort, sendToEveryBrowser, sendToEach } = createExtensionCommands({ browsers });
|
|
17124
|
-
const debugEnabled = yield*
|
|
17170
|
+
const debugEnabled = yield* Boolean2("OPENCODE_BROWSER_DEBUG").pipe(withDefault(false));
|
|
17125
17171
|
const debugLog = debugEnabled ? (line) => console.error(`[bc ${new Date().toISOString().slice(11, 23)}] ${line}`) : undefined;
|
|
17126
17172
|
const contextDebugLog = debugLog ? (line) => debugLog(`[bc:ctx] ${line}`) : undefined;
|
|
17127
|
-
const sendDebuggerCommand =
|
|
17173
|
+
const sendDebuggerCommand = fnUntraced(function* (commandOptions) {
|
|
17128
17174
|
const finishDisable = commandOptions.method === "Runtime.disable" ? cdpRuntime.beginDisable(commandOptions.tabId, commandOptions.sessionId) : undefined;
|
|
17129
17175
|
return yield* sendToExtension({
|
|
17130
17176
|
method: "debugger.sendCommand",
|
|
@@ -17134,7 +17180,7 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17134
17180
|
params: commandOptions.params,
|
|
17135
17181
|
...commandOptions.sessionId === undefined ? {} : { sessionId: commandOptions.sessionId }
|
|
17136
17182
|
}
|
|
17137
|
-
}).pipe(
|
|
17183
|
+
}).pipe(ensuring(sync2(() => {
|
|
17138
17184
|
finishDisable?.();
|
|
17139
17185
|
})));
|
|
17140
17186
|
});
|
|
@@ -17154,22 +17200,22 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17154
17200
|
const ownersAcrossReconnect = new Map;
|
|
17155
17201
|
const sweptBrowsers = new Set;
|
|
17156
17202
|
const recordingRelay = new RecordingRelay({
|
|
17157
|
-
sendToExtension: (command) =>
|
|
17158
|
-
sendDebuggerCommand: (command) =>
|
|
17203
|
+
sendToExtension: (command) => runPromise(sendToExtension(command)),
|
|
17204
|
+
sendDebuggerCommand: (command) => runPromise(sendDebuggerCommand(command)),
|
|
17159
17205
|
isExtensionConnected: () => browsers.connected,
|
|
17160
17206
|
isTabUnavailable: (tabId) => flightRecorder.isActiveTab(tabId),
|
|
17161
17207
|
onTabRecordingChanged: (tabId) => recordingChanged(tabId)
|
|
17162
17208
|
});
|
|
17163
17209
|
const flightRecorder = new FlightRecorderRelay({
|
|
17164
|
-
sendDebuggerCommand: (command) =>
|
|
17210
|
+
sendDebuggerCommand: (command) => runPromise(sendDebuggerCommand(command)),
|
|
17165
17211
|
isExtensionConnected: () => browsers.connected,
|
|
17166
17212
|
isTabRecording: (tabId) => recordingRelay.isRecordingTab(tabId),
|
|
17167
17213
|
onTabRecordingChanged: (tabId) => recordingChanged(tabId)
|
|
17168
17214
|
});
|
|
17169
17215
|
const isTabRecorded = (tabId) => recordingRelay.isRecordingTab(tabId) || flightRecorder.isActiveTab(tabId);
|
|
17170
|
-
const ghostCursorEnv =
|
|
17216
|
+
const ghostCursorEnv = getOrUndefined(yield* option(Boolean2("OPENCODE_BROWSER_GHOST_CURSOR")));
|
|
17171
17217
|
const ghostCursorAlways = (tabId) => ghostCursorEnv ?? browsers.browserTab(tabId)?.link.cursor ?? false;
|
|
17172
|
-
const agentWindowSetting =
|
|
17218
|
+
const agentWindowSetting = getOrUndefined(yield* option(String2("OPENCODE_BROWSER_AGENT_WINDOW")))?.trim().toLowerCase();
|
|
17173
17219
|
const agentWindow = agentWindowSetting === "current" || agentWindowSetting === "separate" ? agentWindowSetting : undefined;
|
|
17174
17220
|
const ghostCursor = new GhostCursorTabs({
|
|
17175
17221
|
send: sendDebuggerCommand,
|
|
@@ -17204,7 +17250,7 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17204
17250
|
const target = registry.tabTargets.get(tabId);
|
|
17205
17251
|
if (!pending || !target)
|
|
17206
17252
|
return;
|
|
17207
|
-
await
|
|
17253
|
+
await runPromise(ignore(presentation.sendPageStatusEffect(target, "waiting", {
|
|
17208
17254
|
sessionId: pending.sessionId,
|
|
17209
17255
|
message: pending.message,
|
|
17210
17256
|
handoffId: pending.id
|
|
@@ -17242,9 +17288,9 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17242
17288
|
browsersChanged,
|
|
17243
17289
|
cdpClientCount: () => cdpClients.size
|
|
17244
17290
|
});
|
|
17245
|
-
const executeTimeoutMs = yield*
|
|
17246
|
-
const keepaliveIntervalMs = yield*
|
|
17247
|
-
const idleTabTtlMs = yield*
|
|
17291
|
+
const executeTimeoutMs = yield* Int("OPENCODE_BROWSER_EXECUTE_TIMEOUT_MS").pipe(withDefault(defaultExecuteTimeoutMs));
|
|
17292
|
+
const keepaliveIntervalMs = yield* Int("OPENCODE_BROWSER_KEEPALIVE_MS").pipe(withDefault(20000));
|
|
17293
|
+
const idleTabTtlMs = yield* Int("OPENCODE_BROWSER_IDLE_TAB_TTL_MS").pipe(withDefault(15 * 60000));
|
|
17248
17294
|
const journalBaseDir = defaultJournalBaseDir();
|
|
17249
17295
|
const sessions = new OpenCodeBrowserSessions(endpointUrl, (id, onDefaultTargetChange) => new ExecuteSandbox({
|
|
17250
17296
|
endpointUrl,
|
|
@@ -17312,8 +17358,8 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17312
17358
|
allowedOrigins: (sessionId) => sessions.allowedOrigins(sessionId),
|
|
17313
17359
|
isExecuting: (sessionId) => sessions.isExecuting(sessionId),
|
|
17314
17360
|
mainFrameId: (tabId) => mainFrameIdsByTab.get(tabId),
|
|
17315
|
-
send: (tabId, method, params) =>
|
|
17316
|
-
closeTab: (tabId) =>
|
|
17361
|
+
send: (tabId, method, params) => runPromise(sendDebuggerCommand({ tabId, method, params })),
|
|
17362
|
+
closeTab: (tabId) => runPromise(gen(function* () {
|
|
17317
17363
|
const target = registry.tabTargets.get(tabId);
|
|
17318
17364
|
if (target?.owner === "user")
|
|
17319
17365
|
return;
|
|
@@ -17358,12 +17404,12 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17358
17404
|
sendToExtension,
|
|
17359
17405
|
sendExtensionBestEffort
|
|
17360
17406
|
});
|
|
17361
|
-
const managed = yield*
|
|
17362
|
-
const restartTimeoutMs = yield*
|
|
17363
|
-
const clientGoneGraceMs = yield*
|
|
17364
|
-
const restartRequestId = yield*
|
|
17407
|
+
const managed = yield* Boolean2("OPENCODE_BROWSER_MANAGED_RELAY").pipe(withDefault(false));
|
|
17408
|
+
const restartTimeoutMs = yield* Int("OPENCODE_BROWSER_RESTART_TIMEOUT_MS").pipe(withDefault(1e4));
|
|
17409
|
+
const clientGoneGraceMs = yield* Int("OPENCODE_BROWSER_CLIENT_GONE_GRACE_MS").pipe(withDefault(60000));
|
|
17410
|
+
const restartRequestId = yield* option(String2("OPENCODE_BROWSER_RESTART_REQUEST_ID"));
|
|
17365
17411
|
const lifecycleLogPath = path14.join(path14.dirname(defaultSessionCatalogPath(port)), "lifecycle.jsonl");
|
|
17366
|
-
const audit = (event) =>
|
|
17412
|
+
const audit = (event) => try_(() => appendRelayLifecycleEvent(lifecycleLogPath, event));
|
|
17367
17413
|
function setProtectedUi(tabId, protectedUi) {
|
|
17368
17414
|
const changed = registry.markRootTargetProtectedUi(tabId, protectedUi);
|
|
17369
17415
|
if (!changed)
|
|
@@ -17371,10 +17417,10 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17371
17417
|
contextDebugLog?.(`protected-ui blocked=${protectedUi} ${targetDiagnosticIdentity(registry, changed)}`);
|
|
17372
17418
|
sessions.markTargetProtectedUi(changed.targetInfo.targetId, protectedUi);
|
|
17373
17419
|
}
|
|
17374
|
-
const detachTab =
|
|
17420
|
+
const detachTab = fnUntraced(function* (tabId) {
|
|
17375
17421
|
yield* sendToExtension({ method: "debugger.detach", params: { tabId } });
|
|
17376
17422
|
detachTargetState(tabId);
|
|
17377
|
-
yield*
|
|
17423
|
+
yield* ignore(sendToExtension({ method: "action.setAttached", params: { tabId, attached: false } }));
|
|
17378
17424
|
});
|
|
17379
17425
|
const housekeeping = createRelayHousekeeping({
|
|
17380
17426
|
instanceId: browserId,
|
|
@@ -17402,28 +17448,28 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17402
17448
|
audit
|
|
17403
17449
|
});
|
|
17404
17450
|
const { shutdown: shutdownControl, updates, sendUpdateNotice, releaseRelayTarget, browserIdle, startTimers, stopTimers } = housekeeping;
|
|
17405
|
-
const toggleTab =
|
|
17451
|
+
const toggleTab = fnUntraced(function* (tabId) {
|
|
17406
17452
|
if (registry.tabTargets.has(tabId))
|
|
17407
17453
|
return yield* detachTab(tabId);
|
|
17408
17454
|
announceTab(tabId);
|
|
17409
17455
|
yield* rootLifecycle.attach({ tabId, owner: "user" });
|
|
17410
17456
|
});
|
|
17411
|
-
const sessionBrowser = (sessionId) =>
|
|
17457
|
+
const sessionBrowser = (sessionId) => suspend2(() => {
|
|
17412
17458
|
const session = sessionId && sessions.sessions.get(sessionId);
|
|
17413
17459
|
if (session) {
|
|
17414
17460
|
const link = handshake.resolveSessionBrowser(undefined, session);
|
|
17415
|
-
return "error" in link ?
|
|
17461
|
+
return "error" in link ? fail4(new Error(link.error)) : succeed6(link);
|
|
17416
17462
|
}
|
|
17417
17463
|
const existing = sessionId ? registry.listRootTargets().find((target) => target.opencodeBrowserSessionId === sessionId && target.owner === "relay") : undefined;
|
|
17418
17464
|
const link = (existing && browsers.browserTab(existing.tabId)?.link) ?? browsers.defaultLink();
|
|
17419
|
-
return link ?
|
|
17465
|
+
return link ? succeed6(link) : fail4(new Error("browse extension is not connected"));
|
|
17420
17466
|
});
|
|
17421
|
-
const createAndAttachTab =
|
|
17467
|
+
const createAndAttachTab = fnUntraced(function* (createOptions) {
|
|
17422
17468
|
const link = yield* sessionBrowser(createOptions.opencodeBrowserSessionId);
|
|
17423
17469
|
const result = yield* sendToExtension({ method: "tabs.create", params: { url: createOptions.url, active: createOptions.active, ...agentWindow ? { window: agentWindow } : {} } }, link);
|
|
17424
17470
|
const tabId = getNumber2(result, "tabId");
|
|
17425
17471
|
if (!tabId)
|
|
17426
|
-
return yield*
|
|
17472
|
+
return yield* fail4(new Error("tabs.create did not return a tabId"));
|
|
17427
17473
|
announceTab(tabId);
|
|
17428
17474
|
return yield* rootLifecycle.attach({
|
|
17429
17475
|
tabId,
|
|
@@ -17432,7 +17478,7 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17432
17478
|
...createOptions.autoAttachParams ? { autoAttachParams: createOptions.autoAttachParams } : {}
|
|
17433
17479
|
});
|
|
17434
17480
|
});
|
|
17435
|
-
const closeTargetByTargetId =
|
|
17481
|
+
const closeTargetByTargetId = fnUntraced(function* (targetId) {
|
|
17436
17482
|
const target = registry.targetsByTargetId.get(targetId);
|
|
17437
17483
|
if (!target)
|
|
17438
17484
|
return;
|
|
@@ -17615,20 +17661,20 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17615
17661
|
handleCdpMessage,
|
|
17616
17662
|
debugLog
|
|
17617
17663
|
});
|
|
17618
|
-
const cleanup =
|
|
17664
|
+
const cleanup = fnUntraced(function* () {
|
|
17619
17665
|
stopTimers();
|
|
17620
17666
|
autoUpdater?.stop();
|
|
17621
|
-
yield* shutdownControl.close().pipe(
|
|
17667
|
+
yield* shutdownControl.close().pipe(ignore);
|
|
17622
17668
|
yield* sessions.closeAll();
|
|
17623
17669
|
yield* rootLifecycle.close();
|
|
17624
17670
|
if (managed) {
|
|
17625
|
-
yield* audit(RelayLifecycleEvent.cases.Closed.make({ instanceId: browserId })).pipe(
|
|
17671
|
+
yield* audit(RelayLifecycleEvent.cases.Closed.make({ instanceId: browserId })).pipe(catch_(() => sync2(() => console.error("Failed to record relay shutdown lifecycle event"))));
|
|
17626
17672
|
}
|
|
17627
17673
|
handoffs.cancelAll();
|
|
17628
17674
|
for (const link of browsers.list())
|
|
17629
17675
|
link.rpc.rejectPending(new Error("Relay closed"));
|
|
17630
|
-
yield*
|
|
17631
|
-
yield*
|
|
17676
|
+
yield* tryPromise(() => recordingRelay.cleanupAll("Relay closed")).pipe(ignore);
|
|
17677
|
+
yield* tryPromise(() => flightRecorder.cleanupAll()).pipe(ignore);
|
|
17632
17678
|
for (const socket of cdpClients)
|
|
17633
17679
|
socket.close();
|
|
17634
17680
|
for (const link of browsers.list())
|
|
@@ -17637,15 +17683,15 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17637
17683
|
yield* closeHttpServer2(httpServer).pipe(logCloseError2("Failed to close http server"));
|
|
17638
17684
|
});
|
|
17639
17685
|
const close = cleanup();
|
|
17640
|
-
yield*
|
|
17641
|
-
return
|
|
17686
|
+
yield* catch_(listenHttpServer2({ server: httpServer, host: listenHost, port }), (error) => {
|
|
17687
|
+
return gen(function* () {
|
|
17642
17688
|
yield* cleanup();
|
|
17643
|
-
return yield*
|
|
17689
|
+
return yield* fail4(error);
|
|
17644
17690
|
});
|
|
17645
17691
|
});
|
|
17646
|
-
yield*
|
|
17647
|
-
yield*
|
|
17648
|
-
const restoredSessions = yield*
|
|
17692
|
+
yield* sync2(() => pruneStaleExecuteFilesDirectories());
|
|
17693
|
+
yield* catch_(gen(function* () {
|
|
17694
|
+
const restoredSessions = yield* tryPromise({
|
|
17649
17695
|
try: async () => {
|
|
17650
17696
|
if (!sessionCatalog)
|
|
17651
17697
|
return [];
|
|
@@ -17660,7 +17706,7 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17660
17706
|
},
|
|
17661
17707
|
catch: (cause) => cause instanceof Error ? cause : new Error("Load browse session catalog", { cause })
|
|
17662
17708
|
});
|
|
17663
|
-
yield*
|
|
17709
|
+
yield* try_({
|
|
17664
17710
|
try: () => sessions.restore(restoredSessions),
|
|
17665
17711
|
catch: (cause) => cause instanceof Error ? cause : new Error("Restore browse sessions", { cause })
|
|
17666
17712
|
});
|
|
@@ -17669,7 +17715,7 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17669
17715
|
yield* audit(yield* RelayLifecycleEvent.cases.Ready.makeEffect({
|
|
17670
17716
|
instanceId: browserId,
|
|
17671
17717
|
buildId: opencodeBrowserBuildId2,
|
|
17672
|
-
...
|
|
17718
|
+
...isSome(restartRequestId) ? { restartRequestId: restartRequestId.value } : {}
|
|
17673
17719
|
}));
|
|
17674
17720
|
}
|
|
17675
17721
|
relayReady = true;
|
|
@@ -17696,9 +17742,9 @@ var makeRelay = Effect34.fnUntraced(function* (options = {}) {
|
|
|
17696
17742
|
}, (error) => relayLog("extension.dev_update_failed", { error: error.message }));
|
|
17697
17743
|
}
|
|
17698
17744
|
relayLog("relay.ready", { url: endpointUrl, version: opencodeBrowserVersion2, build: opencodeBrowserBuildId2, pid: process.pid, managed });
|
|
17699
|
-
}), (error) =>
|
|
17745
|
+
}), (error) => gen(function* () {
|
|
17700
17746
|
yield* cleanup();
|
|
17701
|
-
return yield*
|
|
17747
|
+
return yield* fail4(error);
|
|
17702
17748
|
}));
|
|
17703
17749
|
return {
|
|
17704
17750
|
url: endpointUrl,
|