@desplega.ai/qa-use 2.14.1 → 2.15.1

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 (139) hide show
  1. package/README.md +23 -0
  2. package/dist/lib/api/index.d.ts +5 -1
  3. package/dist/lib/api/index.d.ts.map +1 -1
  4. package/dist/lib/api/index.js +112 -5
  5. package/dist/lib/api/index.js.map +1 -1
  6. package/dist/lib/api/sse.d.ts +22 -2
  7. package/dist/lib/api/sse.d.ts.map +1 -1
  8. package/dist/lib/api/sse.js +77 -5
  9. package/dist/lib/api/sse.js.map +1 -1
  10. package/dist/lib/env/index.d.ts +13 -0
  11. package/dist/lib/env/index.d.ts.map +1 -1
  12. package/dist/lib/env/index.js +35 -0
  13. package/dist/lib/env/index.js.map +1 -1
  14. package/dist/lib/env/localhost.d.ts +22 -0
  15. package/dist/lib/env/localhost.d.ts.map +1 -0
  16. package/dist/lib/env/localhost.js +49 -0
  17. package/dist/lib/env/localhost.js.map +1 -0
  18. package/dist/lib/env/paths.d.ts +27 -0
  19. package/dist/lib/env/paths.d.ts.map +1 -0
  20. package/dist/lib/env/paths.js +42 -0
  21. package/dist/lib/env/paths.js.map +1 -0
  22. package/dist/lib/env/sessions.d.ts +55 -0
  23. package/dist/lib/env/sessions.d.ts.map +1 -0
  24. package/dist/lib/env/sessions.js +128 -0
  25. package/dist/lib/env/sessions.js.map +1 -0
  26. package/dist/lib/tunnel/errors.d.ts +61 -0
  27. package/dist/lib/tunnel/errors.d.ts.map +1 -0
  28. package/dist/lib/tunnel/errors.js +152 -0
  29. package/dist/lib/tunnel/errors.js.map +1 -0
  30. package/dist/lib/tunnel/index.d.ts.map +1 -1
  31. package/dist/lib/tunnel/index.js +26 -11
  32. package/dist/lib/tunnel/index.js.map +1 -1
  33. package/dist/lib/tunnel/registry.d.ts +182 -0
  34. package/dist/lib/tunnel/registry.d.ts.map +1 -0
  35. package/dist/lib/tunnel/registry.js +561 -0
  36. package/dist/lib/tunnel/registry.js.map +1 -0
  37. package/dist/package.json +1 -1
  38. package/dist/src/cli/commands/browser/_detached.d.ts +27 -0
  39. package/dist/src/cli/commands/browser/_detached.d.ts.map +1 -0
  40. package/dist/src/cli/commands/browser/_detached.js +422 -0
  41. package/dist/src/cli/commands/browser/_detached.js.map +1 -0
  42. package/dist/src/cli/commands/browser/close.d.ts +7 -0
  43. package/dist/src/cli/commands/browser/close.d.ts.map +1 -1
  44. package/dist/src/cli/commands/browser/close.js +101 -5
  45. package/dist/src/cli/commands/browser/close.js.map +1 -1
  46. package/dist/src/cli/commands/browser/create.d.ts +7 -0
  47. package/dist/src/cli/commands/browser/create.d.ts.map +1 -1
  48. package/dist/src/cli/commands/browser/create.js +233 -25
  49. package/dist/src/cli/commands/browser/create.js.map +1 -1
  50. package/dist/src/cli/commands/browser/index.d.ts.map +1 -1
  51. package/dist/src/cli/commands/browser/index.js +3 -0
  52. package/dist/src/cli/commands/browser/index.js.map +1 -1
  53. package/dist/src/cli/commands/browser/run.d.ts.map +1 -1
  54. package/dist/src/cli/commands/browser/run.js +13 -6
  55. package/dist/src/cli/commands/browser/run.js.map +1 -1
  56. package/dist/src/cli/commands/browser/status.d.ts +4 -0
  57. package/dist/src/cli/commands/browser/status.d.ts.map +1 -1
  58. package/dist/src/cli/commands/browser/status.js +85 -3
  59. package/dist/src/cli/commands/browser/status.js.map +1 -1
  60. package/dist/src/cli/commands/doctor.d.ts +45 -0
  61. package/dist/src/cli/commands/doctor.d.ts.map +1 -0
  62. package/dist/src/cli/commands/doctor.js +267 -0
  63. package/dist/src/cli/commands/doctor.js.map +1 -0
  64. package/dist/src/cli/commands/test/run.d.ts.map +1 -1
  65. package/dist/src/cli/commands/test/run.js +33 -19
  66. package/dist/src/cli/commands/test/run.js.map +1 -1
  67. package/dist/src/cli/commands/tunnel/close.d.ts +18 -0
  68. package/dist/src/cli/commands/tunnel/close.d.ts.map +1 -0
  69. package/dist/src/cli/commands/tunnel/close.js +154 -0
  70. package/dist/src/cli/commands/tunnel/close.js.map +1 -0
  71. package/dist/src/cli/commands/tunnel/index.d.ts +6 -0
  72. package/dist/src/cli/commands/tunnel/index.d.ts.map +1 -0
  73. package/dist/src/cli/commands/tunnel/index.js +17 -0
  74. package/dist/src/cli/commands/tunnel/index.js.map +1 -0
  75. package/dist/src/cli/commands/tunnel/ls.d.ts +10 -0
  76. package/dist/src/cli/commands/tunnel/ls.d.ts.map +1 -0
  77. package/dist/src/cli/commands/tunnel/ls.js +89 -0
  78. package/dist/src/cli/commands/tunnel/ls.js.map +1 -0
  79. package/dist/src/cli/commands/tunnel/start.d.ts +15 -0
  80. package/dist/src/cli/commands/tunnel/start.d.ts.map +1 -0
  81. package/dist/src/cli/commands/tunnel/start.js +65 -0
  82. package/dist/src/cli/commands/tunnel/start.js.map +1 -0
  83. package/dist/src/cli/commands/tunnel/status.d.ts +8 -0
  84. package/dist/src/cli/commands/tunnel/status.d.ts.map +1 -0
  85. package/dist/src/cli/commands/tunnel/status.js +58 -0
  86. package/dist/src/cli/commands/tunnel/status.js.map +1 -0
  87. package/dist/src/cli/generated/docs-content.d.ts +1 -1
  88. package/dist/src/cli/generated/docs-content.d.ts.map +1 -1
  89. package/dist/src/cli/generated/docs-content.js +157 -100
  90. package/dist/src/cli/generated/docs-content.js.map +1 -1
  91. package/dist/src/cli/index.js +8 -0
  92. package/dist/src/cli/index.js.map +1 -1
  93. package/dist/src/cli/lib/browser.d.ts +25 -9
  94. package/dist/src/cli/lib/browser.d.ts.map +1 -1
  95. package/dist/src/cli/lib/browser.js +73 -42
  96. package/dist/src/cli/lib/browser.js.map +1 -1
  97. package/dist/src/cli/lib/cli-entry.d.ts +40 -0
  98. package/dist/src/cli/lib/cli-entry.d.ts.map +1 -0
  99. package/dist/src/cli/lib/cli-entry.js +65 -0
  100. package/dist/src/cli/lib/cli-entry.js.map +1 -0
  101. package/dist/src/cli/lib/runner.d.ts +6 -0
  102. package/dist/src/cli/lib/runner.d.ts.map +1 -1
  103. package/dist/src/cli/lib/runner.js +2 -2
  104. package/dist/src/cli/lib/runner.js.map +1 -1
  105. package/dist/src/cli/lib/startup-sweep.d.ts +45 -0
  106. package/dist/src/cli/lib/startup-sweep.d.ts.map +1 -0
  107. package/dist/src/cli/lib/startup-sweep.js +246 -0
  108. package/dist/src/cli/lib/startup-sweep.js.map +1 -0
  109. package/dist/src/cli/lib/tunnel-banner.d.ts +33 -0
  110. package/dist/src/cli/lib/tunnel-banner.d.ts.map +1 -0
  111. package/dist/src/cli/lib/tunnel-banner.js +55 -0
  112. package/dist/src/cli/lib/tunnel-banner.js.map +1 -0
  113. package/dist/src/cli/lib/tunnel-error-hint.d.ts +20 -0
  114. package/dist/src/cli/lib/tunnel-error-hint.d.ts.map +1 -0
  115. package/dist/src/cli/lib/tunnel-error-hint.js +48 -0
  116. package/dist/src/cli/lib/tunnel-error-hint.js.map +1 -0
  117. package/dist/src/cli/lib/tunnel-option.d.ts +27 -0
  118. package/dist/src/cli/lib/tunnel-option.d.ts.map +1 -0
  119. package/dist/src/cli/lib/tunnel-option.js +77 -0
  120. package/dist/src/cli/lib/tunnel-option.js.map +1 -0
  121. package/dist/src/cli/lib/tunnel-resolve.d.ts +42 -0
  122. package/dist/src/cli/lib/tunnel-resolve.d.ts.map +1 -0
  123. package/dist/src/cli/lib/tunnel-resolve.js +72 -0
  124. package/dist/src/cli/lib/tunnel-resolve.js.map +1 -0
  125. package/lib/api/index.ts +136 -6
  126. package/lib/api/sse.test.ts +530 -0
  127. package/lib/api/sse.ts +105 -5
  128. package/lib/env/index.ts +51 -0
  129. package/lib/env/localhost.test.ts +63 -0
  130. package/lib/env/localhost.ts +51 -0
  131. package/lib/env/paths.ts +46 -0
  132. package/lib/env/sessions.test.ts +109 -0
  133. package/lib/env/sessions.ts +155 -0
  134. package/lib/tunnel/errors.test.ts +105 -0
  135. package/lib/tunnel/errors.ts +169 -0
  136. package/lib/tunnel/index.ts +26 -11
  137. package/lib/tunnel/registry.test.ts +420 -0
  138. package/lib/tunnel/registry.ts +646 -0
  139. package/package.json +1 -1
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Hidden `__browser-detach` subcommand — runs inside the detached child
3
+ * spawned by the parent `browser create` command.
4
+ *
5
+ * Responsibilities:
6
+ * 1. Start the local browser (Playwright).
7
+ * 2. If tunnel mode is 'on', acquire a tunnel via `TunnelRegistry`.
8
+ * 3. Register a backend session via the API.
9
+ * 4. Write a PID file at `~/.qa-use/sessions/<spawn-id>.json` for the
10
+ * parent to poll. Updated in place as milestones complete.
11
+ * 5. Install SIGTERM/SIGINT handlers that tear down cleanly:
12
+ * API session end → tunnel release → browser close → remove PID file.
13
+ * 6. Run a 30s heartbeat that verifies API session + tunnel health; on
14
+ * external close, self-terminate.
15
+ *
16
+ * This subcommand is `.hidden()` on the Commander definition so it does
17
+ * not appear in `qa-use browser --help`. It is ONLY invoked by the
18
+ * parent `browser create` bootstrap via `child_process.spawn`.
19
+ */
20
+ import { Command } from 'commander';
21
+ import { BrowserManager } from '../../../../lib/browser/index.js';
22
+ import { getAgentSessionId } from '../../../../lib/env/index.js';
23
+ import { removeSessionRecord, sessionFilePath, writeSessionRecord, } from '../../../../lib/env/sessions.js';
24
+ import { classifyTunnelFailure, TunnelError } from '../../../../lib/tunnel/errors.js';
25
+ import { canonicalTarget, tunnelRegistry, } from '../../../../lib/tunnel/registry.js';
26
+ import { createStoredSession, removeStoredSession, storeSession, } from '../../lib/browser-sessions.js';
27
+ import { createBrowserClient, loadConfig } from '../../lib/config.js';
28
+ /**
29
+ * Silence stdout/stderr after startup. The parent spawns us with
30
+ * `{ stdio: 'ignore' }` so these fds are already pointed at /dev/null —
31
+ * we additionally muzzle `console.log`/`console.error` so any stray log
32
+ * statements don't accidentally write to a reused fd.
33
+ */
34
+ function muzzleConsole() {
35
+ const noop = () => {
36
+ /* intentional no-op */
37
+ };
38
+ console.log = noop;
39
+ console.error = noop;
40
+ console.warn = noop;
41
+ console.info = noop;
42
+ }
43
+ async function runDetached(spawnId, options) {
44
+ // Resource bookkeeping for cleanup. Declared up-front so the outer
45
+ // try/catch (around the whole startup sequence) can reference them.
46
+ let browser = null;
47
+ let tunnelHandle = null;
48
+ let backendSessionId = null;
49
+ let heartbeatTimer = null;
50
+ let shuttingDown = false;
51
+ // Set to true when we've written a `phase: 'failed'` record that the
52
+ // parent still needs to read. Prevents `cleanup()` from deleting it.
53
+ let preserveSpawnRecord = false;
54
+ // Deferred client reference — populated once config is loaded. Before
55
+ // that point `cleanup()` treats the backend session as absent.
56
+ let client = null;
57
+ const cleanup = async (exitCode) => {
58
+ if (shuttingDown)
59
+ return;
60
+ shuttingDown = true;
61
+ if (heartbeatTimer) {
62
+ clearInterval(heartbeatTimer);
63
+ heartbeatTimer = null;
64
+ }
65
+ // Close API session.
66
+ if (backendSessionId && client) {
67
+ try {
68
+ await client.deleteSession(backendSessionId);
69
+ }
70
+ catch {
71
+ /* best-effort */
72
+ }
73
+ try {
74
+ await removeStoredSession(backendSessionId);
75
+ }
76
+ catch {
77
+ /* best-effort */
78
+ }
79
+ }
80
+ // Release tunnel handle.
81
+ if (tunnelHandle) {
82
+ try {
83
+ await tunnelRegistry.release(tunnelHandle);
84
+ }
85
+ catch {
86
+ /* best-effort */
87
+ }
88
+ tunnelHandle = null;
89
+ }
90
+ // Close local browser.
91
+ if (browser) {
92
+ try {
93
+ await browser.stopBrowser();
94
+ }
95
+ catch {
96
+ /* best-effort */
97
+ }
98
+ browser = null;
99
+ }
100
+ // Remove PID file.
101
+ if (backendSessionId) {
102
+ removeSessionRecord(backendSessionId);
103
+ }
104
+ // Only remove the spawn-id record when we didn't write a failure
105
+ // payload the parent still needs to read.
106
+ if (!preserveSpawnRecord) {
107
+ removeSessionRecord(spawnId);
108
+ }
109
+ process.exit(exitCode);
110
+ };
111
+ // Helper: write a structured failure record and mark it for preservation
112
+ // so the parent poll loop can read it before cleanup wipes the file.
113
+ const reportFailure = (err, labelPrefix) => {
114
+ preserveSpawnRecord = true;
115
+ writePhase({
116
+ spawnId,
117
+ phase: 'failed',
118
+ errorObject: serializeError(err, labelPrefix),
119
+ });
120
+ };
121
+ process.on('SIGTERM', () => {
122
+ void cleanup(0);
123
+ });
124
+ process.on('SIGINT', () => {
125
+ void cleanup(0);
126
+ });
127
+ try {
128
+ // Phase 1: write initial PID file under spawnId so the parent can see
129
+ // that the child is alive. This happens BEFORE any expensive work.
130
+ writePhase({
131
+ spawnId,
132
+ phase: 'starting',
133
+ target: options.target ?? '',
134
+ });
135
+ // Config + option parsing — moved inside the try/catch so any error
136
+ // here (missing API key, malformed --var-json, etc.) is surfaced to
137
+ // the parent as a structured failure record.
138
+ const config = await loadConfig();
139
+ if (!config.api_key) {
140
+ reportFailure(new Error('API key not configured'), 'ConfigError');
141
+ await cleanup(1);
142
+ return;
143
+ }
144
+ client = createBrowserClient(config);
145
+ const viewport = (options.viewport ?? 'desktop');
146
+ const timeout = options.timeout ? parseInt(options.timeout, 10) : 300;
147
+ const headless = options.headless === true;
148
+ const tunnelOn = options.tunnelMode === 'on';
149
+ const vars = options.varJson
150
+ ? JSON.parse(options.varJson)
151
+ : undefined;
152
+ const sessionIndex = options.sessionIndex ? parseInt(options.sessionIndex, 10) : 0;
153
+ // Phase 2: start local browser.
154
+ browser = new BrowserManager();
155
+ const browserResult = await browser.startBrowser({ headless });
156
+ const wsEndpoint = browserResult.wsEndpoint;
157
+ let wsUrlToUse = wsEndpoint;
158
+ let publicUrlForRecord = null;
159
+ // Phase 3: acquire tunnel if needed.
160
+ if (tunnelOn) {
161
+ try {
162
+ tunnelHandle = await tunnelRegistry.acquire(wsEndpoint, {
163
+ apiKey: config.api_key,
164
+ sessionIndex,
165
+ subdomain: options.subdomain,
166
+ });
167
+ }
168
+ catch (err) {
169
+ const classified = err instanceof TunnelError ? err : classifyTunnelFailure(err, { target: wsEndpoint });
170
+ reportFailure(classified, 'tunnel_start_failed');
171
+ await cleanup(1);
172
+ return;
173
+ }
174
+ publicUrlForRecord = tunnelHandle.publicUrl;
175
+ // Resolve tunneled WS URL for the backend.
176
+ if (tunnelHandle.isCrossProcessAttach) {
177
+ try {
178
+ const wsPath = new URL(wsEndpoint).pathname;
179
+ wsUrlToUse =
180
+ tunnelHandle.publicUrl.replace('https://', 'wss://').replace('http://', 'ws://') +
181
+ wsPath;
182
+ }
183
+ catch (err) {
184
+ reportFailure(err, 'tunnel_ws_url_resolution_failed');
185
+ await cleanup(1);
186
+ return;
187
+ }
188
+ }
189
+ else {
190
+ const liveManager = tunnelRegistry.getLiveManager(wsEndpoint);
191
+ const tunneled = liveManager?.getWebSocketUrl(wsEndpoint);
192
+ if (!tunneled) {
193
+ reportFailure(new Error('Failed to resolve tunneled WebSocket URL'), 'tunnel_ws_url_resolution_failed');
194
+ await cleanup(1);
195
+ return;
196
+ }
197
+ wsUrlToUse = tunneled;
198
+ // Health-check with warmup retries (mirrors legacy logic).
199
+ let ready = false;
200
+ for (let attempt = 0; attempt < 15; attempt++) {
201
+ if (await liveManager.checkHealth()) {
202
+ ready = true;
203
+ break;
204
+ }
205
+ await new Promise((r) => setTimeout(r, 500));
206
+ }
207
+ if (!ready) {
208
+ reportFailure(new Error('Tunnel health check failed — tunnel is not proxying connections'), 'tunnel_health_check_failed');
209
+ await cleanup(1);
210
+ return;
211
+ }
212
+ }
213
+ writePhase({
214
+ spawnId,
215
+ phase: 'tunnel_ready',
216
+ target: canonicalTarget(wsEndpoint),
217
+ publicUrl: publicUrlForRecord,
218
+ });
219
+ }
220
+ else {
221
+ writePhase({
222
+ spawnId,
223
+ phase: 'browser_ready',
224
+ target: canonicalTarget(wsEndpoint),
225
+ });
226
+ }
227
+ // Phase 4: create backend session with resolved ws_url.
228
+ let session;
229
+ try {
230
+ session = await client.createSession({
231
+ viewport,
232
+ timeout,
233
+ ws_url: tunnelOn ? wsUrlToUse : undefined,
234
+ after_test_id: options.afterTestId,
235
+ vars,
236
+ agent_session_id: getAgentSessionId(),
237
+ start_url: options.startUrl,
238
+ });
239
+ }
240
+ catch (err) {
241
+ reportFailure(err, 'api_session_create_failed');
242
+ await cleanup(1);
243
+ return;
244
+ }
245
+ backendSessionId = session.id;
246
+ // Write the real session record — now keyed by backend session id.
247
+ const record = {
248
+ id: session.id,
249
+ pid: process.pid,
250
+ target: canonicalTarget(options.target ?? wsEndpoint),
251
+ publicUrl: publicUrlForRecord,
252
+ startedAt: new Date().toISOString(),
253
+ ttlExpiresAt: Date.now() + timeout * 1000,
254
+ crossProcessTunnel: tunnelHandle?.isCrossProcessAttach === true,
255
+ subdomain: options.subdomain,
256
+ viewport,
257
+ headless,
258
+ };
259
+ writeSessionRecord(record);
260
+ // Remove the pending spawnId file now that we have the real session.
261
+ if (spawnId !== session.id) {
262
+ removeSessionRecord(spawnId);
263
+ }
264
+ await storeSession(createStoredSession(session.id));
265
+ // Wait for session active (if starting). We don't surface this to
266
+ // the parent — the parent has already returned.
267
+ if (session.status === 'starting') {
268
+ try {
269
+ const waitTimeout = options.afterTestId ? 180_000 : 60_000;
270
+ await client.waitForStatus(session.id, 'active', waitTimeout);
271
+ }
272
+ catch {
273
+ // Session creation failed post-handoff. Let the heartbeat below
274
+ // detect the closed state and tear down.
275
+ }
276
+ }
277
+ // Phase 5: heartbeat — verify backend session + tunnel health.
278
+ // Capture client in a non-nullable local so the closure doesn't need
279
+ // repeated null checks (it's guaranteed non-null at this point).
280
+ const heartbeatClient = client;
281
+ heartbeatTimer = setInterval(async () => {
282
+ if (shuttingDown)
283
+ return;
284
+ try {
285
+ const status = await heartbeatClient.getSession(session.id);
286
+ if (status.status === 'closed' || status.status === 'failed') {
287
+ await cleanup(0);
288
+ return;
289
+ }
290
+ // Tunnel health — only meaningful when we own the live manager.
291
+ if (tunnelHandle && !tunnelHandle.isCrossProcessAttach) {
292
+ const manager = tunnelRegistry.getLiveManager(wsEndpoint);
293
+ if (manager) {
294
+ await manager.checkHealth();
295
+ }
296
+ }
297
+ }
298
+ catch (err) {
299
+ const msg = err instanceof Error ? err.message : String(err);
300
+ if (msg.includes('not found') || msg.includes('404')) {
301
+ await cleanup(0);
302
+ }
303
+ }
304
+ }, 30_000);
305
+ if (typeof heartbeatTimer.unref === 'function') {
306
+ heartbeatTimer.unref();
307
+ }
308
+ // After startup, muzzle all console output. The parent gave us
309
+ // `{ stdio: 'ignore' }` anyway, but this belt-and-braces ensures
310
+ // that even libraries that try to log don't spam unknown fds.
311
+ muzzleConsole();
312
+ // Keep the event loop alive indefinitely — heartbeat timer isn't
313
+ // enough because of `.unref()`. Use a long interval with ref().
314
+ const aliveTimer = setInterval(() => {
315
+ /* keep alive */
316
+ }, 60_000);
317
+ // Hold a reference so the process doesn't exit prematurely.
318
+ void aliveTimer;
319
+ }
320
+ catch (err) {
321
+ reportFailure(err);
322
+ await cleanup(1);
323
+ }
324
+ }
325
+ /**
326
+ * Serialize an unknown error value into a structured payload the parent
327
+ * can surface cleanly. Guards against circular refs, truncates stacks to
328
+ * the first 5 lines, and prefixes `name` with an optional label for
329
+ * failure-site context (e.g. `tunnel_start_failed`).
330
+ */
331
+ function serializeError(err, labelPrefix) {
332
+ let message;
333
+ let name;
334
+ let stack;
335
+ if (err instanceof Error) {
336
+ message = err.message || 'Unknown error';
337
+ name = err.name || 'Error';
338
+ if (typeof err.stack === 'string') {
339
+ stack = err.stack.split('\n').slice(0, 5).join('\n');
340
+ }
341
+ }
342
+ else {
343
+ name = 'NonError';
344
+ try {
345
+ message = typeof err === 'string' ? err : JSON.stringify(err);
346
+ }
347
+ catch {
348
+ message = String(err);
349
+ }
350
+ }
351
+ if (labelPrefix) {
352
+ name = `${labelPrefix}:${name}`;
353
+ }
354
+ return stack ? { message, name, stack } : { message, name };
355
+ }
356
+ /**
357
+ * Write a "pending" record under the spawn id so the parent can poll
358
+ * progress. This is a minimal variant of `DetachedSessionRecord` that
359
+ * carries just enough state for the parent. Once the backend session id
360
+ * is known, we switch to writing a full record keyed by session id.
361
+ */
362
+ function writePhase(phase) {
363
+ const record = {
364
+ id: phase.spawnId,
365
+ pid: process.pid,
366
+ target: phase.target ?? '',
367
+ publicUrl: phase.publicUrl ?? null,
368
+ startedAt: new Date().toISOString(),
369
+ ttlExpiresAt: Date.now() + 60_000,
370
+ phase: phase.phase,
371
+ error: phase.errorObject,
372
+ spawnId: phase.spawnId,
373
+ failedAt: phase.phase === 'failed' ? Date.now() : undefined,
374
+ };
375
+ try {
376
+ writeSessionRecord(record);
377
+ }
378
+ catch {
379
+ /* best-effort */
380
+ }
381
+ }
382
+ export const detachedCommand = new Command('__browser-detach')
383
+ .description('(internal) Detached browser-session lifecycle holder')
384
+ .argument('<spawn-id>', 'Spawn token generated by the parent')
385
+ .option('--target <url>', 'Tunnel target URL (browser WS endpoint source)')
386
+ .option('--tunnel-mode <mode>', 'on | off')
387
+ .option('-s, --subdomain <name>', 'Deterministic tunnel subdomain')
388
+ .option('--viewport <type>', 'Viewport type', 'desktop')
389
+ .option('--timeout <seconds>', 'Session timeout', '300')
390
+ .option('--headless', 'Headless browser', false)
391
+ .option('--after-test-id <id>', 'After-test to run')
392
+ .option('--start-url <url>', 'Start URL')
393
+ .option('--var-json <json>', 'Variables as JSON string')
394
+ .option('--session-index <idx>', 'Tunnel session index', '0')
395
+ .action(async (spawnId, options) => {
396
+ try {
397
+ await runDetached(spawnId, options);
398
+ }
399
+ catch (err) {
400
+ // Belt-and-braces: if `runDetached` somehow throws out of its own
401
+ // try/catch (should not happen), still persist a failure record so
402
+ // the parent doesn't time out with the generic readiness message.
403
+ try {
404
+ writePhase({
405
+ spawnId,
406
+ phase: 'failed',
407
+ errorObject: serializeError(err, 'fatal'),
408
+ });
409
+ }
410
+ catch {
411
+ /* best-effort */
412
+ }
413
+ process.exit(1);
414
+ }
415
+ });
416
+ detachedCommand._hidden = true;
417
+ /**
418
+ * Convenience export for the lookup table used by PID-file consumers.
419
+ * Kept in sync with `DetachedSessionRecord` shape.
420
+ */
421
+ export const __detachedPhaseFileName = (spawnId) => sessionFilePath(spawnId);
422
+ //# sourceMappingURL=_detached.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_detached.js","sourceRoot":"","sources":["../../../../../src/cli/commands/browser/_detached.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,12 @@
1
1
  /**
2
2
  * qa-use browser close - Close a browser session
3
+ *
4
+ * Phase 4: when a PID file exists under `~/.qa-use/sessions/<id>.json`,
5
+ * the session is a detached child. SIGTERM the child, wait up to 5s for
6
+ * the PID file to disappear (child cleans up on signal), then SIGKILL
7
+ * as a fallback. Also calls the backend session delete for
8
+ * consistency and prints a one-line grace hint when a tunnel handle
9
+ * kept the tunnel alive post-close.
3
10
  */
