@rynx-ai/daemon 0.1.11-beta.3 → 0.1.11-beta.31
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 +6046 -6018
- package/bundled-plugins/plugins/lark/dist/settings.js +6006 -5987
- 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-runtime.js +4 -3
- package/dist/chrome-for-testing-store.js +29 -7
- 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 +41 -16
- package/dist/db.d.ts +2 -2
- package/dist/db.js +155 -21
- package/dist/headless-browser-host.js +283 -34
- package/dist/lifecycle.d.ts +1 -0
- package/dist/lifecycle.js +3 -0
- 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 +552 -80
- 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/remote-runtime-access-store.d.ts +2 -2
- package/dist/remote-runtime-access-store.js +6 -9
- package/dist/remote-runtime-connection-manager.js +1 -1
- 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 +82 -28
- package/dist/session-launch-operations.js +6 -5
- package/dist/session-log-store.d.ts +1 -0
- package/dist/session-log-store.js +19 -16
- 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 +386 -68
- 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/system-dependencies.d.ts +22 -0
- package/dist/system-dependencies.js +79 -0
- package/package.json +15 -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,14 @@ 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
|
+
];
|
|
37
45
|
export function createHeadlessBrowserHost(options = {}) {
|
|
38
46
|
return new HeadlessBrowserHost(options);
|
|
39
47
|
}
|
|
@@ -95,7 +103,7 @@ class HeadlessBrowserHost {
|
|
|
95
103
|
throw error;
|
|
96
104
|
}
|
|
97
105
|
const ownerToken = randomBytes(32).toString("hex");
|
|
98
|
-
const args = chromeArguments(profileDir, ownerToken
|
|
106
|
+
const args = chromeArguments(profileDir, ownerToken);
|
|
99
107
|
let child;
|
|
100
108
|
try {
|
|
101
109
|
child = spawn(build.executablePath, args, {
|
|
@@ -198,6 +206,7 @@ class HeadlessBrowserHandle {
|
|
|
198
206
|
closingTargetIds = new Set();
|
|
199
207
|
pageSessions = new Map();
|
|
200
208
|
targetBySession = new Map();
|
|
209
|
+
networkTargetTypeBySession = new Map();
|
|
201
210
|
surfaceSources = new Map();
|
|
202
211
|
loadingTargets = new Set();
|
|
203
212
|
activeTargetId = null;
|
|
@@ -205,8 +214,11 @@ class HeadlessBrowserHandle {
|
|
|
205
214
|
closing = false;
|
|
206
215
|
closePromise;
|
|
207
216
|
operations = Promise.resolve();
|
|
217
|
+
networkOperations = Promise.resolve();
|
|
218
|
+
requestHeaders;
|
|
208
219
|
constructor(host) {
|
|
209
220
|
this.host = host;
|
|
221
|
+
this.requestHeaders = cloneRequestHeadersConfig(host.input.requestHeaders ?? { enabled: false, headers: [] });
|
|
210
222
|
this.events = this.eventQueue;
|
|
211
223
|
this.host.connection.setEventHandler((event) => this.onCdpEvent(event));
|
|
212
224
|
this.host.connection.setDisconnectHandler((reason) => this.markUnavailable(reason));
|
|
@@ -223,21 +235,22 @@ class HeadlessBrowserHandle {
|
|
|
223
235
|
}
|
|
224
236
|
async initialize() {
|
|
225
237
|
await this.host.connection.command("Target.setDiscoverTargets", { discover: true });
|
|
226
|
-
await this.
|
|
238
|
+
await this.host.connection.command("Target.setAutoAttach", {
|
|
239
|
+
autoAttach: true,
|
|
240
|
+
waitForDebuggerOnStart: true,
|
|
241
|
+
flatten: true,
|
|
242
|
+
filter: NETWORK_AUTO_ATTACH_FILTER,
|
|
243
|
+
});
|
|
244
|
+
await this.createFreshStartupPage();
|
|
227
245
|
await this.refreshTargets();
|
|
228
246
|
const requestedUrl = this.host.input.url;
|
|
229
247
|
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
|
-
}
|
|
248
|
+
const targetId = this.activeTargetId;
|
|
249
|
+
if (!targetId)
|
|
250
|
+
throw new Error("Headless Browser startup Page is unavailable");
|
|
251
|
+
const pageSessionId = await this.pageSession(targetId);
|
|
252
|
+
await this.host.connection.command("Page.navigate", { url: requestedUrl }, pageSessionId);
|
|
253
|
+
await this.resetPageNavigationHistory(targetId);
|
|
241
254
|
}
|
|
242
255
|
}
|
|
243
256
|
async getCdpEndpoint() {
|
|
@@ -336,10 +349,17 @@ class HeadlessBrowserHandle {
|
|
|
336
349
|
if (this.targets.size >= RUNTIME_BROWSER_MAX_PAGES) {
|
|
337
350
|
throw new SessionBrowserHostError("capacity", "Headless Browser Page capacity was reached");
|
|
338
351
|
}
|
|
339
|
-
const result = await this.host.connection.command("Target.createTarget", {
|
|
352
|
+
const result = await this.host.connection.command("Target.createTarget", {
|
|
353
|
+
url: "about:blank",
|
|
354
|
+
});
|
|
340
355
|
const targetId = requiredCdpId(result.targetId, "Target.createTarget targetId");
|
|
341
356
|
this.activeTargetId = targetId;
|
|
342
357
|
await this.refreshTargets();
|
|
358
|
+
if (url !== "about:blank") {
|
|
359
|
+
const pageSessionId = await this.pageSession(targetId);
|
|
360
|
+
await this.host.connection.command("Page.navigate", { url }, pageSessionId);
|
|
361
|
+
await this.resetPageNavigationHistory(targetId);
|
|
362
|
+
}
|
|
343
363
|
this.eventQueue.changed(this.browserGeneration);
|
|
344
364
|
}
|
|
345
365
|
catch (error) {
|
|
@@ -395,6 +415,18 @@ class HeadlessBrowserHandle {
|
|
|
395
415
|
reload(hostPageId) {
|
|
396
416
|
return this.pageCommand(hostPageId, "Page.reload", {});
|
|
397
417
|
}
|
|
418
|
+
setRequestHeaders(config) {
|
|
419
|
+
return this.exclusive(async () => {
|
|
420
|
+
this.ensureAvailable();
|
|
421
|
+
await this.networkExclusive(async () => {
|
|
422
|
+
// Every target is permanently Fetch-intercepted before it is resumed.
|
|
423
|
+
// Swapping this immutable snapshot is therefore the single commit
|
|
424
|
+
// point for all current and future targets; already-paused/in-flight
|
|
425
|
+
// requests keep the snapshot they started with.
|
|
426
|
+
this.requestHeaders = cloneRequestHeadersConfig(config);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
398
430
|
close() {
|
|
399
431
|
if (this.closePromise)
|
|
400
432
|
return this.closePromise;
|
|
@@ -434,6 +466,24 @@ class HeadlessBrowserHandle {
|
|
|
434
466
|
}
|
|
435
467
|
});
|
|
436
468
|
}
|
|
469
|
+
async resetPageNavigationHistory(targetId) {
|
|
470
|
+
let lastError;
|
|
471
|
+
for (let attempt = 0; attempt < PAGE_SNAPSHOT_TRANSITION_ATTEMPTS; attempt += 1) {
|
|
472
|
+
const sessionId = await this.pageSession(targetId);
|
|
473
|
+
try {
|
|
474
|
+
await this.host.connection.command("Page.resetNavigationHistory", {}, sessionId);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
if (!isMissingTargetError(error) && !isTransientPageTransitionError(error))
|
|
479
|
+
throw error;
|
|
480
|
+
lastError = error;
|
|
481
|
+
this.dropAttachedSession(sessionId);
|
|
482
|
+
await delay(PAGE_SNAPSHOT_TRANSITION_POLL_MS);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
throw lastError ?? new Error("Headless Browser could not reset Page navigation history");
|
|
486
|
+
}
|
|
437
487
|
pageCommand(hostPageId, method, params) {
|
|
438
488
|
return this.exclusive(async () => {
|
|
439
489
|
this.ensureAvailable();
|
|
@@ -491,13 +541,23 @@ class HeadlessBrowserHandle {
|
|
|
491
541
|
throw this.mapOperationError(error);
|
|
492
542
|
}
|
|
493
543
|
}
|
|
494
|
-
async
|
|
544
|
+
async createFreshStartupPage() {
|
|
495
545
|
const initialPages = await this.readPageTargets();
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
546
|
+
for (const page of initialPages) {
|
|
547
|
+
try {
|
|
548
|
+
await this.host.connection.command("Target.closeTarget", { targetId: page.targetId });
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
if (!isMissingPageError(error))
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const created = await this.host.connection.command("Target.createTarget", {
|
|
556
|
+
url: "about:blank",
|
|
557
|
+
});
|
|
499
558
|
const freshTargetId = requiredCdpId(created.targetId, "Target.createTarget targetId");
|
|
500
559
|
this.activeTargetId = freshTargetId;
|
|
560
|
+
await this.host.connection.command("Target.activateTarget", { targetId: freshTargetId });
|
|
501
561
|
for (let attempt = 0; attempt < STARTUP_PAGE_CLEANUP_ATTEMPTS; attempt += 1) {
|
|
502
562
|
const pages = await this.readPageTargets();
|
|
503
563
|
if (!pages.some((page) => page.targetId === freshTargetId)) {
|
|
@@ -581,11 +641,21 @@ class HeadlessBrowserHandle {
|
|
|
581
641
|
canGoForward: false,
|
|
582
642
|
};
|
|
583
643
|
}
|
|
584
|
-
async pageSession(targetId) {
|
|
644
|
+
async pageSession(targetId, retryMissing = true) {
|
|
585
645
|
this.requireTarget(targetId);
|
|
586
646
|
const existing = this.pageSessions.get(targetId);
|
|
587
|
-
if (existing)
|
|
588
|
-
|
|
647
|
+
if (existing) {
|
|
648
|
+
try {
|
|
649
|
+
return await existing;
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
if (!retryMissing || !isMissingPageError(error))
|
|
653
|
+
throw error;
|
|
654
|
+
if (this.pageSessions.get(targetId) === existing)
|
|
655
|
+
this.pageSessions.delete(targetId);
|
|
656
|
+
return this.pageSession(targetId, false);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
589
659
|
const pending = (async () => {
|
|
590
660
|
const result = await this.host.connection.command("Target.attachToTarget", {
|
|
591
661
|
targetId,
|
|
@@ -593,7 +663,8 @@ class HeadlessBrowserHandle {
|
|
|
593
663
|
});
|
|
594
664
|
const sessionId = requiredCdpId(result.sessionId, "Target.attachToTarget sessionId");
|
|
595
665
|
this.targetBySession.set(sessionId, targetId);
|
|
596
|
-
|
|
666
|
+
this.networkTargetTypeBySession.set(sessionId, "page");
|
|
667
|
+
await this.networkExclusive(() => this.initializeAttachedTarget(sessionId, this.requireTarget(targetId), false));
|
|
597
668
|
return sessionId;
|
|
598
669
|
})();
|
|
599
670
|
this.pageSessions.set(targetId, pending);
|
|
@@ -608,6 +679,67 @@ class HeadlessBrowserHandle {
|
|
|
608
679
|
onCdpEvent(event) {
|
|
609
680
|
if (this.closing || this.unavailableReason)
|
|
610
681
|
return;
|
|
682
|
+
if (event.method === "Fetch.requestPaused" && event.sessionId) {
|
|
683
|
+
void this.continuePausedRequest(event.sessionId, event.params).catch((error) => {
|
|
684
|
+
if (isMissingFetchRequestError(error))
|
|
685
|
+
return;
|
|
686
|
+
if (isMissingTargetError(error) || isTransientPageTransitionError(error)) {
|
|
687
|
+
this.dropAttachedSession(event.sessionId);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
this.markUnavailable("Headless Browser could not continue an intercepted request");
|
|
691
|
+
});
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
if (event.method === "Target.attachedToTarget") {
|
|
695
|
+
const sessionId = optionalCdpId(event.params.sessionId);
|
|
696
|
+
const target = parseTargetInfo(event.params.targetInfo);
|
|
697
|
+
if (!sessionId)
|
|
698
|
+
return;
|
|
699
|
+
const waitingForDebugger = event.params.waitingForDebugger === true;
|
|
700
|
+
this.targetBySession.set(sessionId, target.targetId);
|
|
701
|
+
this.networkTargetTypeBySession.set(sessionId, target.type);
|
|
702
|
+
if (target.type === "page") {
|
|
703
|
+
this.targets.set(target.targetId, target);
|
|
704
|
+
let pending;
|
|
705
|
+
pending = this.networkExclusive(() => this.initializeAttachedTarget(sessionId, target, waitingForDebugger))
|
|
706
|
+
.then(async () => {
|
|
707
|
+
const selected = this.pageSessions.get(target.targetId);
|
|
708
|
+
if (selected && selected !== pending) {
|
|
709
|
+
let selectedSessionId;
|
|
710
|
+
try {
|
|
711
|
+
selectedSessionId = await selected;
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
// The next Page operation will establish a replacement.
|
|
715
|
+
}
|
|
716
|
+
if (selectedSessionId !== sessionId &&
|
|
717
|
+
this.targetBySession.get(sessionId) === target.targetId) {
|
|
718
|
+
await this.networkExclusive(() => this.detachAuxiliaryPageSession(sessionId));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return sessionId;
|
|
722
|
+
})
|
|
723
|
+
.catch((error) => {
|
|
724
|
+
if (this.pageSessions.get(target.targetId) === pending) {
|
|
725
|
+
this.pageSessions.delete(target.targetId);
|
|
726
|
+
}
|
|
727
|
+
throw error;
|
|
728
|
+
});
|
|
729
|
+
void pending.catch(() => undefined);
|
|
730
|
+
// Chrome may deliver a waitForDebugger auto-attach session while a
|
|
731
|
+
// manual Target.attachToTarget for the same Page is still pending.
|
|
732
|
+
// Every physical session must be initialized and resumed even when a
|
|
733
|
+
// different session already owns semantic Page commands.
|
|
734
|
+
if (!this.pageSessions.has(target.targetId)) {
|
|
735
|
+
this.pageSessions.set(target.targetId, pending);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
void this.networkExclusive(() => this.initializeAttachedTarget(sessionId, target, waitingForDebugger)).catch(() => undefined);
|
|
740
|
+
}
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
611
743
|
if (event.method === "Target.targetCreated" || event.method === "Target.targetInfoChanged") {
|
|
612
744
|
const target = parseTargetInfo(event.params.targetInfo);
|
|
613
745
|
if (target.type !== "page")
|
|
@@ -640,7 +772,7 @@ class HeadlessBrowserHandle {
|
|
|
640
772
|
if (event.method === "Target.detachedFromTarget") {
|
|
641
773
|
const sessionId = optionalCdpId(event.params.sessionId);
|
|
642
774
|
if (sessionId)
|
|
643
|
-
this.
|
|
775
|
+
this.dropAttachedSession(sessionId);
|
|
644
776
|
return;
|
|
645
777
|
}
|
|
646
778
|
const targetId = event.sessionId ? this.targetBySession.get(event.sessionId) : undefined;
|
|
@@ -683,22 +815,106 @@ class HeadlessBrowserHandle {
|
|
|
683
815
|
this.loadingTargets.delete(targetId);
|
|
684
816
|
const session = this.pageSessions.get(targetId);
|
|
685
817
|
this.pageSessions.delete(targetId);
|
|
818
|
+
for (const [sessionId, attachedTargetId] of this.targetBySession) {
|
|
819
|
+
if (attachedTargetId === targetId)
|
|
820
|
+
this.dropAttachedSession(sessionId);
|
|
821
|
+
}
|
|
822
|
+
// A manual attach can resolve after targetDestroyed. Clean the late
|
|
823
|
+
// session as well instead of retaining it in the Network update set.
|
|
686
824
|
if (session)
|
|
687
|
-
void session.then((id) => this.
|
|
825
|
+
void session.then((id) => this.dropAttachedSession(id), () => undefined);
|
|
688
826
|
}
|
|
689
827
|
dropPageSession(sessionId) {
|
|
690
828
|
const targetId = this.targetBySession.get(sessionId);
|
|
829
|
+
this.targetBySession.delete(sessionId);
|
|
830
|
+
this.networkTargetTypeBySession.delete(sessionId);
|
|
691
831
|
if (!targetId)
|
|
692
832
|
return;
|
|
693
|
-
this.
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
833
|
+
const selected = this.pageSessions.get(targetId);
|
|
834
|
+
if (selected) {
|
|
835
|
+
void selected.then((selectedSessionId) => {
|
|
836
|
+
if (selectedSessionId === sessionId &&
|
|
837
|
+
this.pageSessions.get(targetId) === selected) {
|
|
838
|
+
this.pageSessions.delete(targetId);
|
|
839
|
+
const surface = this.surfaceSources.get(targetId);
|
|
840
|
+
if (surface) {
|
|
841
|
+
this.surfaceSources.delete(targetId);
|
|
842
|
+
surface.closeAfterSourceLoss();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}, () => undefined);
|
|
699
846
|
}
|
|
700
847
|
this.eventQueue.changed(this.browserGeneration);
|
|
701
848
|
}
|
|
849
|
+
dropAttachedSession(sessionId) {
|
|
850
|
+
if (this.networkTargetTypeBySession.get(sessionId) === "page") {
|
|
851
|
+
this.dropPageSession(sessionId);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
this.networkTargetTypeBySession.delete(sessionId);
|
|
855
|
+
this.targetBySession.delete(sessionId);
|
|
856
|
+
}
|
|
857
|
+
async initializeAttachedTarget(sessionId, target, waitingForDebugger) {
|
|
858
|
+
try {
|
|
859
|
+
await this.host.connection.command("Network.enable", {}, sessionId);
|
|
860
|
+
await this.host.connection.command("Fetch.enable", {
|
|
861
|
+
patterns: [{ urlPattern: "*", requestStage: "Request" }],
|
|
862
|
+
}, sessionId);
|
|
863
|
+
if (target.type === "page" || target.type === "iframe") {
|
|
864
|
+
await this.host.connection.command("Target.setAutoAttach", {
|
|
865
|
+
autoAttach: true,
|
|
866
|
+
waitForDebuggerOnStart: true,
|
|
867
|
+
flatten: true,
|
|
868
|
+
filter: NETWORK_AUTO_ATTACH_FILTER,
|
|
869
|
+
}, sessionId);
|
|
870
|
+
}
|
|
871
|
+
if (target.type === "page") {
|
|
872
|
+
await this.host.connection.command("Page.enable", {}, sessionId);
|
|
873
|
+
}
|
|
874
|
+
if (waitingForDebugger) {
|
|
875
|
+
await this.host.connection.command("Runtime.runIfWaitingForDebugger", {}, sessionId);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
catch (error) {
|
|
879
|
+
if (isMissingTargetError(error) || isTransientPageTransitionError(error)) {
|
|
880
|
+
this.dropAttachedSession(sessionId);
|
|
881
|
+
throw missingPage(error);
|
|
882
|
+
}
|
|
883
|
+
this.markUnavailable("Headless Browser could not initialize a network target");
|
|
884
|
+
throw this.mapOperationError(error);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
async detachAuxiliaryPageSession(sessionId) {
|
|
888
|
+
try {
|
|
889
|
+
await this.host.connection.command("Target.detachFromTarget", { sessionId });
|
|
890
|
+
}
|
|
891
|
+
catch (error) {
|
|
892
|
+
if (!isMissingTargetError(error) && !isTransientPageTransitionError(error)) {
|
|
893
|
+
this.markUnavailable("Headless Browser could not fence a duplicate Page session");
|
|
894
|
+
throw this.mapOperationError(error);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
finally {
|
|
898
|
+
this.dropAttachedSession(sessionId);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
async continuePausedRequest(sessionId, params) {
|
|
902
|
+
const requestId = requiredCdpId(params.requestId, "Fetch.requestPaused requestId");
|
|
903
|
+
const config = this.requestHeaders;
|
|
904
|
+
const effective = config.enabled && config.headers.length > 0;
|
|
905
|
+
const request = effective && isRecord(params.request) ? params.request : {};
|
|
906
|
+
// This is a product-level baseline, not a security boundary: a caller
|
|
907
|
+
// holding the privileged native CDP endpoint can alter interception.
|
|
908
|
+
await this.host.connection.command("Fetch.continueRequest", {
|
|
909
|
+
requestId,
|
|
910
|
+
// Fetch treats `headers` as a full override. Omitting it is the only
|
|
911
|
+
// true pass-through path for disabled/empty policy and preserves
|
|
912
|
+
// Browser-managed Cookie, auth, redirect, and duplicate headers.
|
|
913
|
+
...(effective
|
|
914
|
+
? { headers: mergePausedRequestHeaders(pausedRequestHeaders(request.headers), config) }
|
|
915
|
+
: {}),
|
|
916
|
+
}, sessionId);
|
|
917
|
+
}
|
|
702
918
|
ensureAvailable() {
|
|
703
919
|
if (this.unavailableReason || this.closing || !this.host.connection.connected) {
|
|
704
920
|
throw new SessionBrowserHostError("unavailable", "Headless Browser is unavailable");
|
|
@@ -729,6 +945,11 @@ class HeadlessBrowserHandle {
|
|
|
729
945
|
this.operations = result.then(() => undefined, () => undefined);
|
|
730
946
|
return result;
|
|
731
947
|
}
|
|
948
|
+
networkExclusive(operation) {
|
|
949
|
+
const result = this.networkOperations.then(operation, operation);
|
|
950
|
+
this.networkOperations = result.then(() => undefined, () => undefined);
|
|
951
|
+
return result;
|
|
952
|
+
}
|
|
732
953
|
}
|
|
733
954
|
class HeadlessBrowserSurfaceSource {
|
|
734
955
|
source;
|
|
@@ -1476,7 +1697,7 @@ class CdpConnection {
|
|
|
1476
1697
|
this.pending.delete(id);
|
|
1477
1698
|
rejectCommand(new CdpCommandTimeoutError(`Native CDP command ${method} timed out`));
|
|
1478
1699
|
}, this.commandTimeoutMs);
|
|
1479
|
-
this.pending.set(id, { resolve: resolveCommand, reject: rejectCommand, timer });
|
|
1700
|
+
this.pending.set(id, { method, resolve: resolveCommand, reject: rejectCommand, timer });
|
|
1480
1701
|
try {
|
|
1481
1702
|
this.socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }));
|
|
1482
1703
|
}
|
|
@@ -1518,7 +1739,9 @@ class CdpConnection {
|
|
|
1518
1739
|
this.pending.delete(value.id);
|
|
1519
1740
|
clearTimeout(pending.timer);
|
|
1520
1741
|
if (isRecord(value.error)) {
|
|
1521
|
-
pending.reject(new CdpProtocolError(typeof value.error.code === "number" ? value.error.code : -1,
|
|
1742
|
+
pending.reject(new CdpProtocolError(typeof value.error.code === "number" ? value.error.code : -1, `Native CDP command ${pending.method} failed: ${typeof value.error.message === "string"
|
|
1743
|
+
? value.error.message
|
|
1744
|
+
: "Native CDP command failed"}`));
|
|
1522
1745
|
}
|
|
1523
1746
|
else {
|
|
1524
1747
|
pending.resolve(value.result ?? {});
|
|
@@ -1887,7 +2110,7 @@ function readSmallRegularJson(path, maxBytes, label) {
|
|
|
1887
2110
|
closeSync(descriptor);
|
|
1888
2111
|
}
|
|
1889
2112
|
}
|
|
1890
|
-
function chromeArguments(profileDir, ownerToken
|
|
2113
|
+
function chromeArguments(profileDir, ownerToken) {
|
|
1891
2114
|
return [
|
|
1892
2115
|
"--headless=new",
|
|
1893
2116
|
"--use-mock-keychain",
|
|
@@ -1905,7 +2128,29 @@ function chromeArguments(profileDir, ownerToken, url) {
|
|
|
1905
2128
|
"--metrics-recording-only",
|
|
1906
2129
|
"--no-report-upload",
|
|
1907
2130
|
"--window-size=1280,800",
|
|
1908
|
-
|
|
2131
|
+
"--no-startup-window",
|
|
2132
|
+
];
|
|
2133
|
+
}
|
|
2134
|
+
function cloneRequestHeadersConfig(config) {
|
|
2135
|
+
return {
|
|
2136
|
+
enabled: config.enabled,
|
|
2137
|
+
headers: config.headers.map((header) => ({ ...header })),
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
function pausedRequestHeaders(value) {
|
|
2141
|
+
if (!isRecord(value))
|
|
2142
|
+
return [];
|
|
2143
|
+
return Object.entries(value).flatMap(([name, headerValue]) => typeof headerValue === "string" ? [{ name, value: headerValue }] : []);
|
|
2144
|
+
}
|
|
2145
|
+
function mergePausedRequestHeaders(original, config) {
|
|
2146
|
+
if (!config.enabled)
|
|
2147
|
+
return original.map((header) => ({ ...header }));
|
|
2148
|
+
const overriddenNames = new Set(config.headers.map((header) => header.name.toLowerCase()));
|
|
2149
|
+
return [
|
|
2150
|
+
...original
|
|
2151
|
+
.filter((header) => !overriddenNames.has(header.name.toLowerCase()))
|
|
2152
|
+
.map((header) => ({ ...header })),
|
|
2153
|
+
...config.headers.map((header) => ({ ...header })),
|
|
1909
2154
|
];
|
|
1910
2155
|
}
|
|
1911
2156
|
function validateActiveExecutable(build) {
|
|
@@ -2118,6 +2363,10 @@ function isTransientPageTransitionError(error) {
|
|
|
2118
2363
|
/cannot find (?:an )?execution context/i.test(error.message) ||
|
|
2119
2364
|
/cannot find context with specified id/i.test(error.message));
|
|
2120
2365
|
}
|
|
2366
|
+
function isMissingFetchRequestError(error) {
|
|
2367
|
+
return error instanceof CdpProtocolError && (/invalid (?:interception|request)id/i.test(error.message) ||
|
|
2368
|
+
/no resource with given identifier/i.test(error.message));
|
|
2369
|
+
}
|
|
2121
2370
|
function isMissingPageError(error) {
|
|
2122
2371
|
return (error instanceof SessionBrowserHostError && error.code === "page_not_found") || isMissingTargetError(error);
|
|
2123
2372
|
}
|
package/dist/lifecycle.d.ts
CHANGED
package/dist/lifecycle.js
CHANGED
|
@@ -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
|
}
|
|
@@ -20,6 +20,7 @@ import { mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { rynxHome } from "@rynx-ai/core";
|
|
22
22
|
import { db } from "../db.js";
|
|
23
|
+
import { transaction } from "../sqlite.js";
|
|
23
24
|
/** Mirrors the daemon runtime store's default resolution. */
|
|
24
25
|
function defaultProxyDir() {
|
|
25
26
|
return path.join(rynxHome(), ".codex-proxy");
|
|
@@ -46,9 +47,9 @@ export function cleanupLegacySessions(opts = {}) {
|
|
|
46
47
|
let deletedLogRows = 0;
|
|
47
48
|
let deletedSessions = 0;
|
|
48
49
|
let prunedStoreEntries = 0;
|
|
49
|
-
|
|
50
|
-
deletedLogRows = conn.prepare(`DELETE FROM session_items WHERE ${LEGACY_LOG}`).run().changes;
|
|
51
|
-
deletedSessions = conn.prepare(`DELETE FROM sessions WHERE ${LEGACY_META}`).run().changes;
|
|
50
|
+
transaction(conn, () => {
|
|
51
|
+
deletedLogRows = Number(conn.prepare(`DELETE FROM session_items WHERE ${LEGACY_LOG}`).run().changes);
|
|
52
|
+
deletedSessions = Number(conn.prepare(`DELETE FROM sessions WHERE ${LEGACY_META}`).run().changes);
|
|
52
53
|
})();
|
|
53
54
|
// The runtime store is daemon-owned. Plugin routing stores remain private to
|
|
54
55
|
// their plugins and are never inspected by this migration.
|
package/dist/plugin-cli.d.ts
CHANGED
|
@@ -20,6 +20,16 @@ export interface PluginCliCommandResult {
|
|
|
20
20
|
stderrTruncated: boolean;
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
+
export interface InstalledPluginListItem {
|
|
24
|
+
id: string;
|
|
25
|
+
version: string;
|
|
26
|
+
source: "local" | "git";
|
|
27
|
+
state: "enabled" | "disabled";
|
|
28
|
+
}
|
|
29
|
+
/** Read the durable installed-plugin inventory without contacting the daemon. */
|
|
30
|
+
export declare function listInstalledPlugins(): InstalledPluginListItem[];
|
|
31
|
+
/** Render manifest-declared command help without executing plugin code. */
|
|
32
|
+
export declare function installedPluginCommandUsage(pluginId: string): string;
|
|
23
33
|
/** Execute one statically declared, language-neutral plugin command. */
|
|
24
34
|
export declare function runPluginCliCommand(pluginId: string, args: readonly string[], options?: RunPluginCliOptions): Promise<PluginCliCommandResult>;
|
|
25
35
|
export declare function pluginCommandUsage(record: RunnablePluginRecord): string;
|
package/dist/plugin-cli.js
CHANGED
|
@@ -3,12 +3,29 @@ import { mkdirSync, realpathSync, statSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createPluginCliInvocationLeaseId, removePluginRuntimeLease, writePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
5
5
|
import { db } from "./db.js";
|
|
6
|
+
import { transaction } from "./sqlite.js";
|
|
6
7
|
import { pluginDataDir, pluginRuntimeCacheDir } from "./plugin-installer.js";
|
|
7
8
|
import { validatePluginPackageSnapshot } from "./plugin-package.js";
|
|
8
|
-
import { getPlugin } from "./plugin-store.js";
|
|
9
|
+
import { getPlugin, listPlugins, } from "./plugin-store.js";
|
|
9
10
|
import { loadOrCreateDaemonInstallationIdentitySync } from "./installation-id.js";
|
|
10
11
|
const DEFAULT_TERMINATION_GRACE_MS = 2_000;
|
|
11
12
|
const MAX_TERMINATION_GRACE_MS = 30_000;
|
|
13
|
+
/** Read the durable installed-plugin inventory without contacting the daemon. */
|
|
14
|
+
export function listInstalledPlugins() {
|
|
15
|
+
return listPlugins().map((record) => ({
|
|
16
|
+
id: record.canonicalId,
|
|
17
|
+
version: record.version,
|
|
18
|
+
source: record.source,
|
|
19
|
+
state: record.enabled ? "enabled" : "disabled",
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/** Render manifest-declared command help without executing plugin code. */
|
|
23
|
+
export function installedPluginCommandUsage(pluginId) {
|
|
24
|
+
const record = getPlugin(pluginId);
|
|
25
|
+
if (!record)
|
|
26
|
+
throw new Error(`plugin "${pluginId}" is not installed`);
|
|
27
|
+
return pluginCommandUsage(record);
|
|
28
|
+
}
|
|
12
29
|
/** Execute one statically declared, language-neutral plugin command. */
|
|
13
30
|
export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
14
31
|
const hostEnv = options.env ?? process.env;
|
|
@@ -74,7 +91,7 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
74
91
|
removePluginRuntimeLease(dataDir, leaseId);
|
|
75
92
|
};
|
|
76
93
|
try {
|
|
77
|
-
db()
|
|
94
|
+
transaction(db(), () => {
|
|
78
95
|
const record = getPlugin(pluginId);
|
|
79
96
|
if (!record)
|
|
80
97
|
throw new Error(`plugin "${pluginId}" is not installed`);
|