@ait-co/devtools 0.1.128 → 0.1.131

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 (113) hide show
  1. package/dist/{attach-orchestrator-CPMQWkGV.js → attach-orchestrator-BiaYiNOr.js} +34 -107
  2. package/dist/attach-orchestrator-BiaYiNOr.js.map +1 -0
  3. package/dist/{attach-orchestrator-RhOdO3DS.js → attach-orchestrator-Cjdhir2U.js} +31 -13
  4. package/dist/{attach-orchestrator-RhOdO3DS.js.map → attach-orchestrator-Cjdhir2U.js.map} +1 -1
  5. package/dist/{attach-orchestrator-CpE0dMew.js → attach-orchestrator-ca34r46y.js} +33 -106
  6. package/dist/attach-orchestrator-ca34r46y.js.map +1 -0
  7. package/dist/{bundle-Cb3GFuw6.d.ts → bundle-BKqyhEK9.d.ts} +1 -1
  8. package/dist/{bundle-Cb3GFuw6.d.ts.map → bundle-BKqyhEK9.d.ts.map} +1 -1
  9. package/dist/{capture-B1zfGyTo.d.ts → capture-ltuV0gZa.d.ts} +1 -1
  10. package/dist/{capture-B1zfGyTo.d.ts.map → capture-ltuV0gZa.d.ts.map} +1 -1
  11. package/dist/{cdp-connection-Dxw_EY8s.d.ts → cdp-connection-D7AYQck2.d.ts} +1 -1
  12. package/dist/{cdp-connection-Dxw_EY8s.d.ts.map → cdp-connection-D7AYQck2.d.ts.map} +1 -1
  13. package/dist/{cell-D0dJSGHi.js → cell-Bi8MAPA8.js} +11 -7
  14. package/dist/cell-Bi8MAPA8.js.map +1 -0
  15. package/dist/{cell-Ba9lCI3B.js → cell-Cw4fVOl2.js} +11 -7
  16. package/dist/cell-Cw4fVOl2.js.map +1 -0
  17. package/dist/{cell-CX92lDI0.js → cell-rr6gL9AT.js} +11 -7
  18. package/dist/cell-rr6gL9AT.js.map +1 -0
  19. package/dist/debug-server-BSbfnFR5.js +436 -0
  20. package/dist/debug-server-BSbfnFR5.js.map +1 -0
  21. package/dist/debug-server-CD4zYBq4.js +379 -0
  22. package/dist/{debug-server-BzzNkETg.js.map → debug-server-CD4zYBq4.js.map} +1 -1
  23. package/dist/{debug-server-Dctc9GAh.js → debug-server-qRSRG7YZ.js} +68 -65
  24. package/dist/debug-server-qRSRG7YZ.js.map +1 -0
  25. package/dist/in-app/auto.js +15 -0
  26. package/dist/in-app/auto.js.map +1 -1
  27. package/dist/in-app/index.d.ts +27 -0
  28. package/dist/in-app/index.d.ts.map +1 -1
  29. package/dist/in-app/index.js +15 -0
  30. package/dist/in-app/index.js.map +1 -1
  31. package/dist/log-CUik_tHp.js +95 -0
  32. package/dist/log-CUik_tHp.js.map +1 -0
  33. package/dist/mcp/cli.js +1272 -1111
  34. package/dist/mcp/cli.js.map +1 -1
  35. package/dist/mcp/server.js +1 -1
  36. package/dist/panel/index.js +11 -1
  37. package/dist/panel/index.js.map +1 -1
  38. package/dist/{pool-DBLKNeRC.d.ts → pool-BaSCZALv.d.ts} +12 -4
  39. package/dist/{pool-DBLKNeRC.d.ts.map → pool-BaSCZALv.d.ts.map} +1 -1
  40. package/dist/{qr-http-server-Bo8LMOJQ.js → qr-http-server-BdNGPRoL.js} +76 -6
  41. package/dist/qr-http-server-BdNGPRoL.js.map +1 -0
  42. package/dist/{qr-http-server-CrGohOwC.js → qr-http-server-Bq9zR7-z.js} +76 -6
  43. package/dist/qr-http-server-Bq9zR7-z.js.map +1 -0
  44. package/dist/{qr-http-server-Bt_RAxFn.cjs → qr-http-server-BxoRp2w7.cjs} +76 -6
  45. package/dist/qr-http-server-BxoRp2w7.cjs.map +1 -0
  46. package/dist/{qr-http-server-DM2R25jP.cjs → qr-http-server-CV6TA9ZN.cjs} +76 -6
  47. package/dist/qr-http-server-CV6TA9ZN.cjs.map +1 -0
  48. package/dist/{qr-http-server-s1iW_5VQ.js → qr-http-server-DCbjORFa.js} +76 -6
  49. package/dist/qr-http-server-DCbjORFa.js.map +1 -0
  50. package/dist/{qr-http-server-BH0UFeUn.js → qr-http-server-cKbciY5k.js} +76 -6
  51. package/dist/qr-http-server-cKbciY5k.js.map +1 -0
  52. package/dist/{relay-factory-Dug7W6Ao.js → relay-factory-Cloog1jb.js} +34 -14
  53. package/dist/relay-factory-Cloog1jb.js.map +1 -0
  54. package/dist/{relay-secret-store-DKxs7zwq.js → relay-secret-store-DzxDsK6S.js} +1 -1
  55. package/dist/{relay-secret-store-DKxs7zwq.js.map → relay-secret-store-DzxDsK6S.js.map} +1 -1
  56. package/dist/{relay-url-store-xmUuTjXA.js → relay-url-store-D3rY-GJJ.js} +2 -2
  57. package/dist/{relay-url-store-xmUuTjXA.js.map → relay-url-store-D3rY-GJJ.js.map} +1 -1
  58. package/dist/{debug-server-BzzNkETg.js → relay-worker-CCAHLC4a.js} +220 -350
  59. package/dist/relay-worker-CCAHLC4a.js.map +1 -0
  60. package/dist/{relay-worker-TReZtzGl.d.ts → relay-worker-o0M2ndyJ.d.ts} +7 -7
  61. package/dist/relay-worker-o0M2ndyJ.d.ts.map +1 -0
  62. package/dist/{runtime-ozyPnRbU.d.ts → runtime-9xhN9pr8.d.ts} +1 -1
  63. package/dist/{runtime-ozyPnRbU.d.ts.map → runtime-9xhN9pr8.d.ts.map} +1 -1
  64. package/dist/test-runner/bin.js +744 -25
  65. package/dist/test-runner/bin.js.map +1 -1
  66. package/dist/test-runner/bundle.d.ts +1 -1
  67. package/dist/test-runner/capture.d.ts +1 -1
  68. package/dist/test-runner/config.d.ts +15 -9
  69. package/dist/test-runner/config.d.ts.map +1 -1
  70. package/dist/test-runner/config.js +1 -1
  71. package/dist/test-runner/pool.d.ts +1 -1
  72. package/dist/test-runner/pool.js +1 -1
  73. package/dist/test-runner/pool.js.map +1 -1
  74. package/dist/test-runner/relay-factory.d.ts +23 -9
  75. package/dist/test-runner/relay-factory.d.ts.map +1 -1
  76. package/dist/test-runner/relay-factory.js +32 -12
  77. package/dist/test-runner/relay-factory.js.map +1 -1
  78. package/dist/test-runner/relay-worker.d.ts +1 -1
  79. package/dist/test-runner/relay-worker.js +1 -187
  80. package/dist/test-runner/report.d.ts +3 -3
  81. package/dist/test-runner/rpc.d.ts +2 -2
  82. package/dist/test-runner/runtime.d.ts +1 -1
  83. package/dist/test-runner/task-graph.d.ts +1 -1
  84. package/dist/{tunnel-DyEV6WwL.cjs → tunnel-B2UCKAc7.cjs} +4 -3
  85. package/dist/{tunnel-DyEV6WwL.cjs.map → tunnel-B2UCKAc7.cjs.map} +1 -1
  86. package/dist/{tunnel-BFwOSwT4.js → tunnel-C-rQ34dM.js} +4 -3
  87. package/dist/{tunnel-BFwOSwT4.js.map → tunnel-C-rQ34dM.js.map} +1 -1
  88. package/dist/unplugin/index.cjs +1 -1
  89. package/dist/unplugin/index.js +1 -1
  90. package/dist/unplugin/tunnel.cjs +3 -2
  91. package/dist/unplugin/tunnel.cjs.map +1 -1
  92. package/dist/unplugin/tunnel.d.cts.map +1 -1
  93. package/dist/unplugin/tunnel.d.ts.map +1 -1
  94. package/dist/unplugin/tunnel.js +3 -2
  95. package/dist/unplugin/tunnel.js.map +1 -1
  96. package/package.json +1 -1
  97. package/dist/attach-orchestrator-CPMQWkGV.js.map +0 -1
  98. package/dist/attach-orchestrator-CpE0dMew.js.map +0 -1
  99. package/dist/cell-Ba9lCI3B.js.map +0 -1
  100. package/dist/cell-CX92lDI0.js.map +0 -1
  101. package/dist/cell-D0dJSGHi.js.map +0 -1
  102. package/dist/debug-server-1dhDA3aw.js +0 -980
  103. package/dist/debug-server-1dhDA3aw.js.map +0 -1
  104. package/dist/debug-server-Dctc9GAh.js.map +0 -1
  105. package/dist/qr-http-server-BH0UFeUn.js.map +0 -1
  106. package/dist/qr-http-server-Bo8LMOJQ.js.map +0 -1
  107. package/dist/qr-http-server-Bt_RAxFn.cjs.map +0 -1
  108. package/dist/qr-http-server-CrGohOwC.js.map +0 -1
  109. package/dist/qr-http-server-DM2R25jP.cjs.map +0 -1
  110. package/dist/qr-http-server-s1iW_5VQ.js.map +0 -1
  111. package/dist/relay-factory-Dug7W6Ao.js.map +0 -1
  112. package/dist/relay-worker-TReZtzGl.d.ts.map +0 -1
  113. package/dist/test-runner/relay-worker.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { buildIndicatorExpression } from "./attach-orchestrator-CPMQWkGV.js";