4
11
  import { Command } from 'commander';
5
12
  export declare const closeCommand: Command;
@@ -1 +1 @@
1
- {"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/browser/close.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,YAAY,SAuCrB,CAAC"}
1
+ {"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/browser/close.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqCpC,eAAO,MAAM,YAAY,SAyHrB,CAAC"}
@@ -1,13 +1,43 @@
1
1
  /**
2
2
  * qa-use browser close - Close a browser session
3
+ *
4
+ * Phase 4: when a PID file exists under `~/.qa-use/sessions/<id>.json`,
5
+ * the session is a detached child. SIGTERM the child, wait up to 5s for
6
+ * the PID file to disappear (child cleans up on signal), then SIGKILL
7
+ * as a fallback. Also calls the backend session delete for
8
+ * consistency and prints a one-line grace hint when a tunnel handle
9
+ * kept the tunnel alive post-close.
3
10
  */
11
+ import fs from 'node:fs';
4
12
  import { Command } from 'commander';
13
+ import { isPidAlive, readSessionRecord, removeSessionRecord, sessionFilePath, } from '../../../../lib/env/sessions.js';
14
+ import { canonicalTarget, tunnelRegistry } from '../../../../lib/tunnel/registry.js';
5
15
  import { removeStoredSession, resolveSessionId } from '../../lib/browser-sessions.js';
