@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.
Files changed (72) hide show
  1. package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +0 -12
  2. package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +3 -1
  3. package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +3 -3
  4. package/bundled-plugins/plugins/lark/dist/host-adapters.js +24 -18
  5. package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
  6. package/bundled-plugins/plugins/lark/dist/runtime.js +6043 -6016
  7. package/bundled-plugins/plugins/lark/dist/settings.js +6003 -5985
  8. package/bundled-plugins/plugins/lark/package.json +1 -1
  9. package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
  10. package/bundled-plugins/plugins/lark/ui/index.html +2 -2
  11. package/dist/agent-file.js +0 -1
  12. package/dist/browser-artifact-management.js +4 -1
  13. package/dist/chrome-for-testing-http.d.ts +9 -0
  14. package/dist/chrome-for-testing-http.js +58 -0
  15. package/dist/chrome-for-testing-release-resolver.js +13 -6
  16. package/dist/chrome-for-testing-runtime.js +4 -3
  17. package/dist/chrome-for-testing-store.d.ts +2 -0
  18. package/dist/chrome-for-testing-store.js +99 -17
  19. package/dist/control-deps.d.ts +6 -2
  20. package/dist/control-deps.js +87 -4
  21. package/dist/control-link-store.js +4 -3
  22. package/dist/daemon-owner.js +4 -7
  23. package/dist/daemon-server.js +42 -17
  24. package/dist/db.d.ts +2 -2
  25. package/dist/db.js +157 -21
  26. package/dist/headless-browser-host.js +397 -41
  27. package/dist/lifecycle.d.ts +2 -1
  28. package/dist/lifecycle.js +4 -1
  29. package/dist/migrations/cleanup-legacy-sessions.js +4 -3
  30. package/dist/plugin-cli.d.ts +10 -0
  31. package/dist/plugin-cli.js +19 -2
  32. package/dist/plugin-host-rpc.d.ts +48 -9
  33. package/dist/plugin-host-rpc.js +593 -81
  34. package/dist/plugin-installer.js +3 -2
  35. package/dist/plugin-management-service.js +0 -2
  36. package/dist/plugin-marketplace.js +2 -1
  37. package/dist/plugin-store.js +2 -4
  38. package/dist/plugin-supervisor.js +0 -4
  39. package/dist/pm2.d.ts +2 -0
  40. package/dist/pm2.js +6 -0
  41. package/dist/remote-runtime-access-store.d.ts +2 -2
  42. package/dist/remote-runtime-access-store.js +6 -9
  43. package/dist/remote-runtime-connection-manager.js +25 -42
  44. package/dist/remote-runtime-target-store.d.ts +2 -2
  45. package/dist/remote-runtime-target-store.js +6 -6
  46. package/dist/session-emulator-binding-store.js +4 -6
  47. package/dist/session-fork-store.d.ts +1 -0
  48. package/dist/session-fork-store.js +85 -30
  49. package/dist/session-launch-operations.js +6 -5
  50. package/dist/session-log-store.d.ts +1 -0
  51. package/dist/session-log-store.js +46 -22
  52. package/dist/session-meta-store.d.ts +5 -0
  53. package/dist/session-meta-store.js +25 -2
  54. package/dist/session-portal-grant-store.d.ts +55 -0
  55. package/dist/session-portal-grant-store.js +160 -0
  56. package/dist/session-resource-store.d.ts +24 -6
  57. package/dist/session-resource-store.js +387 -69
  58. package/dist/session-timeline-projection-store.d.ts +24 -0
  59. package/dist/session-timeline-projection-store.js +53 -0
  60. package/dist/setup-service.d.ts +1 -0
  61. package/dist/setup-service.js +1 -0
  62. package/dist/skills-catalog.d.ts +11 -2
  63. package/dist/skills-catalog.js +317 -10
  64. package/dist/sqlite.d.ts +22 -0
  65. package/dist/sqlite.js +57 -0
  66. package/dist/stdio-epipe-guard.d.ts +1 -1
  67. package/dist/stdio-epipe-guard.js +1 -1
  68. package/dist/system-dependencies.d.ts +22 -0
  69. package/dist/system-dependencies.js +79 -0
  70. package/package.json +17 -12
  71. package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
  72. /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, input.url ?? "about:blank");
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.replaceRestoredStartupPages(this.host.input.url ?? "about:blank");
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
- let requestedTarget;
231
- for (const target of this.targets.values()) {
232
- if (target.url === requestedUrl)
233
- requestedTarget = target;
234
- }
235
- if (requestedTarget) {
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", { url });
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
- await terminateProcessGroup(this.host.child, this.host.exit, this.host.terminationGraceMs);
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
- for (const target of targets) {
455
- if (next.size >= RUNTIME_BROWSER_MAX_PAGES) {
456
- throw new SessionBrowserHostError("capacity", "Headless Browser has too many Pages");
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
- next.set(target.targetId, target);
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 replaceRestoredStartupPages(url) {
569
+ async createFreshStartupPage() {
495
570
  const initialPages = await this.readPageTargets();
496
- if (initialPages.length <= 1)
497
- return;
498
- const created = await this.host.connection.command("Target.createTarget", { url });
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
- return existing;
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
- await this.host.connection.command("Page.enable", {}, sessionId);
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.markUnavailable("A Headless Browser Page renderer crashed");
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.dropPageSession(sessionId);
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.targetBySession.delete(id), () => undefined);
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.targetBySession.delete(sessionId);
694
- this.pageSessions.delete(targetId);
695
- const surface = this.surfaceSources.get(targetId);
696
- if (surface) {
697
- this.surfaceSources.delete(targetId);
698
- surface.closeAfterSourceLoss();
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, typeof value.error.message === "string" ? value.error.message : "Native CDP command failed"));
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, url) {
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
- url,
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
  }
@@ -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
  }