@rynx-ai/daemon 0.1.11-beta.4 → 0.1.11-beta.41
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/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +0 -12
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +3 -1
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +3 -3
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +24 -18
- package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
- package/bundled-plugins/plugins/lark/dist/runtime.js +6043 -6016
- package/bundled-plugins/plugins/lark/dist/settings.js +6003 -5985
- package/bundled-plugins/plugins/lark/package.json +1 -1
- package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
- package/bundled-plugins/plugins/lark/ui/index.html +2 -2
- package/dist/agent-file.js +0 -1
- package/dist/browser-artifact-management.js +4 -1
- package/dist/chrome-for-testing-http.d.ts +9 -0
- package/dist/chrome-for-testing-http.js +58 -0
- package/dist/chrome-for-testing-release-resolver.js +13 -6
- package/dist/chrome-for-testing-runtime.js +4 -3
- package/dist/chrome-for-testing-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +99 -17
- package/dist/control-deps.d.ts +6 -2
- package/dist/control-deps.js +87 -4
- package/dist/control-link-store.js +4 -3
- package/dist/daemon-owner.js +4 -7
- package/dist/daemon-server.js +42 -17
- package/dist/db.d.ts +2 -2
- package/dist/db.js +157 -21
- package/dist/headless-browser-host.js +397 -41
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.js +4 -1
- package/dist/migrations/cleanup-legacy-sessions.js +4 -3
- package/dist/plugin-cli.d.ts +10 -0
- package/dist/plugin-cli.js +19 -2
- package/dist/plugin-host-rpc.d.ts +48 -9
- package/dist/plugin-host-rpc.js +593 -81
- package/dist/plugin-installer.js +3 -2
- package/dist/plugin-management-service.js +0 -2
- package/dist/plugin-marketplace.js +2 -1
- package/dist/plugin-store.js +2 -4
- package/dist/plugin-supervisor.js +0 -4
- package/dist/pm2.d.ts +2 -0
- package/dist/pm2.js +6 -0
- package/dist/remote-runtime-access-store.d.ts +2 -2
- package/dist/remote-runtime-access-store.js +6 -9
- package/dist/remote-runtime-connection-manager.js +25 -42
- package/dist/remote-runtime-target-store.d.ts +2 -2
- package/dist/remote-runtime-target-store.js +6 -6
- package/dist/session-emulator-binding-store.js +4 -6
- package/dist/session-fork-store.d.ts +1 -0
- package/dist/session-fork-store.js +85 -30
- package/dist/session-launch-operations.js +6 -5
- package/dist/session-log-store.d.ts +1 -0
- package/dist/session-log-store.js +46 -22
- package/dist/session-meta-store.d.ts +5 -0
- package/dist/session-meta-store.js +25 -2
- package/dist/session-portal-grant-store.d.ts +55 -0
- package/dist/session-portal-grant-store.js +160 -0
- package/dist/session-resource-store.d.ts +24 -6
- package/dist/session-resource-store.js +387 -69
- package/dist/session-timeline-projection-store.d.ts +24 -0
- package/dist/session-timeline-projection-store.js +53 -0
- package/dist/setup-service.d.ts +1 -0
- package/dist/setup-service.js +1 -0
- package/dist/skills-catalog.d.ts +11 -2
- package/dist/skills-catalog.js +317 -10
- package/dist/sqlite.d.ts +22 -0
- package/dist/sqlite.js +57 -0
- package/dist/stdio-epipe-guard.d.ts +1 -1
- package/dist/stdio-epipe-guard.js +1 -1
- package/dist/system-dependencies.d.ts +22 -0
- package/dist/system-dependencies.js +79 -0
- package/package.json +17 -12
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
- /package/bundled-plugins/plugins/lark/ui/assets/{index-D9N-AkLX.js → index-D8Amoe6a.js} +0 -0
|
@@ -34,6 +34,22 @@ const DEVTOOLS_BROWSER_PATH = /^\/devtools\/browser\/[A-Za-z0-9._~-]+$/;
|
|
|
34
34
|
const DEVTOOLS_TARGET_ID_PATTERN = /^[A-Za-z0-9._~-]+$/;
|
|
35
35
|
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
36
36
|
const CONTROL_CHARACTER_GLOBAL_PATTERN = /[\u0000-\u001f\u007f]/g;
|
|
37
|
+
const NETWORK_AUTO_ATTACH_FILTER = [
|
|
38
|
+
{ type: "page" },
|
|
39
|
+
{ type: "iframe" },
|
|
40
|
+
{ type: "worker" },
|
|
41
|
+
{ type: "service_worker" },
|
|
42
|
+
{ type: "shared_worker" },
|
|
43
|
+
{ exclude: true },
|
|
44
|
+
];
|
|
45
|
+
const NETWORK_AUTO_ATTACH_PARAMS = {
|
|
46
|
+
autoAttach: true,
|
|
47
|
+
// Pause new targets until Fetch interception is installed. This is required
|
|
48
|
+
// for Session Headers to cover a popup's very first navigation request.
|
|
49
|
+
waitForDebuggerOnStart: true,
|
|
50
|
+
flatten: true,
|
|
51
|
+
filter: NETWORK_AUTO_ATTACH_FILTER,
|
|
52
|
+
};
|
|
37
53
|
export function createHeadlessBrowserHost(options = {}) {
|
|
38
54
|
return new HeadlessBrowserHost(options);
|
|
39
55
|
}
|
|
@@ -95,7 +111,7 @@ class HeadlessBrowserHost {
|
|
|
95
111
|
throw error;
|
|
96
112
|
}
|
|
97
113
|
const ownerToken = randomBytes(32).toString("hex");
|
|
98
|
-
const args = chromeArguments(profileDir, ownerToken
|
|
114
|
+
const args = chromeArguments(profileDir, ownerToken);
|
|
99
115
|
let child;
|
|
100
116
|
try {
|
|
101
117
|
child = spawn(build.executablePath, args, {
|
|
@@ -198,6 +214,7 @@ class HeadlessBrowserHandle {
|
|
|
198
214
|
closingTargetIds = new Set();
|
|
199
215
|
pageSessions = new Map();
|
|
200
216
|
targetBySession = new Map();
|
|
217
|
+
networkTargetTypeBySession = new Map();
|
|
201
218
|
surfaceSources = new Map();
|
|
202
219
|
loadingTargets = new Set();
|
|
203
220
|
activeTargetId = null;
|
|
@@ -205,8 +222,11 @@ class HeadlessBrowserHandle {
|
|
|
205
222
|
closing = false;
|
|
206
223
|
closePromise;
|
|
207
224
|
operations = Promise.resolve();
|
|
225
|
+
networkOperations = Promise.resolve();
|
|
226
|
+
requestHeaders;
|
|
208
227
|
constructor(host) {
|
|
209
228
|
this.host = host;
|
|
229
|
+
this.requestHeaders = cloneRequestHeadersConfig(host.input.requestHeaders ?? { enabled: false, headers: [] });
|
|
210
230
|
this.events = this.eventQueue;
|
|
211
231
|
this.host.connection.setEventHandler((event) => this.onCdpEvent(event));
|
|
212
232
|
this.host.connection.setDisconnectHandler((reason) => this.markUnavailable(reason));
|
|
@@ -223,21 +243,17 @@ class HeadlessBrowserHandle {
|
|
|
223
243
|
}
|
|
224
244
|
async initialize() {
|
|
225
245
|
await this.host.connection.command("Target.setDiscoverTargets", { discover: true });
|
|
226
|
-
await this.
|
|
246
|
+
await this.host.connection.command("Target.setAutoAttach", NETWORK_AUTO_ATTACH_PARAMS);
|
|
247
|
+
await this.createFreshStartupPage();
|
|
227
248
|
await this.refreshTargets();
|
|
228
249
|
const requestedUrl = this.host.input.url;
|
|
229
250
|
if (requestedUrl) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
await this.host.connection.command("Target.activateTarget", {
|
|
237
|
-
targetId: requestedTarget.targetId,
|
|
238
|
-
});
|
|
239
|
-
this.activeTargetId = requestedTarget.targetId;
|
|
240
|
-
}
|
|
251
|
+
const targetId = this.activeTargetId;
|
|
252
|
+
if (!targetId)
|
|
253
|
+
throw new Error("Headless Browser startup Page is unavailable");
|
|
254
|
+
const pageSessionId = await this.pageSession(targetId);
|
|
255
|
+
await this.host.connection.command("Page.navigate", { url: requestedUrl }, pageSessionId);
|
|
256
|
+
await this.resetPageNavigationHistory(targetId);
|
|
241
257
|
}
|
|
242
258
|
}
|
|
243
259
|
async getCdpEndpoint() {
|
|
@@ -336,10 +352,17 @@ class HeadlessBrowserHandle {
|
|
|
336
352
|
if (this.targets.size >= RUNTIME_BROWSER_MAX_PAGES) {
|
|
337
353
|
throw new SessionBrowserHostError("capacity", "Headless Browser Page capacity was reached");
|
|
338
354
|
}
|
|
339
|
-
const result = await this.host.connection.command("Target.createTarget", {
|
|
355
|
+
const result = await this.host.connection.command("Target.createTarget", {
|
|
356
|
+
url: "about:blank",
|
|
357
|
+
});
|
|
340
358
|
const targetId = requiredCdpId(result.targetId, "Target.createTarget targetId");
|
|
341
359
|
this.activeTargetId = targetId;
|
|
342
360
|
await this.refreshTargets();
|
|
361
|
+
if (url !== "about:blank") {
|
|
362
|
+
const pageSessionId = await this.pageSession(targetId);
|
|
363
|
+
await this.host.connection.command("Page.navigate", { url }, pageSessionId);
|
|
364
|
+
await this.resetPageNavigationHistory(targetId);
|
|
365
|
+
}
|
|
343
366
|
this.eventQueue.changed(this.browserGeneration);
|
|
344
367
|
}
|
|
345
368
|
catch (error) {
|
|
@@ -395,20 +418,46 @@ class HeadlessBrowserHandle {
|
|
|
395
418
|
reload(hostPageId) {
|
|
396
419
|
return this.pageCommand(hostPageId, "Page.reload", {});
|
|
397
420
|
}
|
|
421
|
+
setRequestHeaders(config) {
|
|
422
|
+
return this.exclusive(async () => {
|
|
423
|
+
this.ensureAvailable();
|
|
424
|
+
await this.networkExclusive(async () => {
|
|
425
|
+
// Every target is permanently Fetch-intercepted before it is resumed.
|
|
426
|
+
// Swapping this immutable snapshot is therefore the single commit
|
|
427
|
+
// point for all current and future targets; already-paused/in-flight
|
|
428
|
+
// requests keep the snapshot they started with.
|
|
429
|
+
this.requestHeaders = cloneRequestHeadersConfig(config);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
}
|
|
398
433
|
close() {
|
|
399
434
|
if (this.closePromise)
|
|
400
435
|
return this.closePromise;
|
|
401
436
|
this.closing = true;
|
|
402
437
|
this.closePromise = (async () => {
|
|
403
438
|
await Promise.allSettled([...this.surfaceSources.values()].map((source) => source.close()));
|
|
404
|
-
this.host.connection.close();
|
|
405
439
|
let terminated = false;
|
|
406
440
|
try {
|
|
407
|
-
|
|
441
|
+
let exited = false;
|
|
442
|
+
if (this.host.connection.connected) {
|
|
443
|
+
const closeCommand = this.host.connection.command("Browser.close")
|
|
444
|
+
.catch(() => undefined);
|
|
445
|
+
exited = await Promise.race([
|
|
446
|
+
this.host.exit.then(() => true),
|
|
447
|
+
delay(this.host.terminationGraceMs).then(() => false),
|
|
448
|
+
]);
|
|
449
|
+
if (!exited)
|
|
450
|
+
this.host.connection.close();
|
|
451
|
+
await closeCommand;
|
|
452
|
+
}
|
|
453
|
+
if (!exited) {
|
|
454
|
+
await terminateProcessGroup(this.host.child, this.host.exit, this.host.terminationGraceMs);
|
|
455
|
+
}
|
|
408
456
|
removeMatchingProcessLease(this.host.profileDir, this.host.ownerToken);
|
|
409
457
|
terminated = true;
|
|
410
458
|
}
|
|
411
459
|
finally {
|
|
460
|
+
this.host.connection.close();
|
|
412
461
|
if (terminated)
|
|
413
462
|
this.host.release();
|
|
414
463
|
this.eventQueue.end();
|
|
@@ -434,6 +483,24 @@ class HeadlessBrowserHandle {
|
|
|
434
483
|
}
|
|
435
484
|
});
|
|
436
485
|
}
|
|
486
|
+
async resetPageNavigationHistory(targetId) {
|
|
487
|
+
let lastError;
|
|
488
|
+
for (let attempt = 0; attempt < PAGE_SNAPSHOT_TRANSITION_ATTEMPTS; attempt += 1) {
|
|
489
|
+
const sessionId = await this.pageSession(targetId);
|
|
490
|
+
try {
|
|
491
|
+
await this.host.connection.command("Page.resetNavigationHistory", {}, sessionId);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
if (!isMissingTargetError(error) && !isTransientPageTransitionError(error))
|
|
496
|
+
throw error;
|
|
497
|
+
lastError = error;
|
|
498
|
+
this.dropAttachedSession(sessionId);
|
|
499
|
+
await delay(PAGE_SNAPSHOT_TRANSITION_POLL_MS);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
throw lastError ?? new Error("Headless Browser could not reset Page navigation history");
|
|
503
|
+
}
|
|
437
504
|
pageCommand(hostPageId, method, params) {
|
|
438
505
|
return this.exclusive(async () => {
|
|
439
506
|
this.ensureAvailable();
|
|
@@ -451,11 +518,19 @@ class HeadlessBrowserHandle {
|
|
|
451
518
|
try {
|
|
452
519
|
const targets = await this.readPageTargets();
|
|
453
520
|
const next = new Map();
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
521
|
+
const orderedTargets = [
|
|
522
|
+
...targets.filter((target) => this.targets.has(target.targetId)),
|
|
523
|
+
...targets.filter((target) => !this.targets.has(target.targetId)),
|
|
524
|
+
];
|
|
525
|
+
for (const target of orderedTargets) {
|
|
526
|
+
if (this.closingTargetIds.has(target.targetId))
|
|
527
|
+
continue;
|
|
528
|
+
if (next.size < RUNTIME_BROWSER_MAX_PAGES) {
|
|
529
|
+
next.set(target.targetId, target);
|
|
530
|
+
continue;
|
|
457
531
|
}
|
|
458
|
-
|
|
532
|
+
this.closingTargetIds.add(target.targetId);
|
|
533
|
+
await this.networkExclusive(() => this.closeRejectedTarget(undefined, target, false));
|
|
459
534
|
}
|
|
460
535
|
for (const targetId of this.targets.keys()) {
|
|
461
536
|
if (!next.has(targetId))
|
|
@@ -491,13 +566,23 @@ class HeadlessBrowserHandle {
|
|
|
491
566
|
throw this.mapOperationError(error);
|
|
492
567
|
}
|
|
493
568
|
}
|
|
494
|
-
async
|
|
569
|
+
async createFreshStartupPage() {
|
|
495
570
|
const initialPages = await this.readPageTargets();
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
571
|
+
for (const page of initialPages) {
|
|
572
|
+
try {
|
|
573
|
+
await this.host.connection.command("Target.closeTarget", { targetId: page.targetId });
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
if (!isMissingPageError(error))
|
|
577
|
+
throw error;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const created = await this.host.connection.command("Target.createTarget", {
|
|
581
|
+
url: "about:blank",
|
|
582
|
+
});
|
|
499
583
|
const freshTargetId = requiredCdpId(created.targetId, "Target.createTarget targetId");
|
|
500
584
|
this.activeTargetId = freshTargetId;
|
|
585
|
+
await this.host.connection.command("Target.activateTarget", { targetId: freshTargetId });
|
|
501
586
|
for (let attempt = 0; attempt < STARTUP_PAGE_CLEANUP_ATTEMPTS; attempt += 1) {
|
|
502
587
|
const pages = await this.readPageTargets();
|
|
503
588
|
if (!pages.some((page) => page.targetId === freshTargetId)) {
|
|
@@ -581,11 +666,21 @@ class HeadlessBrowserHandle {
|
|
|
581
666
|
canGoForward: false,
|
|
582
667
|
};
|
|
583
668
|
}
|
|
584
|
-
async pageSession(targetId) {
|
|
669
|
+
async pageSession(targetId, retryMissing = true) {
|
|
585
670
|
this.requireTarget(targetId);
|
|
586
671
|
const existing = this.pageSessions.get(targetId);
|
|
587
|
-
if (existing)
|
|
588
|
-
|
|
672
|
+
if (existing) {
|
|
673
|
+
try {
|
|
674
|
+
return await existing;
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
if (!retryMissing || !isMissingPageError(error))
|
|
678
|
+
throw error;
|
|
679
|
+
if (this.pageSessions.get(targetId) === existing)
|
|
680
|
+
this.pageSessions.delete(targetId);
|
|
681
|
+
return this.pageSession(targetId, false);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
589
684
|
const pending = (async () => {
|
|
590
685
|
const result = await this.host.connection.command("Target.attachToTarget", {
|
|
591
686
|
targetId,
|
|
@@ -593,7 +688,8 @@ class HeadlessBrowserHandle {
|
|
|
593
688
|
});
|
|
594
689
|
const sessionId = requiredCdpId(result.sessionId, "Target.attachToTarget sessionId");
|
|
595
690
|
this.targetBySession.set(sessionId, targetId);
|
|
596
|
-
|
|
691
|
+
this.networkTargetTypeBySession.set(sessionId, "page");
|
|
692
|
+
await this.networkExclusive(() => this.initializeAttachedTarget(sessionId, this.requireTarget(targetId), false));
|
|
597
693
|
return sessionId;
|
|
598
694
|
})();
|
|
599
695
|
this.pageSessions.set(targetId, pending);
|
|
@@ -608,10 +704,88 @@ class HeadlessBrowserHandle {
|
|
|
608
704
|
onCdpEvent(event) {
|
|
609
705
|
if (this.closing || this.unavailableReason)
|
|
610
706
|
return;
|
|
707
|
+
if (event.method === "Fetch.requestPaused" && event.sessionId) {
|
|
708
|
+
void this.continuePausedRequest(event.sessionId, event.params).catch((error) => {
|
|
709
|
+
if (isMissingFetchRequestError(error))
|
|
710
|
+
return;
|
|
711
|
+
if (isMissingTargetError(error) || isTransientPageTransitionError(error)) {
|
|
712
|
+
this.dropAttachedSession(event.sessionId);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
this.markUnavailable("Headless Browser could not continue an intercepted request");
|
|
716
|
+
});
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (event.method === "Target.attachedToTarget") {
|
|
720
|
+
const sessionId = optionalCdpId(event.params.sessionId);
|
|
721
|
+
const target = parseTargetInfo(event.params.targetInfo);
|
|
722
|
+
if (!sessionId)
|
|
723
|
+
return;
|
|
724
|
+
const waitingForDebugger = event.params.waitingForDebugger === true;
|
|
725
|
+
this.targetBySession.set(sessionId, target.targetId);
|
|
726
|
+
this.networkTargetTypeBySession.set(sessionId, target.type);
|
|
727
|
+
if (target.type === "page") {
|
|
728
|
+
if (this.closingTargetIds.has(target.targetId) ||
|
|
729
|
+
(!this.targets.has(target.targetId) &&
|
|
730
|
+
this.targets.size >= RUNTIME_BROWSER_MAX_PAGES)) {
|
|
731
|
+
this.closingTargetIds.add(target.targetId);
|
|
732
|
+
void this.networkExclusive(() => this.closeRejectedTarget(sessionId, target, waitingForDebugger)).catch(() => undefined);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
this.targets.set(target.targetId, target);
|
|
736
|
+
let pending;
|
|
737
|
+
pending = this.networkExclusive(() => this.initializeAttachedTarget(sessionId, target, waitingForDebugger))
|
|
738
|
+
.then(async () => {
|
|
739
|
+
const selected = this.pageSessions.get(target.targetId);
|
|
740
|
+
if (selected && selected !== pending) {
|
|
741
|
+
let selectedSessionId;
|
|
742
|
+
try {
|
|
743
|
+
selectedSessionId = await selected;
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
// The next Page operation will establish a replacement.
|
|
747
|
+
}
|
|
748
|
+
if (selectedSessionId !== sessionId &&
|
|
749
|
+
this.targetBySession.get(sessionId) === target.targetId) {
|
|
750
|
+
await this.networkExclusive(() => this.detachAuxiliaryPageSession(sessionId));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return sessionId;
|
|
754
|
+
})
|
|
755
|
+
.catch((error) => {
|
|
756
|
+
if (this.pageSessions.get(target.targetId) === pending) {
|
|
757
|
+
this.pageSessions.delete(target.targetId);
|
|
758
|
+
}
|
|
759
|
+
throw error;
|
|
760
|
+
});
|
|
761
|
+
void pending.catch(() => undefined);
|
|
762
|
+
// Chrome may deliver a waitForDebugger auto-attach session while a
|
|
763
|
+
// manual Target.attachToTarget for the same Page is still pending.
|
|
764
|
+
// Every physical session must be initialized and resumed even when a
|
|
765
|
+
// different session already owns semantic Page commands.
|
|
766
|
+
if (!this.pageSessions.has(target.targetId)) {
|
|
767
|
+
this.pageSessions.set(target.targetId, pending);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
void this.networkExclusive(() => this.initializeAttachedTarget(sessionId, target, waitingForDebugger)).catch(() => undefined);
|
|
772
|
+
}
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
611
775
|
if (event.method === "Target.targetCreated" || event.method === "Target.targetInfoChanged") {
|
|
612
776
|
const target = parseTargetInfo(event.params.targetInfo);
|
|
613
777
|
if (target.type !== "page")
|
|
614
778
|
return;
|
|
779
|
+
if (event.method === "Target.targetCreated" &&
|
|
780
|
+
!this.targets.has(target.targetId) &&
|
|
781
|
+
this.targets.size >= RUNTIME_BROWSER_MAX_PAGES) {
|
|
782
|
+
this.closingTargetIds.add(target.targetId);
|
|
783
|
+
void this.networkExclusive(() => this.closeRejectedTarget(undefined, target, false))
|
|
784
|
+
.catch(() => undefined);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (this.closingTargetIds.has(target.targetId))
|
|
788
|
+
return;
|
|
615
789
|
this.targets.set(target.targetId, target);
|
|
616
790
|
if (event.method === "Target.targetCreated")
|
|
617
791
|
this.activeTargetId = target.targetId;
|
|
@@ -633,19 +807,28 @@ class HeadlessBrowserHandle {
|
|
|
633
807
|
if (event.method === "Target.targetCrashed") {
|
|
634
808
|
const targetId = optionalCdpId(event.params.targetId);
|
|
635
809
|
if (targetId && this.targets.has(targetId)) {
|
|
636
|
-
this.
|
|
810
|
+
const target = this.targets.get(targetId);
|
|
811
|
+
this.closingTargetIds.add(targetId);
|
|
812
|
+
this.dropTarget(targetId);
|
|
813
|
+
void this.networkExclusive(() => this.closeRejectedTarget(undefined, target, false))
|
|
814
|
+
.catch(() => undefined);
|
|
815
|
+
this.eventQueue.changed(this.browserGeneration);
|
|
637
816
|
}
|
|
638
817
|
return;
|
|
639
818
|
}
|
|
640
819
|
if (event.method === "Target.detachedFromTarget") {
|
|
641
820
|
const sessionId = optionalCdpId(event.params.sessionId);
|
|
642
821
|
if (sessionId)
|
|
643
|
-
this.
|
|
822
|
+
this.dropAttachedSession(sessionId);
|
|
644
823
|
return;
|
|
645
824
|
}
|
|
646
825
|
const targetId = event.sessionId ? this.targetBySession.get(event.sessionId) : undefined;
|
|
647
826
|
if (!targetId)
|
|
648
827
|
return;
|
|
828
|
+
if (event.method === "Page.javascriptDialogOpening") {
|
|
829
|
+
void this.dismissJavaScriptDialog(event.sessionId, targetId);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
649
832
|
if (event.method === "Inspector.detached") {
|
|
650
833
|
this.dropPageSession(event.sessionId);
|
|
651
834
|
return;
|
|
@@ -683,22 +866,162 @@ class HeadlessBrowserHandle {
|
|
|
683
866
|
this.loadingTargets.delete(targetId);
|
|
684
867
|
const session = this.pageSessions.get(targetId);
|
|
685
868
|
this.pageSessions.delete(targetId);
|
|
869
|
+
for (const [sessionId, attachedTargetId] of this.targetBySession) {
|
|
870
|
+
if (attachedTargetId === targetId)
|
|
871
|
+
this.dropAttachedSession(sessionId);
|
|
872
|
+
}
|
|
873
|
+
// A manual attach can resolve after targetDestroyed. Clean the late
|
|
874
|
+
// session as well instead of retaining it in the Network update set.
|
|
686
875
|
if (session)
|
|
687
|
-
void session.then((id) => this.
|
|
876
|
+
void session.then((id) => this.dropAttachedSession(id), () => undefined);
|
|
688
877
|
}
|
|
689
878
|
dropPageSession(sessionId) {
|
|
690
879
|
const targetId = this.targetBySession.get(sessionId);
|
|
880
|
+
this.targetBySession.delete(sessionId);
|
|
881
|
+
this.networkTargetTypeBySession.delete(sessionId);
|
|
691
882
|
if (!targetId)
|
|
692
883
|
return;
|
|
693
|
-
this.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
884
|
+
const selected = this.pageSessions.get(targetId);
|
|
885
|
+
if (selected) {
|
|
886
|
+
void selected.then((selectedSessionId) => {
|
|
887
|
+
if (selectedSessionId === sessionId &&
|
|
888
|
+
this.pageSessions.get(targetId) === selected) {
|
|
889
|
+
this.pageSessions.delete(targetId);
|
|
890
|
+
const surface = this.surfaceSources.get(targetId);
|
|
891
|
+
if (surface) {
|
|
892
|
+
this.surfaceSources.delete(targetId);
|
|
893
|
+
surface.closeAfterSourceLoss();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}, () => undefined);
|
|
699
897
|
}
|
|
700
898
|
this.eventQueue.changed(this.browserGeneration);
|
|
701
899
|
}
|
|
900
|
+
dropAttachedSession(sessionId) {
|
|
901
|
+
if (this.networkTargetTypeBySession.get(sessionId) === "page") {
|
|
902
|
+
this.dropPageSession(sessionId);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
this.networkTargetTypeBySession.delete(sessionId);
|
|
906
|
+
this.targetBySession.delete(sessionId);
|
|
907
|
+
}
|
|
908
|
+
async initializeAttachedTarget(sessionId, target, waitingForDebugger) {
|
|
909
|
+
try {
|
|
910
|
+
// Fetch.enable succeeds while a waitForDebugger target is paused and must
|
|
911
|
+
// precede its first request. Network.enable does not: current Chrome for
|
|
912
|
+
// Testing builds leave that command pending until the target is resumed.
|
|
913
|
+
await this.host.connection.command("Fetch.enable", {
|
|
914
|
+
patterns: [{ urlPattern: "*", requestStage: "Request" }],
|
|
915
|
+
}, sessionId);
|
|
916
|
+
if (target.type === "page" || target.type === "iframe") {
|
|
917
|
+
await this.host.connection.command("Target.setAutoAttach", NETWORK_AUTO_ATTACH_PARAMS, sessionId);
|
|
918
|
+
}
|
|
919
|
+
if (target.type === "page") {
|
|
920
|
+
await this.host.connection.command("Page.enable", {}, sessionId);
|
|
921
|
+
}
|
|
922
|
+
if (waitingForDebugger) {
|
|
923
|
+
await this.host.connection.command("Runtime.runIfWaitingForDebugger", {}, sessionId);
|
|
924
|
+
}
|
|
925
|
+
await this.host.connection.command("Network.enable", {}, sessionId);
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
if (isMissingTargetError(error) || isTransientPageTransitionError(error)) {
|
|
929
|
+
this.dropAttachedSession(sessionId);
|
|
930
|
+
throw missingPage(error);
|
|
931
|
+
}
|
|
932
|
+
if (!this.host.connection.connected || error instanceof CdpDisconnectedError) {
|
|
933
|
+
this.markUnavailable("The native CDP observer disconnected");
|
|
934
|
+
throw this.mapOperationError(error);
|
|
935
|
+
}
|
|
936
|
+
await this.closeRejectedTarget(sessionId, target, waitingForDebugger);
|
|
937
|
+
if (target.type === "page")
|
|
938
|
+
throw missingPage(error);
|
|
939
|
+
throw unavailable("Headless Browser isolated a network target that failed setup", error);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
async closeRejectedTarget(sessionId, target, waitingForDebugger) {
|
|
943
|
+
if (target.type === "page") {
|
|
944
|
+
this.closingTargetIds.add(target.targetId);
|
|
945
|
+
this.dropTarget(target.targetId);
|
|
946
|
+
}
|
|
947
|
+
else if (sessionId) {
|
|
948
|
+
this.dropAttachedSession(sessionId);
|
|
949
|
+
}
|
|
950
|
+
let closed = false;
|
|
951
|
+
try {
|
|
952
|
+
if (this.host.connection.connected) {
|
|
953
|
+
const result = await this.host.connection.command("Target.closeTarget", {
|
|
954
|
+
targetId: target.targetId,
|
|
955
|
+
});
|
|
956
|
+
closed = result.success === true;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
if (!isMissingTargetError(error) && !isTransientPageTransitionError(error)) {
|
|
961
|
+
if (!this.host.connection.connected || error instanceof CdpDisconnectedError) {
|
|
962
|
+
this.markUnavailable("The native CDP observer disconnected");
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (!closed && waitingForDebugger && sessionId && this.host.connection.connected) {
|
|
967
|
+
await this.host.connection.command("Runtime.runIfWaitingForDebugger", {}, sessionId).catch(() => undefined);
|
|
968
|
+
}
|
|
969
|
+
if (!closed && sessionId && this.host.connection.connected) {
|
|
970
|
+
await this.host.connection.command("Target.detachFromTarget", { sessionId }).catch(() => undefined);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
async detachAuxiliaryPageSession(sessionId) {
|
|
974
|
+
try {
|
|
975
|
+
await this.host.connection.command("Target.detachFromTarget", { sessionId });
|
|
976
|
+
}
|
|
977
|
+
catch (error) {
|
|
978
|
+
if (!isMissingTargetError(error) && !isTransientPageTransitionError(error)) {
|
|
979
|
+
this.markUnavailable("Headless Browser could not fence a duplicate Page session");
|
|
980
|
+
throw this.mapOperationError(error);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
finally {
|
|
984
|
+
this.dropAttachedSession(sessionId);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
async dismissJavaScriptDialog(sessionId, targetId) {
|
|
988
|
+
try {
|
|
989
|
+
// Rynx has no native dialog presentation surface. Dismiss every modal
|
|
990
|
+
// deterministically so alert/confirm/prompt/beforeunload cannot block all
|
|
991
|
+
// later CDP commands for this Page.
|
|
992
|
+
await this.host.connection.command("Page.handleJavaScriptDialog", { accept: false }, sessionId);
|
|
993
|
+
}
|
|
994
|
+
catch (error) {
|
|
995
|
+
if (isMissingTargetError(error) || isTransientPageTransitionError(error)) {
|
|
996
|
+
this.dropAttachedSession(sessionId);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (!this.host.connection.connected || error instanceof CdpDisconnectedError) {
|
|
1000
|
+
this.markUnavailable("The native CDP observer disconnected");
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const target = this.targets.get(targetId);
|
|
1004
|
+
if (target)
|
|
1005
|
+
await this.closeRejectedTarget(sessionId, target, false);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async continuePausedRequest(sessionId, params) {
|
|
1009
|
+
const requestId = requiredCdpId(params.requestId, "Fetch.requestPaused requestId");
|
|
1010
|
+
const config = this.requestHeaders;
|
|
1011
|
+
const effective = config.enabled && config.headers.length > 0;
|
|
1012
|
+
const request = effective && isRecord(params.request) ? params.request : {};
|
|
1013
|
+
// This is a product-level baseline, not a security boundary: a caller
|
|
1014
|
+
// holding the privileged native CDP endpoint can alter interception.
|
|
1015
|
+
await this.host.connection.command("Fetch.continueRequest", {
|
|
1016
|
+
requestId,
|
|
1017
|
+
// Fetch treats `headers` as a full override. Omitting it is the only
|
|
1018
|
+
// true pass-through path for disabled/empty policy and preserves
|
|
1019
|
+
// Browser-managed Cookie, auth, redirect, and duplicate headers.
|
|
1020
|
+
...(effective
|
|
1021
|
+
? { headers: mergePausedRequestHeaders(pausedRequestHeaders(request.headers), config) }
|
|
1022
|
+
: {}),
|
|
1023
|
+
}, sessionId);
|
|
1024
|
+
}
|
|
702
1025
|
ensureAvailable() {
|
|
703
1026
|
if (this.unavailableReason || this.closing || !this.host.connection.connected) {
|
|
704
1027
|
throw new SessionBrowserHostError("unavailable", "Headless Browser is unavailable");
|
|
@@ -729,6 +1052,11 @@ class HeadlessBrowserHandle {
|
|
|
729
1052
|
this.operations = result.then(() => undefined, () => undefined);
|
|
730
1053
|
return result;
|
|
731
1054
|
}
|
|
1055
|
+
networkExclusive(operation) {
|
|
1056
|
+
const result = this.networkOperations.then(operation, operation);
|
|
1057
|
+
this.networkOperations = result.then(() => undefined, () => undefined);
|
|
1058
|
+
return result;
|
|
1059
|
+
}
|
|
732
1060
|
}
|
|
733
1061
|
class HeadlessBrowserSurfaceSource {
|
|
734
1062
|
source;
|
|
@@ -1476,7 +1804,7 @@ class CdpConnection {
|
|
|
1476
1804
|
this.pending.delete(id);
|
|
1477
1805
|
rejectCommand(new CdpCommandTimeoutError(`Native CDP command ${method} timed out`));
|
|
1478
1806
|
}, this.commandTimeoutMs);
|
|
1479
|
-
this.pending.set(id, { resolve: resolveCommand, reject: rejectCommand, timer });
|
|
1807
|
+
this.pending.set(id, { method, resolve: resolveCommand, reject: rejectCommand, timer });
|
|
1480
1808
|
try {
|
|
1481
1809
|
this.socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }));
|
|
1482
1810
|
}
|
|
@@ -1518,7 +1846,9 @@ class CdpConnection {
|
|
|
1518
1846
|
this.pending.delete(value.id);
|
|
1519
1847
|
clearTimeout(pending.timer);
|
|
1520
1848
|
if (isRecord(value.error)) {
|
|
1521
|
-
pending.reject(new CdpProtocolError(typeof value.error.code === "number" ? value.error.code : -1,
|
|
1849
|
+
pending.reject(new CdpProtocolError(typeof value.error.code === "number" ? value.error.code : -1, `Native CDP command ${pending.method} failed: ${typeof value.error.message === "string"
|
|
1850
|
+
? value.error.message
|
|
1851
|
+
: "Native CDP command failed"}`));
|
|
1522
1852
|
}
|
|
1523
1853
|
else {
|
|
1524
1854
|
pending.resolve(value.result ?? {});
|
|
@@ -1887,7 +2217,7 @@ function readSmallRegularJson(path, maxBytes, label) {
|
|
|
1887
2217
|
closeSync(descriptor);
|
|
1888
2218
|
}
|
|
1889
2219
|
}
|
|
1890
|
-
function chromeArguments(profileDir, ownerToken
|
|
2220
|
+
function chromeArguments(profileDir, ownerToken) {
|
|
1891
2221
|
return [
|
|
1892
2222
|
"--headless=new",
|
|
1893
2223
|
"--use-mock-keychain",
|
|
@@ -1905,7 +2235,29 @@ function chromeArguments(profileDir, ownerToken, url) {
|
|
|
1905
2235
|
"--metrics-recording-only",
|
|
1906
2236
|
"--no-report-upload",
|
|
1907
2237
|
"--window-size=1280,800",
|
|
1908
|
-
|
|
2238
|
+
"--no-startup-window",
|
|
2239
|
+
];
|
|
2240
|
+
}
|
|
2241
|
+
function cloneRequestHeadersConfig(config) {
|
|
2242
|
+
return {
|
|
2243
|
+
enabled: config.enabled,
|
|
2244
|
+
headers: config.headers.map((header) => ({ ...header })),
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
function pausedRequestHeaders(value) {
|
|
2248
|
+
if (!isRecord(value))
|
|
2249
|
+
return [];
|
|
2250
|
+
return Object.entries(value).flatMap(([name, headerValue]) => typeof headerValue === "string" ? [{ name, value: headerValue }] : []);
|
|
2251
|
+
}
|
|
2252
|
+
function mergePausedRequestHeaders(original, config) {
|
|
2253
|
+
if (!config.enabled)
|
|
2254
|
+
return original.map((header) => ({ ...header }));
|
|
2255
|
+
const overriddenNames = new Set(config.headers.map((header) => header.name.toLowerCase()));
|
|
2256
|
+
return [
|
|
2257
|
+
...original
|
|
2258
|
+
.filter((header) => !overriddenNames.has(header.name.toLowerCase()))
|
|
2259
|
+
.map((header) => ({ ...header })),
|
|
2260
|
+
...config.headers.map((header) => ({ ...header })),
|
|
1909
2261
|
];
|
|
1910
2262
|
}
|
|
1911
2263
|
function validateActiveExecutable(build) {
|
|
@@ -2118,6 +2470,10 @@ function isTransientPageTransitionError(error) {
|
|
|
2118
2470
|
/cannot find (?:an )?execution context/i.test(error.message) ||
|
|
2119
2471
|
/cannot find context with specified id/i.test(error.message));
|
|
2120
2472
|
}
|
|
2473
|
+
function isMissingFetchRequestError(error) {
|
|
2474
|
+
return error instanceof CdpProtocolError && (/invalid (?:interception|request)id/i.test(error.message) ||
|
|
2475
|
+
/no resource with given identifier/i.test(error.message));
|
|
2476
|
+
}
|
|
2121
2477
|
function isMissingPageError(error) {
|
|
2122
2478
|
return (error instanceof SessionBrowserHostError && error.code === "page_not_found") || isMissingTargetError(error);
|
|
2123
2479
|
}
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export { formatControlUrl, isPm2DaemonOnline, statusDaemon, stopDaemon, streamLogs, } from "./pm2.js";
|
|
1
|
+
export { formatControlUrl, isDaemonStartRequired, isPm2DaemonOnline, statusDaemon, stopDaemon, streamLogs, } from "./pm2.js";
|
|
2
2
|
export { resolveDaemonEntry } from "./entry-path.js";
|
|
3
3
|
export interface StandaloneDaemonLaunchContext {
|
|
4
4
|
builtinSkillsDir: string;
|
|
5
5
|
cliVersion: string;
|
|
6
6
|
productVersion: string;
|
|
7
|
+
tmuxBin?: string;
|
|
7
8
|
}
|
|
8
9
|
export declare function startDaemon(options: {
|
|
9
10
|
force: boolean;
|
package/dist/lifecycle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { startDaemon as startPm2Daemon, } from "./pm2.js";
|
|
2
|
-
export { formatControlUrl, isPm2DaemonOnline, statusDaemon, stopDaemon, streamLogs, } from "./pm2.js";
|
|
2
|
+
export { formatControlUrl, isDaemonStartRequired, isPm2DaemonOnline, statusDaemon, stopDaemon, streamLogs, } from "./pm2.js";
|
|
3
3
|
export { resolveDaemonEntry } from "./entry-path.js";
|
|
4
4
|
export function startDaemon(options) {
|
|
5
5
|
return startPm2Daemon({
|
|
@@ -11,6 +11,9 @@ export function startDaemon(options) {
|
|
|
11
11
|
RYNX_DISTRIBUTION: "standalone",
|
|
12
12
|
RYNX_PRODUCT_VERSION: options.context.productVersion,
|
|
13
13
|
RYNX_DAEMON_LIFECYCLE: "standalone",
|
|
14
|
+
// An empty value explicitly disables PATH fallback when the optional
|
|
15
|
+
// platform package is missing, while keeping the control plane runnable.
|
|
16
|
+
RYNX_TMUX_BIN: options.context.tmuxBin ?? "",
|
|
14
17
|
},
|
|
15
18
|
});
|
|
16
19
|
}
|