6
16
  import { createBrowserClient, loadConfig } from '../../lib/config.js';
7
- import { error, info, success } from '../../lib/output.js';
17
+ import { error, info, success, warning } from '../../lib/output.js';
18
+ const SIGTERM_GRACE_MS = 5_000;
19
+ function fileExists(path) {
20
+ try {
21
+ return fs.existsSync(path);
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ async function waitForFileGone(path, graceMs) {
28
+ const deadline = Date.now() + graceMs;
29
+ while (Date.now() < deadline) {
30
+ if (!fileExists(path))
31
+ return true;
32
+ await new Promise((r) => setTimeout(r, 100));
33
+ }
34
+ return !fileExists(path);
35
+ }
8
36
  export const closeCommand = new Command('close')
9
37
  .description('Close a browser session')
10
38
  .option('-s, --session-id <id>', 'Session ID (auto-resolved if only one session)')
39
+ .option('--json', 'Output as JSON')
40
+ .option('--quiet', 'Suppress non-essential stderr output')
11
41
  .action(async (options) => {
12
42
  try {
13
43
  // Load configuration
@@ -18,16 +48,82 @@ export const closeCommand = new Command('close')
18
48
  }
19
49
  // Create client and set API key
20
50
  const client = createBrowserClient(config);
21
- // Resolve session ID
51
+ // Resolve session ID (verify=false — the detached child might have
52
+ // already cleaned the backend session; we still want to reap the
53
+ // PID file regardless)
22
54
  const resolved = await resolveSessionId({
23
55
  explicitId: options.sessionId,
24
56
  client,
57
+ verify: false,
25
58
  });
26
- console.log(info(`Closing session ${resolved.id}...`));
27
- // Close session on API
28
- await client.deleteSession(resolved.id);
59
+ if (!options.json) {
60
+ console.log(info(`Closing session ${resolved.id}...`));
61
+ }
62
+ // Detached-child path: check for a PID file.
63
+ const record = readSessionRecord(resolved.id);
64
+ let sigkilled = false;
65
+ let pidFileCleared = false;
66
+ if (record?.pid && isPidAlive(record.pid)) {
67
+ try {
68
+ process.kill(record.pid, 'SIGTERM');
69
+ }
70
+ catch {
71
+ /* already gone */
72
+ }
73
+ pidFileCleared = await waitForFileGone(sessionFilePath(resolved.id), SIGTERM_GRACE_MS);
74
+ if (!pidFileCleared) {
75
+ // Child didn't clean up — SIGKILL fallback.
76
+ try {
77
+ process.kill(record.pid, 'SIGKILL');
78
+ sigkilled = true;
79
+ if (!options.quiet && !options.json) {
80
+ console.log(warning(`Session PID ${record.pid} did not exit on SIGTERM within ${SIGTERM_GRACE_MS}ms; sent SIGKILL`));
81
+ }
82
+ }
83
+ catch {
84
+ /* already gone */
85
+ }
86
+ // Remove stale PID file ourselves.
87
+ removeSessionRecord(resolved.id);
88
+ }
89
+ }
90
+ else if (record) {
91
+ // PID file exists but owner is gone — just remove it.
92
+ removeSessionRecord(resolved.id);
93
+ }
94
+ // Close session on backend (best-effort — the child may have done it).
95
+ try {
96
+ await client.deleteSession(resolved.id);
97
+ }
98
+ catch {
99
+ /* best-effort — backend may already report closed */
100
+ }
29
101
  // Remove from local storage
30
102
  await removeStoredSession(resolved.id);
103
+ // Grace-period hint: when the session released a tunnel handle but
104
+ // the registry entry lingers (refcount 0 + grace window), let the
105
+ // user know how to force tear-down.
106
+ if (record?.target) {
107
+ const canon = canonicalTarget(record.target);
108
+ const lingering = tunnelRegistry.get(canon);
109
+ if (lingering?.ttlExpiresAt &&
110
+ lingering.refcount === 0 &&
111
+ !options.json &&
112
+ !options.quiet) {
113
+ const remainMs = Math.max(0, lingering.ttlExpiresAt - Date.now());
114
+ const remainSeconds = Math.ceil(remainMs / 1000);
115
+ console.error(`✓ Session ${resolved.id} closed. Tunnel ${canon} kept alive ~${remainSeconds}s (grace) — run \`qa-use tunnel close ${canon}\` to tear it down now.`);
116
+ }
117
+ }
118
+ if (options.json) {
119
+ console.log(JSON.stringify({
120
+ closed: true,
121
+ id: resolved.id,
122
+ sigkilled,
123
+ pid: record?.pid ?? null,
124
+ }, null, 2));
125
+ return;
126
+ }
31
127
  console.log(success(`Session ${resolved.id} closed successfully`));
32
128
  // Show source if auto-resolved
33
129
  if (resolved.source === 'stored') {
@@ -1 +1 @@
1
- {"version":3,"file":"close.js","sourceRoot":"","sources":["../../../../../src/cli/commands/browser/close.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAM3D,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,yBAAyB,CAAC;KACtC,MAAM,CAAC,uBAAuB,EAAE,gDAAgD,CAAC;KACjF,MAAM,CAAC,KAAK,EAAE,OAAqB,EAAE,EAAE;IACtC,IAAI,CAAC;QACH,qBAAqB;QACrB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC,CAAC;YACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,gCAAgC;QAChC,MAAM,MAAM,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAE3C,qBAAqB;QACrB,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC;YACtC,UAAU,EAAE,OAAO,CAAC,SAAS;YAC7B,MAAM;SACP,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAEvD,uBAAuB;QACvB,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAExC,4BAA4B;QAC5B,MAAM,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAEvC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,QAAQ,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC;QAEnE,+BAA+B;QAC/B,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"close.js","sourceRoot":"","sources":["../../../../../src/cli/commands/browser/close.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,12 @@
1
1
  /**
2
2
  * qa-use browser create - Create a new browser session
3
+ *
4
+ * Phase 4: when tunnel mode is 'on', the lifecycle is handed off to a
5
+ * detached child process (re-exec of the CLI with the hidden
6
+ * `__browser-detach` subcommand). The parent returns in < 2 s after
7
+ * printing the session id + public URL. For `QA_USE_DETACH=0` we
8
+ * preserve the legacy blocking flow for one release as a rollback
9
+ * escape hatch.
3
10
  */
4
11
  import { Command } from 'commander';
5
12
  export declare const createCommand: Command;
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/browser/create.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,eAAO,MAAM,aAAa,SAqEtB,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/browser/create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4CpC,eAAO,MAAM,aAAa,SAsFxB,CAAC"}