dev3000 0.0.186 → 0.0.188

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 (235) hide show
  1. package/README.md +101 -13
  2. package/bin/dev3000 +1 -2
  3. package/dist/cdp-monitor.d.ts +7 -12
  4. package/dist/cdp-monitor.d.ts.map +1 -1
  5. package/dist/cdp-monitor.js +86 -170
  6. package/dist/cdp-monitor.js.map +1 -1
  7. package/dist/cli.d.ts +1 -2
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +1 -1369
  10. package/dist/cli.js.map +1 -1
  11. package/dist/index.d.ts +1 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +0 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/portless.d.ts +3 -0
  16. package/dist/portless.d.ts.map +1 -1
  17. package/dist/portless.js +10 -0
  18. package/dist/portless.js.map +1 -1
  19. package/dist/runtime/bridge.d.ts +26 -0
  20. package/dist/runtime/bridge.d.ts.map +1 -0
  21. package/dist/runtime/bridge.js +340 -0
  22. package/dist/runtime/bridge.js.map +1 -0
  23. package/dist/runtime/chrome-isolation.d.ts +16 -0
  24. package/dist/runtime/chrome-isolation.d.ts.map +1 -0
  25. package/dist/runtime/chrome-isolation.js +119 -0
  26. package/dist/runtime/chrome-isolation.js.map +1 -0
  27. package/dist/runtime/cli.js +116 -26
  28. package/dist/runtime/cli.js.map +1 -1
  29. package/dist/runtime/event-views.d.ts +21 -0
  30. package/dist/runtime/event-views.d.ts.map +1 -0
  31. package/dist/runtime/event-views.js +53 -0
  32. package/dist/runtime/event-views.js.map +1 -0
  33. package/dist/runtime/harness.d.ts +16 -0
  34. package/dist/runtime/harness.d.ts.map +1 -0
  35. package/dist/runtime/harness.js +68 -0
  36. package/dist/runtime/harness.js.map +1 -0
  37. package/dist/runtime/local-proxy.d.ts +12 -0
  38. package/dist/runtime/local-proxy.d.ts.map +1 -0
  39. package/dist/runtime/local-proxy.js +114 -0
  40. package/dist/runtime/local-proxy.js.map +1 -0
  41. package/dist/runtime/runtime.d.ts +14 -0
  42. package/dist/runtime/runtime.d.ts.map +1 -1
  43. package/dist/runtime/runtime.js +80 -19
  44. package/dist/runtime/runtime.js.map +1 -1
  45. package/dist/runtime/sandbox-agent.d.ts +6 -0
  46. package/dist/runtime/sandbox-agent.d.ts.map +1 -0
  47. package/dist/runtime/sandbox-agent.js +35 -0
  48. package/dist/runtime/sandbox-agent.js.map +1 -0
  49. package/dist/runtime/sandbox-command.d.ts +9 -0
  50. package/dist/runtime/sandbox-command.d.ts.map +1 -0
  51. package/dist/runtime/sandbox-command.js +101 -0
  52. package/dist/runtime/sandbox-command.js.map +1 -0
  53. package/dist/runtime/sandbox-proxy.d.ts +11 -0
  54. package/dist/runtime/sandbox-proxy.d.ts.map +1 -0
  55. package/dist/runtime/sandbox-proxy.js +123 -0
  56. package/dist/runtime/sandbox-proxy.js.map +1 -0
  57. package/dist/runtime/sandbox-sessions.d.ts +31 -0
  58. package/dist/runtime/sandbox-sessions.d.ts.map +1 -0
  59. package/dist/runtime/sandbox-sessions.js +102 -0
  60. package/dist/runtime/sandbox-sessions.js.map +1 -0
  61. package/dist/runtime/sandbox.d.ts +21 -0
  62. package/dist/runtime/sandbox.d.ts.map +1 -0
  63. package/dist/runtime/sandbox.js +481 -0
  64. package/dist/runtime/sandbox.js.map +1 -0
  65. package/dist/utils/agent-browser.d.ts +2 -120
  66. package/dist/utils/agent-browser.d.ts.map +1 -1
  67. package/dist/utils/agent-browser.js +13 -376
  68. package/dist/utils/agent-browser.js.map +1 -1
  69. package/dist/utils/project-name.d.ts +2 -2
  70. package/dist/utils/project-name.js +2 -2
  71. package/package.json +13 -22
  72. package/skills/d3k/SKILL.md +23 -1
  73. package/dist/commands/crawl.d.ts +0 -12
  74. package/dist/commands/crawl.d.ts.map +0 -1
  75. package/dist/commands/crawl.js +0 -101
  76. package/dist/commands/crawl.js.map +0 -1
  77. package/dist/commands/errors.d.ts +0 -14
  78. package/dist/commands/errors.d.ts.map +0 -1
  79. package/dist/commands/errors.js +0 -246
  80. package/dist/commands/errors.js.map +0 -1
  81. package/dist/commands/fix.d.ts +0 -13
  82. package/dist/commands/fix.d.ts.map +0 -1
  83. package/dist/commands/fix.js +0 -219
  84. package/dist/commands/fix.js.map +0 -1
  85. package/dist/commands/logs.d.ts +0 -13
  86. package/dist/commands/logs.d.ts.map +0 -1
  87. package/dist/commands/logs.js +0 -146
  88. package/dist/commands/logs.js.map +0 -1
  89. package/dist/commands/resume.d.ts +0 -11
  90. package/dist/commands/resume.d.ts.map +0 -1
  91. package/dist/commands/resume.js +0 -75
  92. package/dist/commands/resume.js.map +0 -1
  93. package/dist/commands/status.d.ts +0 -21
  94. package/dist/commands/status.d.ts.map +0 -1
  95. package/dist/commands/status.js +0 -44
  96. package/dist/commands/status.js.map +0 -1
  97. package/dist/components/AgentSelector.d.ts +0 -12
  98. package/dist/components/AgentSelector.d.ts.map +0 -1
  99. package/dist/components/AgentSelector.js +0 -99
  100. package/dist/components/AgentSelector.js.map +0 -1
  101. package/dist/components/PackageSelector.d.ts +0 -13
  102. package/dist/components/PackageSelector.d.ts.map +0 -1
  103. package/dist/components/PackageSelector.js +0 -79
  104. package/dist/components/PackageSelector.js.map +0 -1
  105. package/dist/constants/log-colors.d.ts +0 -43
  106. package/dist/constants/log-colors.d.ts.map +0 -1
  107. package/dist/constants/log-colors.js +0 -59
  108. package/dist/constants/log-colors.js.map +0 -1
  109. package/dist/dev-environment.d.ts +0 -187
  110. package/dist/dev-environment.d.ts.map +0 -1
  111. package/dist/dev-environment.js +0 -2248
  112. package/dist/dev-environment.js.map +0 -1
  113. package/dist/screencast-manager.d.ts +0 -78
  114. package/dist/screencast-manager.d.ts.map +0 -1
  115. package/dist/screencast-manager.js +0 -529
  116. package/dist/screencast-manager.js.map +0 -1
  117. package/dist/services/parsers/error-detectors/base.d.ts +0 -24
  118. package/dist/services/parsers/error-detectors/base.d.ts.map +0 -1
  119. package/dist/services/parsers/error-detectors/base.js +0 -44
  120. package/dist/services/parsers/error-detectors/base.js.map +0 -1
  121. package/dist/services/parsers/error-detectors/index.d.ts +0 -6
  122. package/dist/services/parsers/error-detectors/index.d.ts.map +0 -1
  123. package/dist/services/parsers/error-detectors/index.js +0 -6
  124. package/dist/services/parsers/error-detectors/index.js.map +0 -1
  125. package/dist/services/parsers/error-detectors/nextjs.d.ts +0 -9
  126. package/dist/services/parsers/error-detectors/nextjs.d.ts.map +0 -1
  127. package/dist/services/parsers/error-detectors/nextjs.js +0 -52
  128. package/dist/services/parsers/error-detectors/nextjs.js.map +0 -1
  129. package/dist/services/parsers/index.d.ts +0 -9
  130. package/dist/services/parsers/index.d.ts.map +0 -1
  131. package/dist/services/parsers/index.js +0 -10
  132. package/dist/services/parsers/index.js.map +0 -1
  133. package/dist/services/parsers/log-parsers/base.d.ts +0 -26
  134. package/dist/services/parsers/log-parsers/base.d.ts.map +0 -1
  135. package/dist/services/parsers/log-parsers/base.js +0 -6
  136. package/dist/services/parsers/log-parsers/base.js.map +0 -1
  137. package/dist/services/parsers/log-parsers/index.d.ts +0 -6
  138. package/dist/services/parsers/log-parsers/index.d.ts.map +0 -1
  139. package/dist/services/parsers/log-parsers/index.js +0 -5
  140. package/dist/services/parsers/log-parsers/index.js.map +0 -1
  141. package/dist/services/parsers/log-parsers/standard.d.ts +0 -9
  142. package/dist/services/parsers/log-parsers/standard.d.ts.map +0 -1
  143. package/dist/services/parsers/log-parsers/standard.js +0 -17
  144. package/dist/services/parsers/log-parsers/standard.js.map +0 -1
  145. package/dist/services/parsers/output-processor.d.ts +0 -39
  146. package/dist/services/parsers/output-processor.d.ts.map +0 -1
  147. package/dist/services/parsers/output-processor.js +0 -54
  148. package/dist/services/parsers/output-processor.js.map +0 -1
  149. package/dist/skills/d3k/internal-skill.md +0 -32
  150. package/dist/skills/index.d.ts +0 -54
  151. package/dist/skills/index.d.ts.map +0 -1
  152. package/dist/skills/index.js +0 -214
  153. package/dist/skills/index.js.map +0 -1
  154. package/dist/src/tui-interface-impl.tsx +0 -644
  155. package/dist/tui-interface-impl.d.ts +0 -30
  156. package/dist/tui-interface-impl.d.ts.map +0 -1
  157. package/dist/tui-interface-impl.js +0 -403
  158. package/dist/tui-interface-impl.js.map +0 -1
  159. package/dist/tui-interface-opentui.d.ts +0 -33
  160. package/dist/tui-interface-opentui.d.ts.map +0 -1
  161. package/dist/tui-interface-opentui.js +0 -1128
  162. package/dist/tui-interface-opentui.js.map +0 -1
  163. package/dist/tui-interface.d.ts +0 -38
  164. package/dist/tui-interface.d.ts.map +0 -1
  165. package/dist/tui-interface.js +0 -84
  166. package/dist/tui-interface.js.map +0 -1
  167. package/dist/utils/agent-detection.d.ts +0 -17
  168. package/dist/utils/agent-detection.d.ts.map +0 -1
  169. package/dist/utils/agent-detection.js +0 -81
  170. package/dist/utils/agent-detection.js.map +0 -1
  171. package/dist/utils/agent-selection.d.ts +0 -25
  172. package/dist/utils/agent-selection.d.ts.map +0 -1
  173. package/dist/utils/agent-selection.js +0 -73
  174. package/dist/utils/agent-selection.js.map +0 -1
  175. package/dist/utils/browser-command-argv.d.ts +0 -19
  176. package/dist/utils/browser-command-argv.d.ts.map +0 -1
  177. package/dist/utils/browser-command-argv.js +0 -149
  178. package/dist/utils/browser-command-argv.js.map +0 -1
  179. package/dist/utils/build-version.d.ts +0 -2
  180. package/dist/utils/build-version.d.ts.map +0 -1
  181. package/dist/utils/build-version.js +0 -8
  182. package/dist/utils/build-version.js.map +0 -1
  183. package/dist/utils/cli-options.d.ts +0 -5
  184. package/dist/utils/cli-options.d.ts.map +0 -1
  185. package/dist/utils/cli-options.js +0 -36
  186. package/dist/utils/cli-options.js.map +0 -1
  187. package/dist/utils/d3k-dir.d.ts +0 -3
  188. package/dist/utils/d3k-dir.d.ts.map +0 -1
  189. package/dist/utils/d3k-dir.js +0 -12
  190. package/dist/utils/d3k-dir.js.map +0 -1
  191. package/dist/utils/d3k-skill-installer.d.ts +0 -4
  192. package/dist/utils/d3k-skill-installer.d.ts.map +0 -1
  193. package/dist/utils/d3k-skill-installer.js +0 -30
  194. package/dist/utils/d3k-skill-installer.js.map +0 -1
  195. package/dist/utils/log-filename.d.ts +0 -30
  196. package/dist/utils/log-filename.d.ts.map +0 -1
  197. package/dist/utils/log-filename.js +0 -60
  198. package/dist/utils/log-filename.js.map +0 -1
  199. package/dist/utils/project-detector.d.ts +0 -25
  200. package/dist/utils/project-detector.d.ts.map +0 -1
  201. package/dist/utils/project-detector.js +0 -153
  202. package/dist/utils/project-detector.js.map +0 -1
  203. package/dist/utils/project-metadata.d.ts +0 -4
  204. package/dist/utils/project-metadata.d.ts.map +0 -1
  205. package/dist/utils/project-metadata.js +0 -48
  206. package/dist/utils/project-metadata.js.map +0 -1
  207. package/dist/utils/repo-validate.d.ts +0 -2
  208. package/dist/utils/repo-validate.d.ts.map +0 -1
  209. package/dist/utils/repo-validate.js +0 -10
  210. package/dist/utils/repo-validate.js.map +0 -1
  211. package/dist/utils/session.d.ts +0 -19
  212. package/dist/utils/session.d.ts.map +0 -1
  213. package/dist/utils/session.js +0 -65
  214. package/dist/utils/session.js.map +0 -1
  215. package/dist/utils/skill-installer.d.ts +0 -65
  216. package/dist/utils/skill-installer.d.ts.map +0 -1
  217. package/dist/utils/skill-installer.js +0 -265
  218. package/dist/utils/skill-installer.js.map +0 -1
  219. package/dist/utils/timestamp.d.ts +0 -20
  220. package/dist/utils/timestamp.d.ts.map +0 -1
  221. package/dist/utils/timestamp.js +0 -57
  222. package/dist/utils/timestamp.js.map +0 -1
  223. package/dist/utils/tmux-helpers.d.ts +0 -36
  224. package/dist/utils/tmux-helpers.d.ts.map +0 -1
  225. package/dist/utils/tmux-helpers.js +0 -118
  226. package/dist/utils/tmux-helpers.js.map +0 -1
  227. package/dist/utils/user-config.d.ts +0 -12
  228. package/dist/utils/user-config.d.ts.map +0 -1
  229. package/dist/utils/user-config.js +0 -46
  230. package/dist/utils/user-config.js.map +0 -1
  231. package/dist/utils/version-check.d.ts +0 -52
  232. package/dist/utils/version-check.d.ts.map +0 -1
  233. package/dist/utils/version-check.js +0 -304
  234. package/dist/utils/version-check.js.map +0 -1
  235. package/src/tui-interface-impl.tsx +0 -644