1
+ import { buildIndicatorExpression } from "./attach-orchestrator-BiaYiNOr.js";
2
2
  //#region src/test-runner/cell.ts
3
3
  /**
4
4
  * Cell injection utility for the `devtools-test` CLI (issue #684 §4.1).
@@ -42,12 +42,16 @@ async function injectGlobals(conn, globals) {
42
42
  }
43
43
  }
44
44
  /**
45
- * Injects the "Debugger Connected" on-phone indicator via `Runtime.evaluate`.
45
+ * Injects (or updates) the "Debugger Connected"/"Debugger Disconnected"
46
+ * on-phone indicator via `Runtime.evaluate` (#730).
46
47
  *
47
48
  * Uses the same CDP mechanism as {@link injectGlobals} — a single
48
49
  * `Runtime.evaluate` round-trip with the expression built by
49
- * {@link buildIndicatorExpression}. The indicator is a dismissible red badge
50
- * rendered at the bottom-left of the page (position:fixed, safe-area-aware).
50
+ * {@link buildIndicatorExpression}. The indicator is a LIVE badge rendered at
51
+ * the bottom-left of the page (position:fixed, safe-area-aware): the badge's
52
+ * controller is idempotent, so calling this again (e.g. right before
53
+ * `close()` with `{ state: 'disconnected' }`) UPDATES the existing badge in
54
+ * place instead of injecting a duplicate.
51
55
  *
52
56
  * **Isolation**: unlike {@link injectGlobals}, this function NEVER throws to
53
57
  * its caller. Injection failure (e.g. page detached during inject, CSS not
@@ -57,12 +61,12 @@ async function injectGlobals(conn, globals) {
57
61
  * `src/in-app/attach.ts`.
58
62
  *
59
63
  * Call this ONLY on the manual debug paths (start_attach MCP, devtools-test
60
- * CLI). Do NOT call on the `run_tests` auto-attach path — the red badge
64
+ * CLI). Do NOT call on the `run_tests` auto-attach path — the badge
61
65
  * would contaminate screenshots, measure_safe_area probes, and DOM snapshots
62
66
  * taken during automated measurement runs.
63
67
  *
64
68
  * SECRET-HANDLING: the injected expression contains no secrets, relay URLs,
65
- * wss addresses, or TOTP codes — it is pure DOM UI text built by
69
+ * wss addresses, or TOTP codes — it is pure DOM UI text + enum state built by
66
70
  * {@link buildIndicatorExpression}.
67
71
  *
68
72
  * @param conn - The live CDP connection (relay-attached page).
@@ -81,4 +85,4 @@ async function injectDebugIndicator(conn, opts) {
81
85
  //#endregion
82
86
  export { injectDebugIndicator, injectGlobals };
83
87
 
84
- //# sourceMappingURL=cell-D0dJSGHi.js.map
88
+ //# sourceMappingURL=cell-Bi8MAPA8.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell-Bi8MAPA8.js","names":[],"sources":["../src/test-runner/cell.ts"],"sourcesContent":["/**\n * Cell injection utility for the `devtools-test` CLI (issue #684 §4.1).\n *\n * Injects arbitrary globals into the page via `Runtime.evaluate` BEFORE the\n * first test bundle is injected. The injected values are session-global — one\n * call covers all files in the run.\n *\n * The primary use-case is injecting `__AIT_CELL__` (sdkLine/platform) so\n * sdk-example's `aitCapture.ts` picks up the correct test-axis values instead\n * of falling back to `'2.x'`/`'mock'`.\n *\n * devtools does NOT know the shape of `__AIT_CELL__` — it only provides the\n * general injection mechanism. The caller (CLI or MCP auto-attach path) is\n * responsible for constructing the cell object.\n *\n * SECRET-HANDLING: cell values (sdkLine/platform) are not secrets and may be\n * logged at the caller's discretion. This module does NOT log them itself.\n *\n * Node-only. react-free. CdpConnection only.\n */\n\nimport { buildIndicatorExpression } from '../mcp/attach-orchestrator.js';\nimport type { CdpConnection } from '../mcp/cdp-connection.js';\n\n/**\n * Injects each key of `globals` into `globalThis` in the page via a single\n * `Runtime.evaluate` call. Must be called BEFORE the first `bundleTestFile`\n * inject — the cell is session-global and applies to all subsequent files.\n *\n * Throws if the CDP evaluate returns an exception.\n *\n * @param conn - The live CDP connection (relay-attached page).\n * @param globals - Plain-JSON-serialisable key→value map to assign onto `globalThis`.\n */\nexport async function injectGlobals(\n conn: CdpConnection,\n globals: Record<string, unknown>,\n): Promise<void> {\n // JSON.stringify is safe here: globals values are plain data (sdkLine/platform\n // strings or simple objects). If a value is not JSON-serialisable this throws\n // at call time, which surfaces the error early and clearly.\n const expr = `(() => { Object.assign(globalThis, ${JSON.stringify(globals)}); return true; })()`;\n const result = await conn.send('Runtime.evaluate', { expression: expr, returnByValue: true });\n if (result.exceptionDetails) {\n const msg =\n result.exceptionDetails.exception?.description ??\n result.exceptionDetails.text ??\n 'unknown error';\n throw new Error(`injectGlobals: Runtime.evaluate threw: ${msg}`);\n }\n}\n\n/**\n * Injects (or updates) the \"Debugger Connected\"/\"Debugger Disconnected\"\n * on-phone indicator via `Runtime.evaluate` (#730).\n *\n * Uses the same CDP mechanism as {@link injectGlobals} — a single\n * `Runtime.evaluate` round-trip with the expression built by\n * {@link buildIndicatorExpression}. The indicator is a LIVE badge rendered at\n * the bottom-left of the page (position:fixed, safe-area-aware): the badge's\n * controller is idempotent, so calling this again (e.g. right before\n * `close()` with `{ state: 'disconnected' }`) UPDATES the existing badge in\n * place instead of injecting a duplicate.\n *\n * **Isolation**: unlike {@link injectGlobals}, this function NEVER throws to\n * its caller. Injection failure (e.g. page detached during inject, CSS not\n * supported) is swallowed and logged as `console.debug` — the badge is\n * informational UI and must never block attach success or test execution.\n * This is the same fire-and-forget spirit as the in-page console mount in\n * `src/in-app/attach.ts`.\n *\n * Call this ONLY on the manual debug paths (start_attach MCP, devtools-test\n * CLI). Do NOT call on the `run_tests` auto-attach path — the badge\n * would contaminate screenshots, measure_safe_area probes, and DOM snapshots\n * taken during automated measurement runs.\n *\n * SECRET-HANDLING: the injected expression contains no secrets, relay URLs,\n * wss addresses, or TOTP codes — it is pure DOM UI text + enum state built by\n * {@link buildIndicatorExpression}.\n *\n * @param conn - The live CDP connection (relay-attached page).\n * @param opts - Optional overrides forwarded to {@link buildIndicatorExpression}.\n */\nexport async function injectDebugIndicator(\n conn: CdpConnection,\n opts?: { label?: string; disconnectedLabel?: string; state?: 'attached' | 'disconnected' },\n): Promise<void> {\n try {\n await conn.send('Runtime.evaluate', {\n expression: buildIndicatorExpression(opts),\n returnByValue: true,\n });\n } catch (err) {\n // Badge injection is informational UI — swallow and log; never propagate.\n console.debug('[@ait-co/devtools] debug indicator inject skipped:', err);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,eAAsB,cACpB,MACA,SACe;CAIf,MAAM,OAAO,sCAAsC,KAAK,UAAU,QAAQ,CAAC;CAC3E,MAAM,SAAS,MAAM,KAAK,KAAK,oBAAoB;EAAE,YAAY;EAAM,eAAe;EAAM,CAAC;AAC7F,KAAI,OAAO,kBAAkB;EAC3B,MAAM,MACJ,OAAO,iBAAiB,WAAW,eACnC,OAAO,iBAAiB,QACxB;AACF,QAAM,IAAI,MAAM,0CAA0C,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCpE,eAAsB,qBACpB,MACA,MACe;AACf,KAAI;AACF,QAAM,KAAK,KAAK,oBAAoB;GAClC,YAAY,yBAAyB,KAAK;GAC1C,eAAe;GAChB,CAAC;UACK,KAAK;AAEZ,UAAQ,MAAM,sDAAsD,IAAI"}
@@ -1,4 +1,4 @@
1
- import { buildIndicatorExpression } from "./attach-orchestrator-RhOdO3DS.js";
1
+ import { buildIndicatorExpression } from "./attach-orchestrator-Cjdhir2U.js";
2
2
  //#region src/test-runner/cell.ts
3
3
  /**
4
4
  * Cell injection utility for the `devtools-test` CLI (issue #684 §4.1).
@@ -42,12 +42,16 @@ async function injectGlobals(conn, globals) {
42
42
  }
43
43
  }
44
44
  /**
45
- * Injects the "Debugger Connected" on-phone indicator via `Runtime.evaluate`.
45
+ * Injects (or updates) the "Debugger Connected"/"Debugger Disconnected"
46
+ * on-phone indicator via `Runtime.evaluate` (#730).
46
47
  *
47
48
  * Uses the same CDP mechanism as {@link injectGlobals} — a single
48
49
  * `Runtime.evaluate` round-trip with the expression built by
49
- * {@link buildIndicatorExpression}. The indicator is a dismissible red badge
50
- * rendered at the bottom-left of the page (position:fixed, safe-area-aware).
50
+ * {@link buildIndicatorExpression}. The indicator is a LIVE badge rendered at
51
+ * the bottom-left of the page (position:fixed, safe-area-aware): the badge's
52
+ * controller is idempotent, so calling this again (e.g. right before
53
+ * `close()` with `{ state: 'disconnected' }`) UPDATES the existing badge in
54
+ * place instead of injecting a duplicate.
51
55
  *
52
56
  * **Isolation**: unlike {@link injectGlobals}, this function NEVER throws to
53
57
  * its caller. Injection failure (e.g. page detached during inject, CSS not
@@ -57,12 +61,12 @@ async function injectGlobals(conn, globals) {
57
61
  * `src/in-app/attach.ts`.
58
62
  *
59
63
  * Call this ONLY on the manual debug paths (start_attach MCP, devtools-test
60
- * CLI). Do NOT call on the `run_tests` auto-attach path — the red badge
64
+ * CLI). Do NOT call on the `run_tests` auto-attach path — the badge
61
65
  * would contaminate screenshots, measure_safe_area probes, and DOM snapshots
62
66
  * taken during automated measurement runs.
63
67
  *
64
68
  * SECRET-HANDLING: the injected expression contains no secrets, relay URLs,
65
- * wss addresses, or TOTP codes — it is pure DOM UI text built by
69
+ * wss addresses, or TOTP codes — it is pure DOM UI text + enum state built by
66
70
  * {@link buildIndicatorExpression}.
67
71
  *
68
72
  * @param conn - The live CDP connection (relay-attached page).
@@ -81,4 +85,4 @@ async function injectDebugIndicator(conn, opts) {
81
85
  //#endregion
82
86
  export { injectDebugIndicator, injectGlobals };
83
87
 
84
- //# sourceMappingURL=cell-Ba9lCI3B.js.map
88
+ //# sourceMappingURL=cell-Cw4fVOl2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell-Cw4fVOl2.js","names":[],"sources":["../src/test-runner/cell.ts"],"sourcesContent":["/**\n * Cell injection utility for the `devtools-test` CLI (issue #684 §4.1).\n *\n * Injects arbitrary globals into the page via `Runtime.evaluate` BEFORE the\n * first test bundle is injected. The injected values are session-global — one\n * call covers all files in the run.\n *\n * The primary use-case is injecting `__AIT_CELL__` (sdkLine/platform) so\n * sdk-example's `aitCapture.ts` picks up the correct test-axis values instead\n * of falling back to `'2.x'`/`'mock'`.\n *\n * devtools does NOT know the shape of `__AIT_CELL__` — it only provides the\n * general injection mechanism. The caller (CLI or MCP auto-attach path) is\n * responsible for constructing the cell object.\n *\n * SECRET-HANDLING: cell values (sdkLine/platform) are not secrets and may be\n * logged at the caller's discretion. This module does NOT log them itself.\n *\n * Node-only. react-free. CdpConnection only.\n */\n\nimport { buildIndicatorExpression } from '../mcp/attach-orchestrator.js';\nimport type { CdpConnection } from '../mcp/cdp-connection.js';\n\n/**\n * Injects each key of `globals` into `globalThis` in the page via a single\n * `Runtime.evaluate` call. Must be called BEFORE the first `bundleTestFile`\n * inject — the cell is session-global and applies to all subsequent files.\n *\n * Throws if the CDP evaluate returns an exception.\n *\n * @param conn - The live CDP connection (relay-attached page).\n * @param globals - Plain-JSON-serialisable key→value map to assign onto `globalThis`.\n */\nexport async function injectGlobals(\n conn: CdpConnection,\n globals: Record<string, unknown>,\n): Promise<void> {\n // JSON.stringify is safe here: globals values are plain data (sdkLine/platform\n // strings or simple objects). If a value is not JSON-serialisable this throws\n // at call time, which surfaces the error early and clearly.\n const expr = `(() => { Object.assign(globalThis, ${JSON.stringify(globals)}); return true; })()`;\n const result = await conn.send('Runtime.evaluate', { expression: expr, returnByValue: true });\n if (result.exceptionDetails) {\n const msg =\n result.exceptionDetails.exception?.description ??\n result.exceptionDetails.text ??\n 'unknown error';\n throw new Error(`injectGlobals: Runtime.evaluate threw: ${msg}`);\n }\n}\n\n/**\n * Injects (or updates) the \"Debugger Connected\"/\"Debugger Disconnected\"\n * on-phone indicator via `Runtime.evaluate` (#730).\n *\n * Uses the same CDP mechanism as {@link injectGlobals} — a single\n * `Runtime.evaluate` round-trip with the expression built by\n * {@link buildIndicatorExpression}. The indicator is a LIVE badge rendered at\n * the bottom-left of the page (position:fixed, safe-area-aware): the badge's\n * controller is idempotent, so calling this again (e.g. right before\n * `close()` with `{ state: 'disconnected' }`) UPDATES the existing badge in\n * place instead of injecting a duplicate.\n *\n * **Isolation**: unlike {@link injectGlobals}, this function NEVER throws to\n * its caller. Injection failure (e.g. page detached during inject, CSS not\n * supported) is swallowed and logged as `console.debug` — the badge is\n * informational UI and must never block attach success or test execution.\n * This is the same fire-and-forget spirit as the in-page console mount in\n * `src/in-app/attach.ts`.\n *\n * Call this ONLY on the manual debug paths (start_attach MCP, devtools-test\n * CLI). Do NOT call on the `run_tests` auto-attach path — the badge\n * would contaminate screenshots, measure_safe_area probes, and DOM snapshots\n * taken during automated measurement runs.\n *\n * SECRET-HANDLING: the injected expression contains no secrets, relay URLs,\n * wss addresses, or TOTP codes — it is pure DOM UI text + enum state built by\n * {@link buildIndicatorExpression}.\n *\n * @param conn - The live CDP connection (relay-attached page).\n * @param opts - Optional overrides forwarded to {@link buildIndicatorExpression}.\n */\nexport async function injectDebugIndicator(\n conn: CdpConnection,\n opts?: { label?: string; disconnectedLabel?: string; state?: 'attached' | 'disconnected' },\n): Promise<void> {\n try {\n await conn.send('Runtime.evaluate', {\n expression: buildIndicatorExpression(opts),\n returnByValue: true,\n });\n } catch (err) {\n // Badge injection is informational UI — swallow and log; never propagate.\n console.debug('[@ait-co/devtools] debug indicator inject skipped:', err);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,eAAsB,cACpB,MACA,SACe;CAIf,MAAM,OAAO,sCAAsC,KAAK,UAAU,QAAQ,CAAC;CAC3E,MAAM,SAAS,MAAM,KAAK,KAAK,oBAAoB;EAAE,YAAY;EAAM,eAAe;EAAM,CAAC;AAC7F,KAAI,OAAO,kBAAkB;EAC3B,MAAM,MACJ,OAAO,iBAAiB,WAAW,eACnC,OAAO,iBAAiB,QACxB;AACF,QAAM,IAAI,MAAM,0CAA0C,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCpE,eAAsB,qBACpB,MACA,MACe;AACf,KAAI;AACF,QAAM,KAAK,KAAK,oBAAoB;GAClC,YAAY,yBAAyB,KAAK;GAC1C,eAAe;GAChB,CAAC;UACK,KAAK;AAEZ,UAAQ,MAAM,sDAAsD,IAAI"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { buildIndicatorExpression } from "./attach-orchestrator-CpE0dMew.js";
2
+ import { buildIndicatorExpression } from "./attach-orchestrator-ca34r46y.js";
3
3
  //#region src/test-runner/cell.ts
4
4
  /**
5
5
  * Cell injection utility for the `devtools-test` CLI (issue #684 §4.1).
@@ -43,12 +43,16 @@ async function injectGlobals(conn, globals) {
43
43
  }
44
44
  }
45
45
  /**
46
- * Injects the "Debugger Connected" on-phone indicator via `Runtime.evaluate`.
46
+ * Injects (or updates) the "Debugger Connected"/"Debugger Disconnected"
47
+ * on-phone indicator via `Runtime.evaluate` (#730).
47
48
  *
48
49
  * Uses the same CDP mechanism as {@link injectGlobals} — a single
49
50
  * `Runtime.evaluate` round-trip with the expression built by
50
- * {@link buildIndicatorExpression}. The indicator is a dismissible red badge
51
- * rendered at the bottom-left of the page (position:fixed, safe-area-aware).
51
+ * {@link buildIndicatorExpression}. The indicator is a LIVE badge rendered at
52
+ * the bottom-left of the page (position:fixed, safe-area-aware): the badge's
53
+ * controller is idempotent, so calling this again (e.g. right before
54
+ * `close()` with `{ state: 'disconnected' }`) UPDATES the existing badge in
55
+ * place instead of injecting a duplicate.
52
56
  *
53
57
  * **Isolation**: unlike {@link injectGlobals}, this function NEVER throws to
54
58
  * its caller. Injection failure (e.g. page detached during inject, CSS not
@@ -58,12 +62,12 @@ async function injectGlobals(conn, globals) {
58
62
  * `src/in-app/attach.ts`.
59
63
  *
60
64
  * Call this ONLY on the manual debug paths (start_attach MCP, devtools-test
61
- * CLI). Do NOT call on the `run_tests` auto-attach path — the red badge
65
+ * CLI). Do NOT call on the `run_tests` auto-attach path — the badge
62
66
  * would contaminate screenshots, measure_safe_area probes, and DOM snapshots
63
67
  * taken during automated measurement runs.
64
68
  *
65
69
  * SECRET-HANDLING: the injected expression contains no secrets, relay URLs,
66
- * wss addresses, or TOTP codes — it is pure DOM UI text built by
70
+ * wss addresses, or TOTP codes — it is pure DOM UI text + enum state built by
67
71
  * {@link buildIndicatorExpression}.
68
72
  *
69
73
  * @param conn - The live CDP connection (relay-attached page).
@@ -82,4 +86,4 @@ async function injectDebugIndicator(conn, opts) {
82
86
  //#endregion
83
87
  export { injectDebugIndicator, injectGlobals };
84
88
 
85
- //# sourceMappingURL=cell-CX92lDI0.js.map
89
+ //# sourceMappingURL=cell-rr6gL9AT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell-rr6gL9AT.js","names":[],"sources":["../src/test-runner/cell.ts"],"sourcesContent":["/**\n * Cell injection utility for the `devtools-test` CLI (issue #684 §4.1).\n *\n * Injects arbitrary globals into the page via `Runtime.evaluate` BEFORE the\n * first test bundle is injected. The injected values are session-global — one\n * call covers all files in the run.\n *\n * The primary use-case is injecting `__AIT_CELL__` (sdkLine/platform) so\n * sdk-example's `aitCapture.ts` picks up the correct test-axis values instead\n * of falling back to `'2.x'`/`'mock'`.\n *\n * devtools does NOT know the shape of `__AIT_CELL__` — it only provides the\n * general injection mechanism. The caller (CLI or MCP auto-attach path) is\n * responsible for constructing the cell object.\n *\n * SECRET-HANDLING: cell values (sdkLine/platform) are not secrets and may be\n * logged at the caller's discretion. This module does NOT log them itself.\n *\n * Node-only. react-free. CdpConnection only.\n */\n\nimport { buildIndicatorExpression } from '../mcp/attach-orchestrator.js';\nimport type { CdpConnection } from '../mcp/cdp-connection.js';\n\n/**\n * Injects each key of `globals` into `globalThis` in the page via a single\n * `Runtime.evaluate` call. Must be called BEFORE the first `bundleTestFile`\n * inject — the cell is session-global and applies to all subsequent files.\n *\n * Throws if the CDP evaluate returns an exception.\n *\n * @param conn - The live CDP connection (relay-attached page).\n * @param globals - Plain-JSON-serialisable key→value map to assign onto `globalThis`.\n */\nexport async function injectGlobals(\n conn: CdpConnection,\n globals: Record<string, unknown>,\n): Promise<void> {\n // JSON.stringify is safe here: globals values are plain data (sdkLine/platform\n // strings or simple objects). If a value is not JSON-serialisable this throws\n // at call time, which surfaces the error early and clearly.\n const expr = `(() => { Object.assign(globalThis, ${JSON.stringify(globals)}); return true; })()`;\n const result = await conn.send('Runtime.evaluate', { expression: expr, returnByValue: true });\n if (result.exceptionDetails) {\n const msg =\n result.exceptionDetails.exception?.description ??\n result.exceptionDetails.text ??\n 'unknown error';\n throw new Error(`injectGlobals: Runtime.evaluate threw: ${msg}`);\n }\n}\n\n/**\n * Injects (or updates) the \"Debugger Connected\"/\"Debugger Disconnected\"\n * on-phone indicator via `Runtime.evaluate` (#730).\n *\n * Uses the same CDP mechanism as {@link injectGlobals} — a single\n * `Runtime.evaluate` round-trip with the expression built by\n * {@link buildIndicatorExpression}. The indicator is a LIVE badge rendered at\n * the bottom-left of the page (position:fixed, safe-area-aware): the badge's\n * controller is idempotent, so calling this again (e.g. right before\n * `close()` with `{ state: 'disconnected' }`) UPDATES the existing badge in\n * place instead of injecting a duplicate.\n *\n * **Isolation**: unlike {@link injectGlobals}, this function NEVER throws to\n * its caller. Injection failure (e.g. page detached during inject, CSS not\n * supported) is swallowed and logged as `console.debug` — the badge is\n * informational UI and must never block attach success or test execution.\n * This is the same fire-and-forget spirit as the in-page console mount in\n * `src/in-app/attach.ts`.\n *\n * Call this ONLY on the manual debug paths (start_attach MCP, devtools-test\n * CLI). Do NOT call on the `run_tests` auto-attach path — the badge\n * would contaminate screenshots, measure_safe_area probes, and DOM snapshots\n * taken during automated measurement runs.\n *\n * SECRET-HANDLING: the injected expression contains no secrets, relay URLs,\n * wss addresses, or TOTP codes — it is pure DOM UI text + enum state built by\n * {@link buildIndicatorExpression}.\n *\n * @param conn - The live CDP connection (relay-attached page).\n * @param opts - Optional overrides forwarded to {@link buildIndicatorExpression}.\n */\nexport async function injectDebugIndicator(\n conn: CdpConnection,\n opts?: { label?: string; disconnectedLabel?: string; state?: 'attached' | 'disconnected' },\n): Promise<void> {\n try {\n await conn.send('Runtime.evaluate', {\n expression: buildIndicatorExpression(opts),\n returnByValue: true,\n });\n } catch (err) {\n // Badge injection is informational UI — swallow and log; never propagate.\n console.debug('[@ait-co/devtools] debug indicator inject skipped:', err);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,eAAsB,cACpB,MACA,SACe;CAIf,MAAM,OAAO,sCAAsC,KAAK,UAAU,QAAQ,CAAC;CAC3E,MAAM,SAAS,MAAM,KAAK,KAAK,oBAAoB;EAAE,YAAY;EAAM,eAAe;EAAM,CAAC;AAC7F,KAAI,OAAO,kBAAkB;EAC3B,MAAM,MACJ,OAAO,iBAAiB,WAAW,eACnC,OAAO,iBAAiB,QACxB;AACF,QAAM,IAAI,MAAM,0CAA0C,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCpE,eAAsB,qBACpB,MACA,MACe;AACf,KAAI;AACF,QAAM,KAAK,KAAK,oBAAoB;GAClC,YAAY,yBAAyB,KAAK;GAC1C,eAAe;GAChB,CAAC;UACK,KAAK;AAEZ,UAAQ,MAAM,sDAAsD,IAAI"}
@@ -0,0 +1,436 @@
1
+ import { n as buildRelayVerifyAuth, t as assertRelayAuthConfigured } from "./totp-DfekTBk3.js";
2
+ import { a as RELAY_AUTH_REJECT_CLOSE_CODE, i as ChiiCdpConnection, o as RELAY_AUTH_REJECT_REASON } from "./relay-worker-CCAHLC4a.js";
3
+ import { n as logInfo, t as logError } from "./log-CUik_tHp.js";
4
+ import "./relay-factory-Cloog1jb.js";
5
+ import { a as startTunnelHealthProbe, i as startQuickTunnel, n as makeTunnelStatus, r as printAttachBanner, t as generateAttachToken } from "./attach-orchestrator-BiaYiNOr.js";
6
+ import "./cell-Bi8MAPA8.js";
7
+ import "./qr-http-server-BdNGPRoL.js";
8
+ import "./relay-secret-store-DzxDsK6S.js";
9
+ import { createRequire } from "node:module";
10
+ import "node:events";
11
+ import { WebSocketServer } from "ws";
12
+ import "@modelcontextprotocol/sdk/server/index.js";
13
+ import "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import "@modelcontextprotocol/sdk/types.js";
15
+ import { createServer } from "node:http";
16
+ `
17
+ devtools-test — run mini-app tests on a real device WebView over the CDP relay
18
+
19
+ USAGE
20
+ devtools-test <glob> [<glob> ...] [options]
21
+
22
+ OPTIONS
23
+ --scheme-url <url> intoss-private:// URL from \`ait deploy --scheme-only\`
24
+ (required for standalone relay attach / env3)
25
+ --timeout <ms> Per-file evaluate timeout in ms (default: 60000).
26
+ Controls how long a single test file is allowed to run
27
+ before it is considered hung. Does NOT affect how long
28
+ the CLI waits for a human to scan the QR code — use
29
+ --attach-timeout for that.
30
+ --attach-timeout <ms> How long to wait for a human to scan the QR code with
31
+ their phone. Omit (default) to wait indefinitely — the
32
+ runner stays up until you stop it (Ctrl-C/SIGTERM).
33
+ Pass a value to bound the wait for CI/headless runs.
34
+ --cell-sdk-line <line> SDK line to inject as __AIT_CELL__.sdkLine (2.x|3.x)
35
+ --cell-platform <plat> Platform to inject as __AIT_CELL__.platform
36
+ (mock|ios|android, default: AIT_CELL_PLATFORM env)
37
+ --report-dir <dir> Persist a runner-agnostic report + captures to <dir>
38
+ (report: <sdkLine>.<platform>.json; captures:
39
+ <dir>/.ait-capture/<category>.<sdkLine>.<platform>.json).
40
+ Omitted = nothing saved. Enables console capture.
41
+ --no-qr-stdout Suppress the QR/attach block on stdout (auto-on for
42
+ non-interactive stdout / CI / AIT_NO_QR_STDOUT)
43
+ --headless Disable browser auto-open (text QR only)
44
+ --project-root <dir> Project root for .ait_relay secret lookup
45
+ (default: current working directory)
46
+ --help, -h Show this help message
47
+
48
+ DESCRIPTION
49
+ Boots a Chii relay + cloudflared tunnel, renders a QR code, waits for a real
50
+ device to scan and attach, injects the cell globals (__AIT_CELL__), bundles
51
+ each matched test file with esbuild (SDK imports redirected to window.__sdk),
52
+ injects the bundle into the attached WebView via Runtime.evaluate, and prints
53
+ a summary.
54
+
55
+ With --report-dir, also harvests __AIT_CAPTURE__ console lines and writes a
56
+ runner-agnostic report + per-category capture files so 2.x↔3.0 runs can be
57
+ compared offline.
58
+
59
+ The test files run against the live relay connection started by this process;
60
+ no separate MCP daemon is required.
61
+
62
+ EXAMPLE
63
+ devtools-test 'src/**/*.ait.test.ts' \\
64
+ --scheme-url "intoss-private://..." \\
65
+ --cell-sdk-line 3.x \\
66
+ --cell-platform ios \\
67
+ --report-dir .ait-report \\
68
+ --timeout 60000
69
+
70
+ `.trimStart();
71
+ //#endregion
72
+ //#region src/mcp/chii-relay.ts
73
+ /**
74
+ * Boots the local Chii relay server.
75
+ *
76
+ * Chii (liriliri/chii) is a chobitsu-based CDP relay that lets non-Chrome
77
+ * WebViews (iOS WKWebView / Android WebView — i.e. the Toss app) expose CDP.
78
+ * The relay accepts a `target` websocket from the phone's injected `target.js`
79
+ * and `client` websockets from CDP frontends (our MCP connection).
80
+ *
81
+ * Node-only: `chii` pulls in Koa + ws. Never bundled into the browser/in-app
82
+ * entries.
83
+ *
84
+ * TOTP auth (relay-side, authoritative gate):
85
+ * When `verifyAuth` is provided, this module gates both inbound surfaces:
86
+ *
87
+ * - HTTP 'request': a listener registered BEFORE `chii.start({server})`.
88
+ * Node's `http.Server` calls listeners in registration order; the first
89
+ * to call `res.end()` wins. Invalid auth → 401 + CORS header + a tiny
90
+ * JSON body (`{"error":"totp-rejected"}`) so a cross-origin script
91
+ * `fetch()` probe can READ the status (issue #478). Valid auth → return
92
+ * without side-effect (chii's Koa handler serves it).
93
+ *
94
+ * - WS 'upgrade': after `chii.start()` has registered chii's own upgrade
95
+ * listener, we take over the upgrade chain (remove chii's listeners,
96
+ * re-dispatch manually). Invalid auth → accept-then-close: complete the
97
+ * handshake via a `noServer` WebSocketServer, then immediately close
98
+ * with code 4401 reason 'totp-rejected' (issue #478). A raw 401 +
99
+ * `socket.destroy()` only ever surfaced as close code 1006 in the
100
+ * browser — indistinguishable from a tunnel failure, which left the
101
+ * env-2 phone UI silent. The explicit dispatch (not listener ordering)
102
+ * is what keeps chii away from rejected sockets: accept-then-close
103
+ * leaves the socket alive, so an order-based early-return would let
104
+ * chii's later listener complete a SECOND handshake on the same socket
105
+ * — an auth bypass. Valid auth → forward to chii's captured listeners.
106
+ *
107
+ * TOTP code transports (issue #466) — two equivalent ways to carry the code:
108
+ * 1. Query param `at=<code>` — used by the daemon-side `/client` connection
109
+ * (`chii-connection.ts` appends it; it holds the secret).
110
+ * 2. Path prefix `/at/<code>/…` — used by the phone-side target. Chii's
111
+ * stock `target.js` derives its WS endpoint from the script `src`
112
+ * (`scriptEl.src.replace('target.js','')`), so the only way for the
113
+ * phone to carry a code is to embed it in the script URL path. The
114
+ * in-app attach injects `https://<host>/at/<code>/target.js`; both the
115
+ * script fetch and the derived `wss://<host>/at/<code>/target/<id>` WS
116
+ * dial then carry the prefix. The listeners below rewrite the prefix
117
+ * into the query form (`rewriteAtPathPrefix`) and MUTATE `req.url`
118
+ * before chii's own handlers (registered later) parse it — chii only
119
+ * ever sees the stripped URL.
120
+ *
121
+ * Threat model: "URL leak" — someone obtains the tunnel URL (Slack paste, QR
122
+ * screenshot, shoulder-surfing) but does not have the shared TOTP secret.
123
+ * Rotating 6-digit code makes the URL stale after 30 s.
124
+ * A determined attacker who extracts the secret from the dogfood bundle can
125
+ * still compute valid codes; that is out of scope (see umbrella CLAUDE.md §4).
126
+ *
127
+ * SECRET-HANDLING: The secret value and computed TOTP codes MUST NOT appear
128
+ * in any log, error message, or process output. `verifyAuth` is a black-box
129
+ * predicate from the caller's perspective; this module only forwards pass/fail.
130
+ */
131
+ const require = createRequire(import.meta.url);
132
+ /**
133
+ * WS keepalive ping interval (ms).
134
+ *
135
+ * Cloudflare proxied connections are dropped after ~100 s of no traffic.
136
+ * 45 s comfortably fits inside that window and lets both the phone-target leg
137
+ * and the daemon-client leg survive idle CDP sessions.
138
+ */
139
+ const DEFAULT_KEEPALIVE_INTERVAL_MS = 45e3;
140
+ /**
141
+ * Loads chii's internal WebSocketServer class and returns it together with a
142
+ * flag indicating whether the real class was found.
143
+ *
144
+ * Returns `null` if the internal path is not resolvable (future chii release
145
+ * changes the layout) — callers skip keepalive gracefully.
146
+ */
147
+ function tryLoadChiiWssClass() {
148
+ try {
149
+ const mod = require("chii/server/lib/WebSocketServer");
150
+ if (typeof mod === "function") return mod;
151
+ } catch {}
152
+ return null;
153
+ }
154
+ /**
155
+ * Calls `chii.start()` and returns the chii `WebSocketServer` instance that
156
+ * was constructed during the call.
157
+ *
158
+ * How: `chii/server/index.js`'s `start()` creates `new WebSocketServer()`
159
+ * where `WebSocketServer` is captured from `require('./lib/WebSocketServer')`
160
+ * at module load time. The class reference is stable, so we can temporarily
161
+ * patch `ChiiWssClass.prototype.start` — which runs *on the instance* —
162
+ * to record `this` before the original `start` runs.
163
+ *
164
+ * The patch is installed before `chii.start()` and removed (via `finally`)
165
+ * immediately after, so concurrent `startChiiRelay` calls nest correctly: each
166
+ * call's patch overrides the previous in the prototype chain for the duration
167
+ * of its own `chii.start()` call, restoring the prior descriptor on exit.
168
+ *
169
+ * If `ChiiWssClass` is null (internal path changed in a future chii release),
170
+ * `chii.start()` runs unpatched and the function returns null — callers skip
171
+ * keepalive gracefully without affecting relay correctness.
172
+ */
173
+ async function startChiiWithCapture(chii, startOptions, ChiiWssClass) {
174
+ if (ChiiWssClass === null) {
175
+ await chii.start(startOptions);
176
+ return null;
177
+ }
178
+ let captured = null;
179
+ const proto = ChiiWssClass.prototype;
180
+ const originalStart = proto.start;
181
+ proto.start = function(server) {
182
+ captured = this;
183
+ return originalStart.call(this, server);
184
+ };
185
+ try {
186
+ await chii.start(startOptions);
187
+ } finally {
188
+ proto.start = originalStart;
189
+ }
190
+ return captured;
191
+ }
192
+ function loadChiiServer() {
193
+ const mod = require("chii");
194
+ if (typeof mod === "object" && mod !== null && "start" in mod && typeof mod.start === "function") return mod;
195
+ throw new Error("chii server module did not expose start()");
196
+ }
197
+ /**
198
+ * Rewrites a `/at/<code>/…` path-prefixed request URL into the equivalent
199
+ * query-based form, e.g.:
200
+ *
201
+ * `/at/123456/target.js` → `/target.js?at=123456`
202
+ * `/at/123456/target/x?url=u` → `/target/x?url=u&at=123456`
203
+ * `/at/123456/` → `/?at=123456`
204
+ *
205
+ * Returns `null` when the URL does not carry the prefix (including an empty
206
+ * code segment) — callers fall back to the unmodified URL and the existing
207
+ * query-based auth path.
208
+ *
209
+ * Pure string surgery — this function knows nothing about secrets or code
210
+ * validity; verification stays inside the caller-provided `verifyAuth`
211
+ * predicate (which parses the query). The raw path segment is appended
212
+ * verbatim to the query: both path segments and query values are
213
+ * percent-decoded exactly once by their consumers, so no re-encoding is
214
+ * needed (TOTP codes are 6 digits and never percent-encoded in practice).
215
+ */
216
+ function rewriteAtPathPrefix(rawUrl) {
217
+ const match = /^\/at\/([^/?]+)(\/[^?]*)?(\?.*)?$/.exec(rawUrl);
218
+ if (match === null) return null;
219
+ const code = match[1];
220
+ const path = match[2] === void 0 || match[2] === "" ? "/" : match[2];
221
+ const query = match[3] ?? "";
222
+ return `${path}${query}${query === "" ? "?" : "&"}at=${code}`;
223
+ }
224
+ /**
225
+ * Starts the Chii relay and resolves once listening.
226
+ *
227
+ * Default port is 0 (OS-assigned). With port 0 the OS picks a free ephemeral
228
+ * port on every start, so a stale cloudflared orphan holding any particular
229
+ * port cannot cause EADDRINUSE. The resolved `ChiiRelay.port` and `baseUrl`
230
+ * always reflect the actual bound port.
231
+ *
232
+ * chii.start() is called with `server` (our pre-created httpServer) BEFORE
233
+ * httpServer.listen(). This is intentional: chii attaches its Koa handler and
234
+ * WS upgrade listener to the server object, but the actual TCP bind is
235
+ * performed by our httpServer.listen() call below. The `port`/`domain` values
236
+ * passed to chii.start() are used for display/banner purposes inside chii and
237
+ * do not affect which port the server binds. The connection path (clients
238
+ * connecting to `relay.baseUrl`) always uses the post-listen confirmed port.
239
+ */
240
+ async function startChiiRelay(options = {}) {
241
+ const requestedPort = options.port ?? 0;
242
+ const host = options.host ?? "127.0.0.1";
243
+ const { verifyAuth, onAuthReject } = options;
244
+ const keepaliveIntervalMs = options.keepaliveIntervalMs !== void 0 ? options.keepaliveIntervalMs : DEFAULT_KEEPALIVE_INTERVAL_MS;
245
+ const httpServer = createServer();
246
+ const notifyAuthReject = (kind) => {
247
+ if (onAuthReject === void 0) return;
248
+ try {
249
+ onAuthReject({ kind });
250
+ } catch {}
251
+ };
252
+ if (verifyAuth) httpServer.on("request", (req, res) => {
253
+ const rewritten = rewriteAtPathPrefix(req.url ?? "");
254
+ if (rewritten !== null) {
255
+ req.url = rewritten;
256
+ if (!verifyAuth(req)) {
257
+ res.statusCode = 401;
258
+ res.setHeader("Access-Control-Allow-Origin", "*");
259
+ res.setHeader("Content-Type", "application/json");
260
+ res.end(JSON.stringify({ error: RELAY_AUTH_REJECT_REASON }));
261
+ notifyAuthReject("http-request");
262
+ }
263
+ return;
264
+ }
265
+ const pathname = (req.url ?? "").split("?")[0];
266
+ if (pathname === "/targets" || pathname === "/targets/") {
267
+ if (!verifyAuth(req)) {
268
+ res.statusCode = 401;
269
+ res.setHeader("Access-Control-Allow-Origin", "*");
270
+ res.setHeader("Content-Type", "application/json");
271
+ res.end(JSON.stringify({ error: RELAY_AUTH_REJECT_REASON }));
272
+ notifyAuthReject("http-request");
273
+ return;
274
+ }
275
+ return;
276
+ }
277
+ });
278
+ const chiiWssClass = keepaliveIntervalMs > 0 ? tryLoadChiiWssClass() : null;
279
+ const capturedChiiWss = await startChiiWithCapture(loadChiiServer(), {
280
+ server: httpServer,
281
+ domain: `${host}:${requestedPort}`,
282
+ port: requestedPort
283
+ }, chiiWssClass);
284
+ if (verifyAuth) {
285
+ const chiiUpgradeListeners = httpServer.listeners("upgrade");
286
+ httpServer.removeAllListeners("upgrade");
287
+ const rejectWss = new WebSocketServer({ noServer: true });
288
+ httpServer.on("upgrade", (req, socket, head) => {
289
+ const rewritten = rewriteAtPathPrefix(req.url ?? "");
290
+ if (rewritten !== null) req.url = rewritten;
291
+ if (!verifyAuth(req)) {
292
+ rejectWss.handleUpgrade(req, socket, head, (ws) => {
293
+ ws.close(RELAY_AUTH_REJECT_CLOSE_CODE, RELAY_AUTH_REJECT_REASON);
294
+ });
295
+ notifyAuthReject("ws-upgrade");
296
+ return;
297
+ }
298
+ for (const listener of chiiUpgradeListeners) listener(req, socket, head);
299
+ });
300
+ }
301
+ const actualPort = await new Promise((resolve, reject) => {
302
+ httpServer.once("error", reject);
303
+ httpServer.listen(requestedPort, host, () => {
304
+ httpServer.off("error", reject);
305
+ resolve(httpServer.address().port);
306
+ });
307
+ });
308
+ let keepaliveHandle = null;
309
+ if (keepaliveIntervalMs > 0 && capturedChiiWss !== null) {
310
+ const chiiWss = capturedChiiWss;
311
+ keepaliveHandle = setInterval(() => {
312
+ for (const client of chiiWss._wss.clients) if (client.readyState === 1) client.ping();
313
+ }, keepaliveIntervalMs);
314
+ }
315
+ return {
316
+ port: actualPort,
317
+ baseUrl: `http://${host}:${actualPort}`,
318
+ close: () => new Promise((resolve) => {
319
+ if (keepaliveHandle !== null) {
320
+ clearInterval(keepaliveHandle);
321
+ keepaliveHandle = null;
322
+ }
323
+ httpServer.close(() => resolve());
324
+ })
325
+ };
326
+ }
327
+ //#endregion
328
+ //#region src/mcp/debug-server.ts
329
+ /**
330
+ * Factory that constructs a `ChiiCdpConnection` for the given relay base URL.
331
+ *
332
+ * Introduced as a named seam so PR-2 (dual-connection, #348) can defer
333
+ * construction to first-activation time by moving or replacing this call. Since
334
+ * #396 every family (relay included) is constructed lazily on its first
335
+ * `start_debug`, so this is always called from the lazy boot path.
336
+ *
337
+ * The relay base URL is only available after `startChiiRelay()` resolves, so
338
+ * the factory is called right after that point (same as before this refactor).
339
+ */
340
+ function createRelayConnection(relayBaseUrl) {
341
+ return new ChiiCdpConnection({
342
+ relayBaseUrl,
343
+ totpSecret: process.env.AIT_DEBUG_TOTP_SECRET
344
+ });
345
+ }
346
+ /**
347
+ * Boots the relay family (issues #348, #356): starts the Chii relay on an
348
+ * OS-assigned port (with optional TOTP gate), opens a cloudflared quick tunnel
349
+ * to the relay's confirmed port in the background, prints the attach banner,
350
+ * and arms the tunnel health probe. Returns a {@link BootedFamily} whose
351
+ * `getTunnelStatus()` reflects the live tunnel (it flips up once the background
352
+ * tunnel resolves and follows reissues).
353
+ *
354
+ * Booted lazily via the dual router's `bootLazyFor('relay-intoss')` callback
355
+ * (symmetry with {@link bootLocalFamily}), at most once on the first
356
+ * `start_debug({ mode: 'relay-staging' })` (all-lazy, #396 — every relay boot now
357
+ * flows through `switchMode` after the project-local secret load). `relay-live`
358
+ * removed (#665).
359
+ *
360
+ * The relay base URL is only known after `startChiiRelay()` resolves, so the
361
+ * `ChiiCdpConnection` (via {@link createRelayConnection}) is constructed inside
362
+ * this function, after the relay port is confirmed.
363
+ *
364
+ * SECRET-HANDLING: the TOTP secret rides only inside `verifyAuth`; the wssUrl
365
+ * (relay host) is never logged here directly.
366
+ */
367
+ async function bootRelayFamily(options = {}) {
368
+ assertRelayAuthConfigured();
369
+ const relayPort = options.relayPort ?? 0;
370
+ const totpEnabled = options.verifyAuth !== void 0;
371
+ const relay = await startChiiRelay({
372
+ port: relayPort,
373
+ verifyAuth: options.verifyAuth,
374
+ onAuthReject: options.onAuthReject
375
+ });
376
+ logInfo("server.start", {
377
+ port: relay.port,
378
+ totpEnabled
379
+ });
380
+ let tunnel = null;
381
+ let tunnelStatus = makeTunnelStatus(false, null);
382
+ let tunnelProbe = null;
383
+ generateAttachToken();
384
+ startQuickTunnel(relay.port).then((t) => {
385
+ tunnel = t;
386
+ tunnelStatus = makeTunnelStatus(true, t.wssUrl);
387
+ options.onWssUrl?.(t.wssUrl);
388
+ if (t.childPid !== void 0) options.onTunnelChildPid?.(t.childPid);
389
+ logInfo("tunnel.up", { totpEnabled });
390
+ tunnelProbe = startTunnelHealthProbe(t, relay.port, {
391
+ onReissue: (newTunnel) => {
392
+ tunnel = newTunnel;
393
+ tunnelStatus = makeTunnelStatus(true, newTunnel.wssUrl, null, 0);
394
+ options.onWssUrl?.(newTunnel.wssUrl);
395
+ if (newTunnel.childPid !== void 0) options.onTunnelChildPid?.(newTunnel.childPid);
396
+ printAttachBanner({
397
+ wssUrl: newTunnel.wssUrl,
398
+ totpEnabled
399
+ }).then(() => {
400
+ logInfo("tunnel.up", {
401
+ totpEnabled,
402
+ reissued: true
403
+ });
404
+ });
405
+ },
406
+ onPermanentDrop: (droppedAt) => {
407
+ tunnelStatus = makeTunnelStatus(false, null, droppedAt, 3);
408
+ logError("tunnel.down", { msg: `tunnel permanently dropped (${droppedAt}). Restart: npx @ait-co/devtools devtools-mcp` });
409
+ options.onTunnelDown?.();
410
+ }
411
+ });
412
+ return printAttachBanner({
413
+ wssUrl: t.wssUrl,
414
+ totpEnabled
415
+ });
416
+ }, (err) => {
417
+ logError("tunnel.down", { msg: `Failed to open cloudflared quick tunnel: ${err instanceof Error ? err.message : String(err)}. The relay is up locally; attach over the public URL is unavailable until the tunnel starts.` });
418
+ });
419
+ const connection = createRelayConnection(relay.baseUrl);
420
+ return {
421
+ connection,
422
+ relayOrigin: "intoss-webview",
423
+ relayHttpUrl: relay.baseUrl,
424
+ getTunnelStatus: () => tunnelStatus,
425
+ stop() {
426
+ tunnelProbe?.stop();
427
+ tunnel?.stop();
428
+ connection.close();
429
+ relay.close();
430
+ }
431
+ };
432
+ }
433
+ //#endregion
434
+ export { bootRelayFamily, buildRelayVerifyAuth };
435
+
436
+ //# sourceMappingURL=debug-server-BSbfnFR5.js.map