@@ -1,9 +1,10 @@
1
1
  import { spawn } from "child_process";
2
- import { existsSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "fs";
3
3
  import { tmpdir } from "os";
4
4
  import { dirname, join } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { WebSocket } from "ws";
7
+ import { isolatedChromeInvocation } from "./runtime/chrome-isolation.js";
7
8
  /** Sandbox exceptions must be explicit; headless mode alone never disables it. */
8
9
  export function getChromeSandboxFlags(env = process.env) {
9
10
  return env.D3K_CHROME_NO_SANDBOX === "1" ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
@@ -233,49 +234,39 @@ export function selectCDPTarget(targets, options = {}) {
233
234
  }
234
235
  export class CDPMonitor {
235
236
  eventObserver;
236
- automaticScreenshots = true;
237
237
  setEventObserver(observer) {
238
238
  this.eventObserver = observer;
239
239
  }
240
- setAutomaticScreenshots(enabled) {
241
- this.automaticScreenshots = enabled;
242
- }
243
240
  browser = null;
244
241
  connection = null;
245
242
  debugPort = 9222;
246
243
  eventHandlers = new Map();
247
244
  profileDir;
248
- screenshotDir;
249
245
  logger;
250
246
  debug = false;
251
247
  browserPath;
252
248
  isShuttingDown = false;
253
- pendingRequests = 0;
254
- networkIdleTimer = null;
255
- pluginReactScan = false;
249
+ reconnecting = false;
256
250
  cdpUrl = null;
257
- lastScreenshotTime = 0;
258
- minScreenshotInterval = 1000; // Minimum 1 second between screenshots
259
- navigationInProgress = false; // Track if a navigation event recently occurred
260
251
  chromePids = new Set(); // Track all Chrome PIDs for this instance
261
252
  onWindowClosedCallback = null; // Callback for when window is manually closed
262
253
  appServerPort; // Port of the user's app server to monitor
263
254
  initialAppUrl; // App URL that the loading page should hand off to
264
255
  externalCdpBase; // Existing CDP HTTP endpoint to connect to instead of launching Chrome
256
+ filesystemIsolation = false;
257
+ isolatedCertificatePin;
265
258
  headless = false; // Run Chrome in headless mode
266
259
  framework; // Framework hint from project detection
267
260
  reactTrackingEnabled = false;
268
261
  lastReactSnapshotLogTime = 0;
269
262
  pendingCommands = new Map();
270
263
  navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS;
271
- constructor(profileDir, screenshotDir, logger, debug = false, browserPath, pluginReactScan = false, appServerPort, initialAppUrl, navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS, debugPort, headless = false, framework, externalCdpBase) {
264
+ constructor(profileDir, logger, debug = false, browserPath, appServerPort, initialAppUrl, navigationTimeoutMs = DEFAULT_NAVIGATION_TIMEOUT_MS, debugPort, headless = false, framework, externalCdpBase) {
272
265
  this.profileDir = profileDir;
273
- this.screenshotDir = screenshotDir;
274
266
  this.appServerPort = appServerPort;
275
267
  this.logger = logger;
276
268
  this.debug = debug;
277
269
  this.browserPath = browserPath;
278
- this.pluginReactScan = pluginReactScan;
279
270
  this.initialAppUrl = initialAppUrl;
280
271
  this.navigationTimeoutMs = navigationTimeoutMs;
281
272
  this.headless = headless;
@@ -452,6 +443,10 @@ export class CDPMonitor {
452
443
  getChromePids() {
453
444
  return Array.from(this.chromePids);
454
445
  }
446
+ requireFilesystemIsolation(certificatePin) {
447
+ this.isolatedCertificatePin = certificatePin;
448
+ this.filesystemIsolation = true;
449
+ }
455
450
  setOnWindowClosedCallback(callback) {
456
451
  this.onWindowClosedCallback = callback;
457
452
  }
@@ -479,7 +474,9 @@ export class CDPMonitor {
479
474
  }
480
475
  createLoadingPage(targetUrl) {
481
476
  // Use a unique temp directory per launch to avoid stale file:// cache artifacts.
482
- const loadingDir = mkdtempSync(join(tmpdir(), "dev3000-loading-"));
477
+ if (this.filesystemIsolation)
478
+ mkdirSync(this.profileDir, { recursive: true, mode: 0o700 });
479
+ const loadingDir = mkdtempSync(join(this.filesystemIsolation ? this.profileDir : tmpdir(), "dev3000-loading-"));
483
480
  const loadingPath = join(loadingDir, "loading.html");
484
481
  // Read the loading HTML from a runtime-valid location.
485
482
  const currentFile = fileURLToPath(import.meta.url);
@@ -538,11 +535,9 @@ export class CDPMonitor {
538
535
  this.debugLog(`Chrome crashed: code=${code}, signal=${signal}`);
539
536
  // Log context for crash correlation
540
537
  this.logger("browser", "[CRASH] Chrome crashed - check recent server/browser logs for correlation");
541
- // Take screenshot if still connected (for crash context)
542
- if (this.connection && this.connection.ws.readyState === 1) {
543
- this.takeScreenshot("crash");
544
- }
545
538
  }
539
+ this.prepareShutdown();
540
+ this.onWindowClosedCallback?.();
546
541
  }
547
542
  });
548
543
  this.browser.on("error", (error) => {
@@ -629,7 +624,13 @@ export class CDPMonitor {
629
624
  this.debugLog(`Custom browser path: ${this.browserPath}`);
630
625
  }
631
626
  let attemptIndex = 0;
627
+ let launchComplete = false;
632
628
  const tryNextChrome = () => {
629
+ if (launchComplete || this.isShuttingDown) {
630
+ if (!launchComplete)
631
+ reject(new Error("Browser session is ending"));
632
+ return;
633
+ }
633
634
  if (attemptIndex >= chromeCommands.length) {
634
635
  const errorMsg = `Failed to launch Chrome: all ${chromeCommands.length} browser paths exhausted`;
635
636
  this.logger("browser", `[CDP] ${errorMsg}`);
@@ -684,7 +685,11 @@ export class CDPMonitor {
684
685
  }
685
686
  // Add initial page
686
687
  chromeArgs.push(this.createLoadingPage(this.initialAppUrl));
687
- this.browser = spawn(chromePath, chromeArgs, {
688
+ const invocation = this.filesystemIsolation
689
+ ? isolatedChromeInvocation(chromePath, chromeArgs, this.profileDir, this.isolatedCertificatePin)
690
+ : { binary: chromePath, args: chromeArgs, env: process.env };
691
+ this.browser = spawn(invocation.binary, invocation.args, {
692
+ env: invocation.env,
688
693
  stdio: "pipe",
689
694
  detached: true // Separate process group so Chrome doesn't receive SIGINT on Ctrl+C
690
695
  });
@@ -721,6 +726,11 @@ export class CDPMonitor {
721
726
  // Poll for Chrome readiness instead of fixed timeout
722
727
  const checkChromeReady = async (attempts = 0) => {
723
728
  const maxAttempts = 30; // 30 attempts = 15 seconds max
729
+ if (launchComplete || this.isShuttingDown) {
730
+ if (!launchComplete)
731
+ reject(new Error("Browser session is ending"));
732
+ return;
733
+ }
724
734
  if (processExited) {
725
735
  return;
726
736
  }
@@ -747,6 +757,11 @@ export class CDPMonitor {
747
757
  this.debugLog(`Chrome successfully started with path: ${chromePath} (after ${attempts * 500}ms)`);
748
758
  // Discover all Chrome PIDs for this instance
749
759
  await this.discoverChromePids();
760
+ if (this.isShuttingDown) {
761
+ reject(new Error("Browser session is ending"));
762
+ return;
763
+ }
764
+ launchComplete = true;
750
765
  // Set up runtime crash monitoring after successful launch
751
766
  this.setupRuntimeCrashMonitoring();
752
767
  resolve();
@@ -771,24 +786,20 @@ export class CDPMonitor {
771
786
  let retryCount = 0;
772
787
  const maxRetries = 5;
773
788
  while (retryCount < maxRetries) {
789
+ if (this.isShuttingDown)
790
+ throw new Error("Browser session is ending");
774
791
  try {
775
792
  // Get the WebSocket URL from Chrome's debug endpoint
776
793
  const targetsResponse = await fetch(`${cdpBase}/json`);
777
- let targets = (await targetsResponse.json());
794
+ const targets = (await targetsResponse.json());
778
795
  this.debugLog(`Found ${targets.length} targets: ${JSON.stringify(targets.map((target) => ({ type: target.type ?? "unknown", url: target.url ?? "" })))}`);
779
- if (targets.length === 0) {
780
- this.debugLog("No debuggable targets found; creating a blank page target");
781
- const newTargetResponse = await fetch(`${cdpBase}/json/new?about:blank`, {
782
- method: "PUT"
783
- });
784
- if (newTargetResponse.ok) {
785
- const createdTarget = (await newTargetResponse.json());
786
- targets = [createdTarget];
787
- this.debugLog(`Created page target: ${createdTarget.id || "unknown"} - ${createdTarget.url || ""}`);
788
- }
789
- else {
790
- this.debugLog(`Failed to create page target: HTTP ${newTargetResponse.status}`);
791
- }
796
+ if (this.isShuttingDown)
797
+ throw new Error("Browser session is ending");
798
+ if (!targets.some((target) => target.type === "page")) {
799
+ // A closed final window is a session end, never a request to create a replacement tab.
800
+ this.prepareShutdown();
801
+ this.onWindowClosedCallback?.();
802
+ throw new Error("Browser has no open targets; session ended");
792
803
  }
793
804
  const pageTarget = selectCDPTarget(targets, {
794
805
  appServerPort: this.appServerPort,
@@ -808,6 +819,11 @@ export class CDPMonitor {
808
819
  ws.setMaxListeners(20);
809
820
  ws.on("open", () => {
810
821
  this.debugLog("WebSocket connection opened successfully");
822
+ if (this.isShuttingDown) {
823
+ ws.close();
824
+ reject(new Error("Browser session is ending"));
825
+ return;
826
+ }
811
827
  this.connection = {
812
828
  ws,
813
829
  sessionId: null,
@@ -884,6 +900,8 @@ export class CDPMonitor {
884
900
  });
885
901
  }
886
902
  catch (error) {
903
+ if (this.isShuttingDown)
904
+ throw error;
887
905
  retryCount++;
888
906
  this.debugLog(`CDP connection attempt ${retryCount} failed: ${error}`);
889
907
  if (retryCount >= maxRetries) {
@@ -896,30 +914,43 @@ export class CDPMonitor {
896
914
  }
897
915
  }
898
916
  }
917
+ /** A returning companion can retry after the previous finite recovery attempt was exhausted. */
918
+ async recoverExternalConnection() {
919
+ if (this.externalCdpBase && !this.getCdpUrl())
920
+ await this.attemptReconnect();
921
+ }
899
922
  async attemptReconnect() {
900
- this.debugLog("Starting CDP reconnection attempt");
901
- this.logger("browser", "[CDP] Starting reconnection process...");
902
- // Close existing connection if any
903
- if (this.connection) {
923
+ if (this.isShuttingDown || this.reconnecting || this.getCdpUrl())
924
+ return;
925
+ this.reconnecting = true;
926
+ try {
927
+ this.debugLog("Starting CDP reconnection attempt");
928
+ this.logger("browser", "[CDP] Starting reconnection process...");
929
+ // Close existing connection if any
930
+ if (this.connection) {
931
+ try {
932
+ this.connection.ws.close();
933
+ }
934
+ catch (err) {
935
+ this.debugLog(`Error closing old connection: ${err}`);
936
+ }
937
+ this.connection = null;
938
+ }
939
+ // Attempt to reconnect
904
940
  try {
905
- this.connection.ws.close();
941
+ await this.connectToCDP();
942
+ await this.enableCDPDomains();
943
+ await this.setupInteractionTracking();
944
+ this.logger("browser", "[CDP] Reconnection successful! Monitoring restored.");
945
+ this.debugLog("CDP reconnection completed successfully and monitoring was restored");
906
946
  }
907
947
  catch (err) {
908
- this.debugLog(`Error closing old connection: ${err}`);
948
+ this.logger("browser", `[CDP] Reconnection failed: ${err}`);
949
+ throw err;
909
950
  }
910
- this.connection = null;
911
951
  }
912
- // Attempt to reconnect
913
- try {
914
- await this.connectToCDP();
915
- await this.enableCDPDomains();
916
- await this.setupInteractionTracking();
917
- this.logger("browser", "[CDP] Reconnection successful! Monitoring restored.");
918
- this.debugLog("CDP reconnection completed successfully and monitoring was restored");
919
- }
920
- catch (err) {
921
- this.logger("browser", `[CDP] Reconnection failed: ${err}`);
922
- throw err;
952
+ finally {
953
+ this.reconnecting = false;
923
954
  }
924
955
  }
925
956
  async sendCDPCommand(method, params = {}, timeoutMs = DEFAULT_CDP_COMMAND_TIMEOUT_MS) {
@@ -1179,8 +1210,6 @@ export class CDPMonitor {
1179
1210
  }
1180
1211
  this.logger("browser", errorMsg);
1181
1212
  void this.logReactSnapshot("exception context", true);
1182
- // Take screenshot immediately on errors (no delay needed)
1183
- this.takeScreenshot("error");
1184
1213
  });
1185
1214
  // Browser console logs via Log domain (additional capture method)
1186
1215
  this.onCDPEvent("Log.entryAdded", (event) => {
@@ -1259,40 +1288,20 @@ export class CDPMonitor {
1259
1288
  return;
1260
1289
  }
1261
1290
  this.logger("browser", `[NAVIGATION] ${url}`);
1262
- // Mark that we're in a navigation - we'll take a screenshot when it settles
1263
- this.navigationInProgress = true;
1264
1291
  });
1265
- // Page load events for better screenshot timing
1292
+ // Reinject interaction tracking when documents load
1266
1293
  this.onCDPEvent("Page.loadEventFired", async (_event) => {
1267
1294
  this.logger("browser", "[DOM] Load event fired");
1268
- this.takeScreenshot("page-loaded");
1269
1295
  // Reinject interaction tracking on page load
1270
1296
  await this.setupInteractionTracking();
1271
1297
  void this.logReactSnapshot("post-load context");
1272
1298
  });
1273
1299
  this.onCDPEvent("Page.domContentEventFired", async (_event) => {
1274
1300
  this.logger("browser", "[DOM] DOM content loaded");
1275
- // Skip screenshot on DOM content loaded - we'll get one on page-loaded
1276
1301
  // Reinject interaction tracking on DOM content loaded
1277
1302
  await this.setupInteractionTracking();
1278
1303
  void this.logReactSnapshot("domcontentloaded context");
1279
1304
  });
1280
- // Network activity tracking for better screenshot timing
1281
- this.onCDPEvent("Network.requestWillBeSent", (_event) => {
1282
- this.pendingRequests++;
1283
- if (this.networkIdleTimer) {
1284
- clearTimeout(this.networkIdleTimer);
1285
- this.networkIdleTimer = null;
1286
- }
1287
- });
1288
- this.onCDPEvent("Network.loadingFinished", (_event) => {
1289
- this.pendingRequests--;
1290
- this.scheduleNetworkIdleScreenshot();
1291
- });
1292
- this.onCDPEvent("Network.loadingFailed", (_event) => {
1293
- this.pendingRequests--;
1294
- this.scheduleNetworkIdleScreenshot();
1295
- });
1296
1305
  // DOM mutations for interaction context
1297
1306
  this.onCDPEvent("DOM.documentUpdated", () => {
1298
1307
  // Document structure changed - useful for SPA routing
@@ -1325,6 +1334,7 @@ export class CDPMonitor {
1325
1334
  if (params.targetId === monitoredTarget && this.onWindowClosedCallback && !this.isShuttingDown) {
1326
1335
  this.debugLog("Chrome window was manually closed, triggering d3k shutdown");
1327
1336
  this.logger("browser", "[TARGET] Chrome window manually closed, shutting down d3k");
1337
+ this.prepareShutdown();
1328
1338
  this.onWindowClosedCallback();
1329
1339
  }
1330
1340
  });
@@ -1409,10 +1419,6 @@ export class CDPMonitor {
1409
1419
  this.logger("browser", `[CDP] Navigation failed: ${error}`);
1410
1420
  throw error;
1411
1421
  }
1412
- // Take a delayed screenshot to catch dynamic content
1413
- setTimeout(() => {
1414
- this.takeScreenshot("navigation-delayed");
1415
- }, 2000);
1416
1422
  // Set up interaction tracking - but be more efficient about it
1417
1423
  const trackingStartTime = Date.now();
1418
1424
  this.debugLog("Setting up interaction tracking");
@@ -1427,7 +1433,6 @@ export class CDPMonitor {
1427
1433
  this.debugLog(`Interaction tracking setup completed (${trackingTime}ms)`);
1428
1434
  // Start polling for interactions from the injected script
1429
1435
  this.startInteractionPolling();
1430
- // Multiple screenshot triggers will ensure we catch the initial page load
1431
1436
  }
1432
1437
  async navigateToApp(port, useHttps = false) {
1433
1438
  const protocol = useHttps ? "https" : "http";
@@ -1452,33 +1457,6 @@ export class CDPMonitor {
1452
1457
  if (!window.__dev3000_cdp_tracking) {
1453
1458
  window.__dev3000_cdp_tracking = true;
1454
1459
 
1455
- ${this.pluginReactScan
1456
- ? `
1457
- // Inject react-scan for React performance monitoring
1458
- if (!window.__REACT_SCAN_INJECTED__) {
1459
- const script = document.createElement('script');
1460
- script.src = 'https://unpkg.com/react-scan@latest/dist/auto.global.js';
1461
- script.onload = () => {
1462
- console.debug('[DEV3000] react-scan loaded successfully');
1463
- window.__REACT_SCAN_INJECTED__ = true;
1464
-
1465
- // Optional: Configure react-scan
1466
- if (window.ReactScan && window.ReactScan.configure) {
1467
- window.ReactScan.configure({
1468
- // Add any configuration options here
1469
- playSound: false,
1470
- showToolbar: true
1471
- });
1472
- }
1473
- };
1474
- script.onerror = (err) => {
1475
- console.debug('[DEV3000] Failed to load react-scan:', err);
1476
- };
1477
- document.head.appendChild(script);
1478
- }
1479
- `
1480
- : ""}
1481
-
1482
1460
  // Helper function to generate CSS selector for element
1483
1461
  function getElementSelector(el) {
1484
1462
  if (!el || el === document) return 'document';
@@ -1762,10 +1740,6 @@ export class CDPMonitor {
1762
1740
  const reactState = value.reactState || null;
1763
1741
  for (const interaction of interactions) {
1764
1742
  this.logger("browser", `[INTERACTION] ${interaction.message}`);
1765
- // Take screenshot when scroll settles
1766
- if (interaction.message.startsWith("SCROLL_SETTLED")) {
1767
- this.takeScreenshot("scroll-settled");
1768
- }
1769
1743
  }
1770
1744
  for (const reactEvent of reactEvents) {
1771
1745
  if (reactEvent.type === "renderer") {
@@ -1793,64 +1767,6 @@ export class CDPMonitor {
1793
1767
  pollInteractions();
1794
1768
  }, 1000);
1795
1769
  }
1796
- scheduleNetworkIdleScreenshot() {
1797
- // Only take network-idle screenshots after a navigation event
1798
- // This prevents screenshot spam from background AJAX/fetch polls
1799
- if (!this.navigationInProgress) {
1800
- return;
1801
- }
1802
- // Only schedule if we have 0 pending requests
1803
- if (this.pendingRequests === 0) {
1804
- if (this.networkIdleTimer) {
1805
- clearTimeout(this.networkIdleTimer);
1806
- }
1807
- // Wait 500ms of network idle before taking screenshot
1808
- this.networkIdleTimer = setTimeout(() => {
1809
- this.takeScreenshot("navigation-settled");
1810
- // Reset navigation flag since we've captured the settled state
1811
- this.navigationInProgress = false;
1812
- this.networkIdleTimer = null;
1813
- }, 500);
1814
- }
1815
- }
1816
- async takeScreenshot(event) {
1817
- if (!this.automaticScreenshots)
1818
- return null;
1819
- try {
1820
- // Throttle screenshots to avoid spam
1821
- const now = Date.now();
1822
- const timeSinceLastScreenshot = now - this.lastScreenshotTime;
1823
- // Special cases that should always take screenshots
1824
- const priorityEvents = ["error", "crash"];
1825
- // If not a priority event and we took a screenshot recently, skip it
1826
- if (!priorityEvents.includes(event) && timeSinceLastScreenshot < this.minScreenshotInterval) {
1827
- this.debugLog(`Skipping screenshot for ${event} - only ${timeSinceLastScreenshot}ms since last screenshot`);
1828
- return null;
1829
- }
1830
- // Update last screenshot time
1831
- this.lastScreenshotTime = now;
1832
- const result = await this.sendCDPCommand("Page.captureScreenshot", {
1833
- format: "png",
1834
- quality: 80,
1835
- clip: undefined, // Full viewport
1836
- fromSurface: true
1837
- });
1838
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
1839
- const filename = `${timestamp}-${event}.png`;
1840
- const screenshotPath = join(this.screenshotDir, filename);
1841
- // Save the base64 image
1842
- const resultWithData = result;
1843
- const buffer = Buffer.from(resultWithData.data, "base64");
1844
- writeFileSync(screenshotPath, buffer);
1845
- // Log screenshot with file path
1846
- this.logger("browser", `[SCREENSHOT] ${screenshotPath}`);
1847
- return filename;
1848
- }
1849
- catch (error) {
1850
- this.logger("browser", `[CDP] Screenshot failed: ${error}`);
1851
- return null;
1852
- }
1853
- }
1854
1770
  // Enhanced replay functionality using CDP
1855
1771
  async executeInteraction(interaction) {
1856
1772
  if (!this.connection) {