@ait-co/devtools 0.1.136 → 0.1.138

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 (126) hide show
  1. package/dist/{attach-orchestrator-Cjdhir2U.js → attach-orchestrator-6kz_ZdWL.js} +5 -3
  2. package/dist/{attach-orchestrator-Cjdhir2U.js.map → attach-orchestrator-6kz_ZdWL.js.map} +1 -1
  3. package/dist/{attach-orchestrator-CE0S09YU.js → attach-orchestrator-BEiz3wAx.js} +5 -3
  4. package/dist/{attach-orchestrator-CMoDuG2A.js.map → attach-orchestrator-BEiz3wAx.js.map} +1 -1
  5. package/dist/{attach-orchestrator-CMoDuG2A.js → attach-orchestrator-BG8TGJOZ.js} +5 -3
  6. package/dist/{attach-orchestrator-CE0S09YU.js.map → attach-orchestrator-BG8TGJOZ.js.map} +1 -1
  7. package/dist/{bundle-BKqyhEK9.d.ts → bundle-C796JIwG.d.ts} +65 -2
  8. package/dist/bundle-C796JIwG.d.ts.map +1 -0
  9. package/dist/{capture-ltuV0gZa.d.ts → capture-DsP525OZ.d.ts} +1 -1
  10. package/dist/{capture-ltuV0gZa.d.ts.map → capture-DsP525OZ.d.ts.map} +1 -1
  11. package/dist/{cdp-connection-BYE9meXe.d.ts → cdp-connection-rP1WdnH5.d.ts} +1 -1
  12. package/dist/{cdp-connection-BYE9meXe.d.ts.map → cdp-connection-rP1WdnH5.d.ts.map} +1 -1
  13. package/dist/{cell-D1y4shoV.js → cell-BMlFh08P.js} +2 -2
  14. package/dist/cell-BMlFh08P.js.map +1 -0
  15. package/dist/{cell-DHA578lX.js → cell-Dtgtusog.js} +90 -16
  16. package/dist/cell-Dtgtusog.js.map +1 -0
  17. package/dist/{cell-BIdSQb9T.js → cell-KsRXFdDs.js} +122 -16
  18. package/dist/cell-KsRXFdDs.js.map +1 -0
  19. package/dist/{debug-server-BZtH2Z5b.js → debug-server-6c22qSWa.js} +150 -9
  20. package/dist/debug-server-6c22qSWa.js.map +1 -0
  21. package/dist/{debug-server-DApo0o3t.js → debug-server-BQHbaKPT.js} +152 -11
  22. package/dist/debug-server-BQHbaKPT.js.map +1 -0
  23. package/dist/{debug-server-D2wpPK3_.js → debug-server-DWat7d-9.js} +82 -6
  24. package/dist/debug-server-DWat7d-9.js.map +1 -0
  25. package/dist/devtools-opener-3Drge_RJ.js +75 -0
  26. package/dist/devtools-opener-3Drge_RJ.js.map +1 -0
  27. package/dist/devtools-opener-CJpEsXXQ.js +76 -0
  28. package/dist/devtools-opener-CJpEsXXQ.js.map +1 -0
  29. package/dist/devtools-opener-CxtryS8c.js +75 -0
  30. package/dist/devtools-opener-CxtryS8c.js.map +1 -0
  31. package/dist/in-app/auto.js +59 -12
  32. package/dist/in-app/auto.js.map +1 -1
  33. package/dist/in-app/index.d.ts +21 -2
  34. package/dist/in-app/index.d.ts.map +1 -1
  35. package/dist/in-app/index.js +60 -13
  36. package/dist/in-app/index.js.map +1 -1
  37. package/dist/mcp/cli.js +363 -73
  38. package/dist/mcp/cli.js.map +1 -1
  39. package/dist/mcp/server.js +1 -1
  40. package/dist/mock/index.d.ts +64 -1
  41. package/dist/mock/index.d.ts.map +1 -1
  42. package/dist/mock/index.js +221 -14
  43. package/dist/mock/index.js.map +1 -1
  44. package/dist/panel/index.js +178 -23
  45. package/dist/panel/index.js.map +1 -1
  46. package/dist/{pool-C6TgrcyW.d.ts → pool-CrP5CPvU.d.ts} +3 -3
  47. package/dist/{pool-C6TgrcyW.d.ts.map → pool-CrP5CPvU.d.ts.map} +1 -1
  48. package/dist/{qr-http-server-ZkT6F7is.js → qr-http-server-D1hpoyBF.js} +1 -1
  49. package/dist/{qr-http-server-ZkT6F7is.js.map → qr-http-server-D1hpoyBF.js.map} +1 -1
  50. package/dist/qr-http-server-D4rGz8M7.js +1640 -0
  51. package/dist/qr-http-server-D4rGz8M7.js.map +1 -0
  52. package/dist/{qr-http-server-C7q8Gn04.js → qr-http-server-DIjCV7h_.js} +1 -1
  53. package/dist/{qr-http-server-C7q8Gn04.js.map → qr-http-server-DIjCV7h_.js.map} +1 -1
  54. package/dist/{relay-factory-DnuTH4v-.js → relay-factory-C1X3G3WY.js} +72 -15
  55. package/dist/relay-factory-C1X3G3WY.js.map +1 -0
  56. package/dist/{relay-secret-store-CLEGyHou.js → relay-secret-store-Bmyleu0A.js} +1 -1
  57. package/dist/{relay-secret-store-CLEGyHou.js.map → relay-secret-store-Bmyleu0A.js.map} +1 -1
  58. package/dist/{relay-secret-store-DhzAnnj-.js → relay-secret-store-CQenfcSL.js} +2 -2
  59. package/dist/{relay-secret-store-DhzAnnj-.js.map → relay-secret-store-CQenfcSL.js.map} +1 -1
  60. package/dist/{relay-secret-store-BcVrWwTq.js → relay-secret-store-DKxs7zwq.js} +1 -1
  61. package/dist/{relay-secret-store-DGduVJhs.js.map → relay-secret-store-DKxs7zwq.js.map} +1 -1
  62. package/dist/{relay-secret-store-DGduVJhs.js → relay-secret-store-WJ8EGkIl.js} +1 -1
  63. package/dist/{relay-secret-store-BcVrWwTq.js.map → relay-secret-store-WJ8EGkIl.js.map} +1 -1
  64. package/dist/{relay-url-store-CKW8RQzf.js → relay-url-store-BR2XodiO.js} +2 -2
  65. package/dist/{relay-url-store-CKW8RQzf.js.map → relay-url-store-BR2XodiO.js.map} +1 -1
  66. package/dist/{relay-url-store-CdA58fgw.js → relay-url-store-CH63fVCm.js} +2 -2
  67. package/dist/{relay-url-store-B0X8TsGr.js.map → relay-url-store-CH63fVCm.js.map} +1 -1
  68. package/dist/{relay-url-store-CwKT7i04.js → relay-url-store-DaY1QPes.js} +2 -2
  69. package/dist/{relay-url-store-CwKT7i04.js.map → relay-url-store-DaY1QPes.js.map} +1 -1
  70. package/dist/{relay-url-store-B0X8TsGr.js → relay-url-store-xmUuTjXA.js} +2 -2
  71. package/dist/{relay-url-store-CdA58fgw.js.map → relay-url-store-xmUuTjXA.js.map} +1 -1
  72. package/dist/{relay-worker-UK0nBfDX.js → relay-worker-BUXI0K0b.js} +13 -6
  73. package/dist/{relay-worker-UK0nBfDX.js.map → relay-worker-BUXI0K0b.js.map} +1 -1
  74. package/dist/{relay-worker-Dw3aIzIK.d.ts → relay-worker-DcyboK43.d.ts} +70 -11
  75. package/dist/relay-worker-DcyboK43.d.ts.map +1 -0
  76. package/dist/{runtime-DfHHZms2.d.ts → runtime-BiigOuvb.d.ts} +30 -3
  77. package/dist/runtime-BiigOuvb.d.ts.map +1 -0
  78. package/dist/test-runner/bin.js +481 -62
  79. package/dist/test-runner/bin.js.map +1 -1
  80. package/dist/test-runner/bridge-stub.d.ts +125 -0
  81. package/dist/test-runner/bridge-stub.d.ts.map +1 -0
  82. package/dist/test-runner/bridge-stub.js +92 -0
  83. package/dist/test-runner/bridge-stub.js.map +1 -0
  84. package/dist/test-runner/bundle.d.ts +2 -2
  85. package/dist/test-runner/bundle.js +107 -24
  86. package/dist/test-runner/bundle.js.map +1 -1
  87. package/dist/test-runner/capture.d.ts +1 -1
  88. package/dist/test-runner/config.d.ts +68 -5
  89. package/dist/test-runner/config.d.ts.map +1 -1
  90. package/dist/test-runner/config.js +1 -1
  91. package/dist/test-runner/method-pace.d.ts +82 -0
  92. package/dist/test-runner/method-pace.d.ts.map +1 -0
  93. package/dist/test-runner/method-pace.js +120 -0
  94. package/dist/test-runner/method-pace.js.map +1 -0
  95. package/dist/test-runner/pool.d.ts +1 -1
  96. package/dist/test-runner/pool.js +1 -1
  97. package/dist/test-runner/relay-factory.d.ts +67 -4
  98. package/dist/test-runner/relay-factory.d.ts.map +1 -1
  99. package/dist/test-runner/relay-factory.js +71 -14
  100. package/dist/test-runner/relay-factory.js.map +1 -1
  101. package/dist/test-runner/relay-worker.d.ts +1 -1
  102. package/dist/test-runner/relay-worker.js +1 -1
  103. package/dist/test-runner/report.d.ts +43 -17
  104. package/dist/test-runner/report.d.ts.map +1 -1
  105. package/dist/test-runner/report.js +33 -9
  106. package/dist/test-runner/report.js.map +1 -1
  107. package/dist/test-runner/rpc.d.ts +2 -2
  108. package/dist/test-runner/runtime.d.ts +2 -2
  109. package/dist/test-runner/runtime.js +83 -6
  110. package/dist/test-runner/runtime.js.map +1 -1
  111. package/dist/test-runner/task-graph.d.ts +1 -1
  112. package/dist/throttle-DKKzX1qC.js +59 -0
  113. package/dist/throttle-DKKzX1qC.js.map +1 -0
  114. package/dist/totp-Dwft0Kz7.js +3 -0
  115. package/package.json +1 -1
  116. package/dist/bundle-BKqyhEK9.d.ts.map +0 -1
  117. package/dist/cell-BIdSQb9T.js.map +0 -1
  118. package/dist/cell-D1y4shoV.js.map +0 -1
  119. package/dist/cell-DHA578lX.js.map +0 -1
  120. package/dist/debug-server-BZtH2Z5b.js.map +0 -1
  121. package/dist/debug-server-D2wpPK3_.js.map +0 -1
  122. package/dist/debug-server-DApo0o3t.js.map +0 -1
  123. package/dist/relay-factory-DnuTH4v-.js.map +0 -1
  124. package/dist/relay-worker-Dw3aIzIK.d.ts.map +0 -1
  125. package/dist/runtime-DfHHZms2.d.ts.map +0 -1
  126. package/dist/totp-D1pulXLa.js +0 -3
@@ -1,4 +1,4 @@
1
- import { i as createRelayPool, n as RelayConnectionFactory, t as RELAY_POOL_NAME } from "../pool-C6TgrcyW.js";
1
+ import { i as createRelayPool, n as RelayConnectionFactory, t as RELAY_POOL_NAME } from "../pool-CrP5CPvU.js";
2
2
 
3
3
  //#region src/test-runner/relay-factory.d.ts
4
4
  /** Options for {@link createRelayConnectionFactory}. */
@@ -7,8 +7,36 @@ interface RelayConnectionFactoryOptions {
7
7
  * intoss-private:// scheme URL from `ait deploy --scheme-only` (env3). The
8
8
  * phone cold-loads the candidate bundle this URL points at. SECRET-HANDLING:
9
9
  * never logged.
10
+ *
11
+ * Ignored when {@link attachLauncher} is `true` — the env-2 launcher path
12
+ * builds its attach URL from {@link appUrl} + the relay wss, not a scheme URL.
13
+ * Pass `''` in that case.
10
14
  */
11
15
  schemeUrl: string;
16
+ /**
17
+ * env-2 (AITC Sandbox PWA) attach mode (devtools#774). When `true`, `open()`
18
+ * builds the attach URL as a **launcher deep-link**
19
+ * (`buildLauncherAttachUrl(appUrl, wssUrl, totp)` — the same env-2 path the
20
+ * MCP daemon takes via `prepareAttach(deps, 'relay-mobile', …)`, issue #378)
21
+ * instead of an intoss-private scheme deep-link. The relay boot, QR
22
+ * rendering, dashboard, and attach-wait are byte-for-byte identical to env 3;
23
+ * only the URL family differs. Omitted/false keeps the env-3 scheme path
24
+ * unchanged (zero behavior change).
25
+ */
26
+ attachLauncher?: boolean;
27
+ /**
28
+ * The consumer dev server's HTTP tunnel URL (e.g. an sdk-example
29
+ * `dev:phone:cdp` `*.trycloudflare.com` URL) that the launcher PWA frames.
30
+ * REQUIRED when {@link attachLauncher} is `true`; ignored otherwise.
31
+ *
32
+ * Threaded to the `relay-mobile` `prepareAttach` branch via the
33
+ * `AIT_TUNNEL_BASE_URL` env var (the same key the MCP daemon reads), so the
34
+ * exact same env-2 assembly runs — no divergent code path.
35
+ *
36
+ * SECRET-HANDLING: this is a tunnel host at the same sensitivity as the relay
37
+ * wss — NEVER logged. It rides only inside the QR/dashboard attach capsule.
38
+ */
39
+ appUrl?: string;
12
40
  /**
13
41
  * Project root for the `.ait_relay` secret lookup (read-only). Defaults to
14
42
  * `process.cwd()`.
@@ -35,6 +63,40 @@ interface RelayConnectionFactoryOptions {
35
63
  sdkLine: string;
36
64
  platform: string;
37
65
  };
66
+ /**
67
+ * When `true`, injects `__AIT_STUB_BLOCKING__ = true` before the first test
68
+ * bundle runs (devtools#740, DT-2) — `bundle.ts`'s sdk-redirect module reads
69
+ * this flag via `isStubBlockingEnabled()` and answers the blocking-UI APIs
70
+ * in `bridge-stub.ts`'s `STUB_REGISTRY` from fixtures instead of forwarding
71
+ * them to native. Optional — omitted/false means no flag is injected, which
72
+ * is byte-for-byte the pre-#740 behavior (the sdk-redirect module treats
73
+ * absence the same as `false`). Not a secret.
74
+ */
75
+ stubBlocking?: boolean;
76
+ /**
77
+ * Injects `__AIT_PACE_MS__ = paceMs` before the first test bundle runs
78
+ * (devtools#767) — `runtime.ts`'s test-execution loop reads this page
79
+ * global and waits `paceMs` before every test after the first one that
80
+ * actually runs. Optional — omitted/0 means no flag is injected, which is
81
+ * byte-for-byte the pre-#767 behavior (the loop treats an absent/non-
82
+ * positive value as "no pacing"). Not a secret. This is the PAGE-side half
83
+ * of `--pace`; the RUNNER-side (file-to-file) half is a plain `opts.paceMs`
84
+ * forwarded to `runTestFilesOverRelay` by the caller (cli.ts) — this
85
+ * factory only owns the page-global injection.
86
+ */
87
+ paceMs?: number;
88
+ /**
89
+ * Injects `__AIT_PACE_METHOD_MS__ = paceMethodMs` before the first test
90
+ * bundle runs (devtools#769) — `bundle.ts`'s sdk-redirect virtual module
91
+ * reads this page global via `method-pace.ts#getPaceMethodMs()` and paces
92
+ * every SDK call to a minimum interval BETWEEN calls to the SAME named
93
+ * function, independent of `--pace`'s test-to-test/file-to-file spacing.
94
+ * Optional — omitted/0 means no flag is injected, which is byte-for-byte
95
+ * the pre-#769 behavior (`getPaceMethodMs()` treats an absent/non-positive
96
+ * value as "no pacing", and `wrapWithMethodPacing` returns the SDK object
97
+ * untouched in that case). Not a secret.
98
+ */
99
+ paceMethodMs?: number;
38
100
  /**
39
101
  * Overrides the dashboard HTTP server's bind base port (devtools#752).
40
102
  * Threaded straight through to `startQrHttpServer`'s `dashboardPort`
@@ -62,12 +124,13 @@ interface RelayConnectionFactoryOptions {
62
124
  onQrContent: (textChunks: string[]) => void;
63
125
  }
64
126
  /**
65
- * Builds a {@link RelayConnectionFactory} that opens a standalone env3 relay
66
- * connection.
127
+ * Builds a {@link RelayConnectionFactory} that opens a standalone relay
128
+ * connection for env 3 (intoss-private scheme, default) or env 2 (AITC Sandbox
129
+ * PWA launcher deep-link, when `opts.attachLauncher` is set — devtools#774).
67
130
  *
68
131
  * `open()` performs the full attach lifecycle and BLOCKS while a human scans
69
- * the rendered QR with their phone — there is no way around the manual scan
70
- * for env3. By default the wait is UNBOUNDED (`opts.timeoutMs` omitted): the
132
+ * the rendered QR with their phone — there is no way around the manual scan.
133
+ * By default the wait is UNBOUNDED (`opts.timeoutMs` omitted): the
71
134
  * runner stays up until the user stops it (Ctrl-C/SIGTERM), since QR-scan is
72
135
  * a human-paced action with no sound default bound (devtools#735). Passing an
73
136
  * explicit `timeoutMs` opts into the old bounded behavior (CI/headless
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","names":[],"sources":["../../src/test-runner/relay-factory.ts","../../src/test-runner/config.ts"],"mappings":";;;;UAiDiB,6BAAA;ECPD;AAIhB;;;;EDSE,SAAA;ECPA;;;;EDYA,WAAA;ECKmC;;;;;;;;AAcrC;EDTE,SAAA;;EAEA,QAAA;ECSA;;;;;EDHA,IAAA;IAAS,OAAA;IAAiB,QAAA;EAAA;;;;;;;;EAQ1B,aAAA;ECqCc;;;;;;;;;;;;;;;;EDpBd,WAAA,GAAc,UAAA;AAAA;;;;;;;;;;;;;;;;;;iBAoBA,4BAAA,CACd,IAAA,EAAM,6BAAA,GACL,sBAAA;;;;;;UCjGc,eAAA;EDgGf;;;;EC3FA,OAAA;;;AALF;EASE,SAAA;;;;;EAKA,cAAA;AAAA;;UAIe,mBAAA;EACf,OAAA;EACA,SAAA;EACA,cAAA;AAAA;;;;;AAgBF;;;iBAAgB,qBAAA,CAAsB,UAAA,GAAa,mBAAA,GAAsB,eAAA;;;;;;AAczE;UAAiB,qBAAA;;EAEf,IAAA,EAAM,UAAA,QAAkB,eAAA;EAAxB;EAEA,OAAA;EAFwB;EAIxB,WAAA;AAAA;;UAIe,qBAAA,SAA8B,mBAAA;EAA9B;;;;;;;EAQf,UAAA,EAAY,sBAAA;EAQG;;AAkBjB;;;;;EAlBE,eAAA;AAAA;;;;;;;;;;;;;;;;iBAkBc,uBAAA,CAAwB,UAAA,EAAY,qBAAA,GAAwB,qBAAA"}
1
+ {"version":3,"file":"config.d.ts","names":[],"sources":["../../src/test-runner/relay-factory.ts","../../src/test-runner/config.ts"],"mappings":";;;;UAiDiB,6BAAA;ECrBe;;;;;;AAkBhC;;;EDaE,SAAA;ECZA;;;;;AAkBF;;;;;EDKE,cAAA;ECLuE;;;AAczE;;;;;;;;;EDIE,MAAA;ECEW;AAIb;;;EDDE,WAAA;ECC6C;;;;;;AAkC/C;;;EDzBE,SAAA;ECyBkD;EDvBlD,QAAA;ECuB0E;;;;;EDjB1E,IAAA;IAAS,OAAA;IAAiB,QAAA;EAAA;;;;;;;;;;EAU1B,YAAA;;;;;;;;;;;;EAYA,MAAA;;;;;;;;;;;;EAYA,YAAA;;;;;;;;EAQA,aAAA;;;;;;;;;;;;;;;;;EAiBA,WAAA,GAAc,UAAA;AAAA;;;;;;;;;;;;;;;;;;;iBAqBA,4BAAA,CACd,IAAA,EAAM,6BAAA,GACL,sBAAA;;;;;;UChKc,eAAA;ED8JD;;;;ECzJd,OAAA;ED0JA;;;ECtJA,SAAA;;;;AATF;EAcE,cAAA;AAAA;;UAIe,mBAAA;EACf,OAAA;EACA,SAAA;EACA,cAAA;AAAA;AAHF;;;;;;;AAAA,iBAmBgB,qBAAA,CAAsB,UAAA,GAAa,mBAAA,GAAsB,eAAA;;AAAzE;;;;;UAciB,qBAAA;EAdwD;EAgBvE,IAAA,EAAM,UAAA,QAAkB,eAAA;EAhB8D;EAkBtF,OAAA;EAJoC;EAMpC,WAAA;AAAA;;UAIe,qBAAA,SAA8B,mBAAA;EARrB;;;;;AAQ1B;;EAQE,UAAA,EAAY,sBAAA;EARoD;;;;;;;EAgBhE,eAAA;AAAA;;;;;;;;;;;;;;;;iBAkBc,uBAAA,CAAwB,UAAA,EAAY,qBAAA,GAAwB,qBAAA"}
@@ -1,5 +1,5 @@
1
1
  import { RELAY_POOL_NAME, createRelayPool } from "./pool.js";
2
- import { t as createRelayConnectionFactory } from "../relay-factory-DnuTH4v-.js";
2
+ import { t as createRelayConnectionFactory } from "../relay-factory-C1X3G3WY.js";
3
3
  //#region src/test-runner/config.ts
4
4
  const DEFAULT_CONFIG = {
5
5
  include: ["**/*.ait.test.ts"],
@@ -0,0 +1,82 @@
1
+ //#region src/test-runner/method-pace.d.ts
2
+ /**
3
+ * Per-method minimum-interval pacing for SDK calls (devtools#769).
4
+ *
5
+ * #767's `--pace` inserts delays BETWEEN test files / test-to-test — it has no
6
+ * effect on a burst of same-method calls WITHIN a single test body (e.g. a
7
+ * clipboard happy-path loop calling `setClipboardText`/`getClipboardText`
8
+ * eight times back to back). Real-device observation (sdk-example#293,
9
+ * 2.x×iOS) showed that burst alone is enough to saturate the native
10
+ * per-method bridge rate limit (`APP_BRIDGE_THROTTLED`, devtools#767/throttle.ts)
11
+ * even though `--pace`'s file/test spacing was already in effect — the
12
+ * limiter reacts to burst cadence within a method name, not just across test
13
+ * boundaries.
14
+ *
15
+ * This module wraps `window.__sdk` (the same bridge object `bundle.ts`'s
16
+ * `sdkRedirectPlugin` redirects SDK imports to) so that every call to the
17
+ * SAME named function waits out a minimum gap since that function's PREVIOUS
18
+ * invocation before the real call proceeds. Preflight (`cell.ts`) proved the
19
+ * inverse case empirically: a steady 250ms sequential cadence passes cleanly
20
+ * where an unpaced burst does not — sequential-with-spacing is the fix this
21
+ * module generalizes to every SDK call, not just the six permission probes.
22
+ *
23
+ * Runs entirely on the PAGE side (browser-compatible, no Node APIs) — bundled
24
+ * into the injected IIFE by `bundle.ts`'s `sdkRedirectPlugin`, the same way
25
+ * `bridge-stub.ts` and `runtime.ts` are. Imported ONLY from the test-runner
26
+ * (never from `sdk-example/src` — boilerplate cleanliness, umbrella §1.4).
27
+ *
28
+ * SECRET-HANDLING: this module carries no secrets — only method names and
29
+ * millisecond timestamps/durations.
30
+ */
31
+ /**
32
+ * Reads the page-global `__AIT_PACE_METHOD_MS__` gap value (devtools#769),
33
+ * injected by `relay-factory.ts` the same way `__AIT_PACE_MS__`/`__AIT_CELL__`
34
+ * are (`cell.ts#injectGlobals`). Absent/non-positive resolves to `0` — the
35
+ * same "treat absence as zero" contract `runtime.ts`'s `__AIT_PACE_MS__` read
36
+ * uses.
37
+ */
38
+ declare function getPaceMethodMs(): number;
39
+ /**
40
+ * Wraps an SDK-shaped bridge object (`window.__sdk`, or the already
41
+ * stub-wrapped result of `bridge-stub.ts#wrapSdkWithStub`) so that every
42
+ * function-valued export is paced to a minimum `gapMs` interval between
43
+ * calls to the SAME name (devtools#769). Non-function exports (constants,
44
+ * etc.) and classes (`PermissionError`, etc. — wrapping a class would break
45
+ * `new`, see `isClass`) pass through completely untouched.
46
+ *
47
+ * **Composition with bridge-stub (devtools#740)**: this wrapper is applied to
48
+ * the RAW `window.__sdk` object, and `wrapSdkWithStub` is applied on TOP of
49
+ * the paced result (`wrapSdkWithStub(wrapWithMethodPacing(rawSdk, gapMs),
50
+ * stubEnabled)` — see `bundle.ts`'s sdk-redirect virtual module). This means
51
+ * pacing sits BEHIND the stub interception: a stubbed name
52
+ * (`STUB_REGISTRY` — `openPermissionDialog`, `requestPermission`,
53
+ * `saveBase64Data`, `showFullScreenAd`) resolves instantly from the fixture
54
+ * with no added delay, because `wrapSdkWithStub`'s Proxy intercepts the
55
+ * property access before the paced function is ever reached. Only calls that
56
+ * fall through to the REAL bridge — the ones that actually risk
57
+ * `APP_BRIDGE_THROTTLED` — pay the pacing cost. Fixed by this ordering, not
58
+ * incidental: swapping it (pacing outside the stub) would pace fixture
59
+ * look-ups that can never hit the native rate limiter, for no benefit.
60
+ *
61
+ * `gapMs <= 0` (the default, no `--pace-method` / non-2.x cell) returns `sdk`
62
+ * itself, untouched — the exact object identity is preserved so the
63
+ * zero-diff-when-off contract holds under strict-equality checks, matching
64
+ * `wrapSdkWithStub`'s own `enabled === false` fast path.
65
+ *
66
+ * **Memoization**: wrapper functions are cached per name in a `Map` local to
67
+ * this call's `Proxy` (`wrapperCache` below), so repeated property access on
68
+ * the SAME `sdk` object within a single `wrapWithMethodPacing` call returns
69
+ * the SAME wrapper reference. This cache does NOT persist across separate
70
+ * `wrapWithMethodPacing` calls — each call (e.g. the sdk-redirect virtual
71
+ * module re-evaluated per test file bundle) gets its own fresh `Map` and
72
+ * therefore its own wrapper function instances, even for the same method
73
+ * name. That's fine for pacing correctness: the actual timing state lives in
74
+ * the page-global `getRegistry()` map (keyed by method name, not by wrapper
75
+ * identity), so cross-bundle pacing still works. But callers should NOT
76
+ * assume cross-call function identity — only "same wrapper within one call"
77
+ * is guaranteed.
78
+ */
79
+ declare function wrapWithMethodPacing(sdk: Record<string, unknown>, gapMs: number): Record<string, unknown>;
80
+ //#endregion
81
+ export { getPaceMethodMs, wrapWithMethodPacing };
82
+ //# sourceMappingURL=method-pace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method-pace.d.ts","names":[],"sources":["../../src/test-runner/method-pace.ts"],"mappings":";;AAmFA;;;;;AA4EA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA5EgB,eAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4EA,oBAAA,CACd,GAAA,EAAK,MAAA,mBACL,KAAA,WACC,MAAA"}
@@ -0,0 +1,120 @@
1
+ //#region src/test-runner/method-pace.ts
2
+ const REGISTRY_KEY = "__AIT_METHOD_PACE_STATE__";
3
+ /** Reads (or lazily creates) the page-global per-method pacing registry. */
4
+ function getRegistry() {
5
+ const g = globalThis;
6
+ let state = g[REGISTRY_KEY];
7
+ if (!(state instanceof Map)) {
8
+ state = /* @__PURE__ */ new Map();
9
+ g[REGISTRY_KEY] = state;
10
+ }
11
+ return state;
12
+ }
13
+ /** `setTimeout`-based sleep — no Node APIs, safe on the page side. */
14
+ function sleep(ms) {
15
+ return new Promise((resolve) => setTimeout(resolve, ms));
16
+ }
17
+ /**
18
+ * True when `value` is a `class` declaration rather than a plain callable
19
+ * function. The SDK export surface includes at least one class (`PermissionError`,
20
+ * `src/__typecheck.ts`/`bridge-stub.ts` both reference it) — wrapping a class
21
+ * in an async arrow function would silently break `new PermissionError()`
22
+ * (arrow functions are not constructible), so classes must pass through
23
+ * `wrapWithMethodPacing` unwrapped exactly like any other non-callable
24
+ * export. Detected via `Function.prototype.toString` starting with
25
+ * `"class "` — the standard, spec-guaranteed way to distinguish a class from
26
+ * an ordinary function at runtime (no Babel/TS transform in this bundle
27
+ * target — `target: 'es2022'` in `bundle.ts` — down-compiles `class` to a
28
+ * function, so this check remains reliable for esbuild's es2022 output).
29
+ */
30
+ function isClass(value) {
31
+ return Function.prototype.toString.call(value).startsWith("class ");
32
+ }
33
+ /**
34
+ * Reads the page-global `__AIT_PACE_METHOD_MS__` gap value (devtools#769),
35
+ * injected by `relay-factory.ts` the same way `__AIT_PACE_MS__`/`__AIT_CELL__`
36
+ * are (`cell.ts#injectGlobals`). Absent/non-positive resolves to `0` — the
37
+ * same "treat absence as zero" contract `runtime.ts`'s `__AIT_PACE_MS__` read
38
+ * uses.
39
+ */
40
+ function getPaceMethodMs() {
41
+ const raw = globalThis.__AIT_PACE_METHOD_MS__;
42
+ return typeof raw === "number" && raw > 0 ? raw : 0;
43
+ }
44
+ /**
45
+ * Wraps a single named SDK function so that calls to it wait out `gapMs`
46
+ * since the function's own previous invocation (tracked in the shared
47
+ * `getRegistry()` map, keyed by `name`) before the real `fn` runs. The
48
+ * result/rejection of `fn` passes through completely unchanged — pacing
49
+ * never alters contract-observed behavior, only its timing.
50
+ */
51
+ function buildPacedFunction(name, fn, gapMs) {
52
+ return async (...args) => {
53
+ const registry = getRegistry();
54
+ const now = performance.now();
55
+ const lastInvokeAt = registry.get(name);
56
+ if (lastInvokeAt !== void 0) {
57
+ const elapsed = now - lastInvokeAt;
58
+ if (elapsed < gapMs) await sleep(gapMs - elapsed);
59
+ }
60
+ registry.set(name, performance.now());
61
+ return fn(...args);
62
+ };
63
+ }
64
+ /**
65
+ * Wraps an SDK-shaped bridge object (`window.__sdk`, or the already
66
+ * stub-wrapped result of `bridge-stub.ts#wrapSdkWithStub`) so that every
67
+ * function-valued export is paced to a minimum `gapMs` interval between
68
+ * calls to the SAME name (devtools#769). Non-function exports (constants,
69
+ * etc.) and classes (`PermissionError`, etc. — wrapping a class would break
70
+ * `new`, see `isClass`) pass through completely untouched.
71
+ *
72
+ * **Composition with bridge-stub (devtools#740)**: this wrapper is applied to
73
+ * the RAW `window.__sdk` object, and `wrapSdkWithStub` is applied on TOP of
74
+ * the paced result (`wrapSdkWithStub(wrapWithMethodPacing(rawSdk, gapMs),
75
+ * stubEnabled)` — see `bundle.ts`'s sdk-redirect virtual module). This means
76
+ * pacing sits BEHIND the stub interception: a stubbed name
77
+ * (`STUB_REGISTRY` — `openPermissionDialog`, `requestPermission`,
78
+ * `saveBase64Data`, `showFullScreenAd`) resolves instantly from the fixture
79
+ * with no added delay, because `wrapSdkWithStub`'s Proxy intercepts the
80
+ * property access before the paced function is ever reached. Only calls that
81
+ * fall through to the REAL bridge — the ones that actually risk
82
+ * `APP_BRIDGE_THROTTLED` — pay the pacing cost. Fixed by this ordering, not
83
+ * incidental: swapping it (pacing outside the stub) would pace fixture
84
+ * look-ups that can never hit the native rate limiter, for no benefit.
85
+ *
86
+ * `gapMs <= 0` (the default, no `--pace-method` / non-2.x cell) returns `sdk`
87
+ * itself, untouched — the exact object identity is preserved so the
88
+ * zero-diff-when-off contract holds under strict-equality checks, matching
89
+ * `wrapSdkWithStub`'s own `enabled === false` fast path.
90
+ *
91
+ * **Memoization**: wrapper functions are cached per name in a `Map` local to
92
+ * this call's `Proxy` (`wrapperCache` below), so repeated property access on
93
+ * the SAME `sdk` object within a single `wrapWithMethodPacing` call returns
94
+ * the SAME wrapper reference. This cache does NOT persist across separate
95
+ * `wrapWithMethodPacing` calls — each call (e.g. the sdk-redirect virtual
96
+ * module re-evaluated per test file bundle) gets its own fresh `Map` and
97
+ * therefore its own wrapper function instances, even for the same method
98
+ * name. That's fine for pacing correctness: the actual timing state lives in
99
+ * the page-global `getRegistry()` map (keyed by method name, not by wrapper
100
+ * identity), so cross-bundle pacing still works. But callers should NOT
101
+ * assume cross-call function identity — only "same wrapper within one call"
102
+ * is guaranteed.
103
+ */
104
+ function wrapWithMethodPacing(sdk, gapMs) {
105
+ if (!(gapMs > 0)) return sdk;
106
+ const wrapperCache = /* @__PURE__ */ new Map();
107
+ return new Proxy(sdk, { get(target, prop, receiver) {
108
+ const value = Reflect.get(target, prop, receiver);
109
+ if (typeof prop !== "string" || typeof value !== "function" || isClass(value)) return value;
110
+ const cached = wrapperCache.get(prop);
111
+ if (cached !== void 0) return cached;
112
+ const wrapped = buildPacedFunction(prop, value, gapMs);
113
+ wrapperCache.set(prop, wrapped);
114
+ return wrapped;
115
+ } });
116
+ }
117
+ //#endregion
118
+ export { getPaceMethodMs, wrapWithMethodPacing };
119
+
120
+ //# sourceMappingURL=method-pace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"method-pace.js","names":[],"sources":["../../src/test-runner/method-pace.ts"],"sourcesContent":["/**\n * Per-method minimum-interval pacing for SDK calls (devtools#769).\n *\n * #767's `--pace` inserts delays BETWEEN test files / test-to-test — it has no\n * effect on a burst of same-method calls WITHIN a single test body (e.g. a\n * clipboard happy-path loop calling `setClipboardText`/`getClipboardText`\n * eight times back to back). Real-device observation (sdk-example#293,\n * 2.x×iOS) showed that burst alone is enough to saturate the native\n * per-method bridge rate limit (`APP_BRIDGE_THROTTLED`, devtools#767/throttle.ts)\n * even though `--pace`'s file/test spacing was already in effect — the\n * limiter reacts to burst cadence within a method name, not just across test\n * boundaries.\n *\n * This module wraps `window.__sdk` (the same bridge object `bundle.ts`'s\n * `sdkRedirectPlugin` redirects SDK imports to) so that every call to the\n * SAME named function waits out a minimum gap since that function's PREVIOUS\n * invocation before the real call proceeds. Preflight (`cell.ts`) proved the\n * inverse case empirically: a steady 250ms sequential cadence passes cleanly\n * where an unpaced burst does not — sequential-with-spacing is the fix this\n * module generalizes to every SDK call, not just the six permission probes.\n *\n * Runs entirely on the PAGE side (browser-compatible, no Node APIs) — bundled\n * into the injected IIFE by `bundle.ts`'s `sdkRedirectPlugin`, the same way\n * `bridge-stub.ts` and `runtime.ts` are. Imported ONLY from the test-runner\n * (never from `sdk-example/src` — boilerplate cleanliness, umbrella §1.4).\n *\n * SECRET-HANDLING: this module carries no secrets — only method names and\n * millisecond timestamps/durations.\n */\n\n/**\n * Page-global registry of per-method last-invocation timestamps\n * (`performance.now()`-relative, monotonic). One registry per page — shared\n * across every wrapped SDK object so repeated `wrapWithMethodPacing` calls\n * (e.g. the sdk-redirect virtual module re-evaluated per test file bundle)\n * still see the true last-invoke time of a given method name, not a fresh\n * per-bundle registry that would silently reset pacing at every file\n * boundary.\n */\ntype MethodPaceState = Map<string, number>;\n\nconst REGISTRY_KEY = '__AIT_METHOD_PACE_STATE__';\n\n/** Reads (or lazily creates) the page-global per-method pacing registry. */\nfunction getRegistry(): MethodPaceState {\n const g = globalThis as { [REGISTRY_KEY]?: MethodPaceState };\n let state = g[REGISTRY_KEY];\n if (!(state instanceof Map)) {\n state = new Map<string, number>();\n g[REGISTRY_KEY] = state;\n }\n return state;\n}\n\n/** `setTimeout`-based sleep — no Node APIs, safe on the page side. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * True when `value` is a `class` declaration rather than a plain callable\n * function. The SDK export surface includes at least one class (`PermissionError`,\n * `src/__typecheck.ts`/`bridge-stub.ts` both reference it) — wrapping a class\n * in an async arrow function would silently break `new PermissionError()`\n * (arrow functions are not constructible), so classes must pass through\n * `wrapWithMethodPacing` unwrapped exactly like any other non-callable\n * export. Detected via `Function.prototype.toString` starting with\n * `\"class \"` — the standard, spec-guaranteed way to distinguish a class from\n * an ordinary function at runtime (no Babel/TS transform in this bundle\n * target — `target: 'es2022'` in `bundle.ts` — down-compiles `class` to a\n * function, so this check remains reliable for esbuild's es2022 output).\n */\nfunction isClass(value: (...args: unknown[]) => unknown): boolean {\n return Function.prototype.toString.call(value).startsWith('class ');\n}\n\n/**\n * Reads the page-global `__AIT_PACE_METHOD_MS__` gap value (devtools#769),\n * injected by `relay-factory.ts` the same way `__AIT_PACE_MS__`/`__AIT_CELL__`\n * are (`cell.ts#injectGlobals`). Absent/non-positive resolves to `0` — the\n * same \"treat absence as zero\" contract `runtime.ts`'s `__AIT_PACE_MS__` read\n * uses.\n */\nexport function getPaceMethodMs(): number {\n const raw = (globalThis as { __AIT_PACE_METHOD_MS__?: unknown }).__AIT_PACE_METHOD_MS__;\n return typeof raw === 'number' && raw > 0 ? raw : 0;\n}\n\n/**\n * Wraps a single named SDK function so that calls to it wait out `gapMs`\n * since the function's own previous invocation (tracked in the shared\n * `getRegistry()` map, keyed by `name`) before the real `fn` runs. The\n * result/rejection of `fn` passes through completely unchanged — pacing\n * never alters contract-observed behavior, only its timing.\n */\nfunction buildPacedFunction(\n name: string,\n fn: (...args: unknown[]) => unknown,\n gapMs: number,\n): (...args: unknown[]) => unknown {\n return async (...args: unknown[]) => {\n const registry = getRegistry();\n const now = performance.now();\n const lastInvokeAt = registry.get(name);\n if (lastInvokeAt !== undefined) {\n const elapsed = now - lastInvokeAt;\n if (elapsed < gapMs) {\n await sleep(gapMs - elapsed);\n }\n }\n // Record the timestamp AFTER any wait but BEFORE the real call — this is\n // \"last invoke attempt\", matching the preflight's own \"sequential with\n // spacing before each call\" model rather than \"last completion\", so two\n // long-running calls in flight cannot both slip under the gap.\n registry.set(name, performance.now());\n return fn(...args);\n };\n}\n\n/**\n * Wraps an SDK-shaped bridge object (`window.__sdk`, or the already\n * stub-wrapped result of `bridge-stub.ts#wrapSdkWithStub`) so that every\n * function-valued export is paced to a minimum `gapMs` interval between\n * calls to the SAME name (devtools#769). Non-function exports (constants,\n * etc.) and classes (`PermissionError`, etc. — wrapping a class would break\n * `new`, see `isClass`) pass through completely untouched.\n *\n * **Composition with bridge-stub (devtools#740)**: this wrapper is applied to\n * the RAW `window.__sdk` object, and `wrapSdkWithStub` is applied on TOP of\n * the paced result (`wrapSdkWithStub(wrapWithMethodPacing(rawSdk, gapMs),\n * stubEnabled)` — see `bundle.ts`'s sdk-redirect virtual module). This means\n * pacing sits BEHIND the stub interception: a stubbed name\n * (`STUB_REGISTRY` — `openPermissionDialog`, `requestPermission`,\n * `saveBase64Data`, `showFullScreenAd`) resolves instantly from the fixture\n * with no added delay, because `wrapSdkWithStub`'s Proxy intercepts the\n * property access before the paced function is ever reached. Only calls that\n * fall through to the REAL bridge — the ones that actually risk\n * `APP_BRIDGE_THROTTLED` — pay the pacing cost. Fixed by this ordering, not\n * incidental: swapping it (pacing outside the stub) would pace fixture\n * look-ups that can never hit the native rate limiter, for no benefit.\n *\n * `gapMs <= 0` (the default, no `--pace-method` / non-2.x cell) returns `sdk`\n * itself, untouched — the exact object identity is preserved so the\n * zero-diff-when-off contract holds under strict-equality checks, matching\n * `wrapSdkWithStub`'s own `enabled === false` fast path.\n *\n * **Memoization**: wrapper functions are cached per name in a `Map` local to\n * this call's `Proxy` (`wrapperCache` below), so repeated property access on\n * the SAME `sdk` object within a single `wrapWithMethodPacing` call returns\n * the SAME wrapper reference. This cache does NOT persist across separate\n * `wrapWithMethodPacing` calls — each call (e.g. the sdk-redirect virtual\n * module re-evaluated per test file bundle) gets its own fresh `Map` and\n * therefore its own wrapper function instances, even for the same method\n * name. That's fine for pacing correctness: the actual timing state lives in\n * the page-global `getRegistry()` map (keyed by method name, not by wrapper\n * identity), so cross-bundle pacing still works. But callers should NOT\n * assume cross-call function identity — only \"same wrapper within one call\"\n * is guaranteed.\n */\nexport function wrapWithMethodPacing(\n sdk: Record<string, unknown>,\n gapMs: number,\n): Record<string, unknown> {\n if (!(gapMs > 0)) return sdk;\n\n const wrapperCache = new Map<string, (...args: unknown[]) => unknown>();\n\n return new Proxy(sdk, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (\n typeof prop !== 'string' ||\n typeof value !== 'function' ||\n isClass(value as (...args: unknown[]) => unknown)\n ) {\n // Non-function export (constants, symbols) OR a class (e.g.\n // PermissionError) — pass through unchanged. Wrapping a class would\n // break `new`, and per-name pacing only makes sense for callable SDK\n // methods anyway.\n return value;\n }\n const cached = wrapperCache.get(prop);\n if (cached !== undefined) return cached;\n const wrapped = buildPacedFunction(prop, value as (...args: unknown[]) => unknown, gapMs);\n wrapperCache.set(prop, wrapped);\n return wrapped;\n },\n });\n}\n"],"mappings":";AAyCA,MAAM,eAAe;;AAGrB,SAAS,cAA+B;CACtC,MAAM,IAAI;CACV,IAAI,QAAQ,EAAE;AACd,KAAI,EAAE,iBAAiB,MAAM;AAC3B,0BAAQ,IAAI,KAAqB;AACjC,IAAE,gBAAgB;;AAEpB,QAAO;;;AAIT,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;AAgB1D,SAAS,QAAQ,OAAiD;AAChE,QAAO,SAAS,UAAU,SAAS,KAAK,MAAM,CAAC,WAAW,SAAS;;;;;;;;;AAUrE,SAAgB,kBAA0B;CACxC,MAAM,MAAO,WAAoD;AACjE,QAAO,OAAO,QAAQ,YAAY,MAAM,IAAI,MAAM;;;;;;;;;AAUpD,SAAS,mBACP,MACA,IACA,OACiC;AACjC,QAAO,OAAO,GAAG,SAAoB;EACnC,MAAM,WAAW,aAAa;EAC9B,MAAM,MAAM,YAAY,KAAK;EAC7B,MAAM,eAAe,SAAS,IAAI,KAAK;AACvC,MAAI,iBAAiB,KAAA,GAAW;GAC9B,MAAM,UAAU,MAAM;AACtB,OAAI,UAAU,MACZ,OAAM,MAAM,QAAQ,QAAQ;;AAOhC,WAAS,IAAI,MAAM,YAAY,KAAK,CAAC;AACrC,SAAO,GAAG,GAAG,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CtB,SAAgB,qBACd,KACA,OACyB;AACzB,KAAI,EAAE,QAAQ,GAAI,QAAO;CAEzB,MAAM,+BAAe,IAAI,KAA8C;AAEvE,QAAO,IAAI,MAAM,KAAK,EACpB,IAAI,QAAQ,MAAM,UAAU;EAC1B,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,SAAS;AACjD,MACE,OAAO,SAAS,YAChB,OAAO,UAAU,cACjB,QAAQ,MAAyC,CAMjD,QAAO;EAET,MAAM,SAAS,aAAa,IAAI,KAAK;AACrC,MAAI,WAAW,KAAA,EAAW,QAAO;EACjC,MAAM,UAAU,mBAAmB,MAAM,OAA0C,MAAM;AACzF,eAAa,IAAI,MAAM,QAAQ;AAC/B,SAAO;IAEV,CAAC"}
@@ -1,2 +1,2 @@
1
- import { i as createRelayPool, n as RelayConnectionFactory, r as RelayPoolOptions, t as RELAY_POOL_NAME } from "../pool-C6TgrcyW.js";
1
+ import { i as createRelayPool, n as RelayConnectionFactory, r as RelayPoolOptions, t as RELAY_POOL_NAME } from "../pool-CrP5CPvU.js";
2
2
  export { RELAY_POOL_NAME, RelayConnectionFactory, RelayPoolOptions, createRelayPool };
@@ -1,4 +1,4 @@
1
- import { i as runTestFilesOverRelay } from "../relay-worker-UK0nBfDX.js";
1
+ import { i as runTestFilesOverRelay } from "../relay-worker-BUXI0K0b.js";
2
2
  import { synthesizeFileTask, toTaskEventPacks, toTaskResultPacks } from "./task-graph.js";
3
3
  //#region src/test-runner/pool.ts
4
4
  /** The pool name Vitest matches against `getFilePoolName(project)`. */
@@ -11105,8 +11105,36 @@ interface RelayConnectionFactoryOptions {
11105
11105
  * intoss-private:// scheme URL from `ait deploy --scheme-only` (env3). The
11106
11106
  * phone cold-loads the candidate bundle this URL points at. SECRET-HANDLING:
11107
11107
  * never logged.
11108
+ *
11109
+ * Ignored when {@link attachLauncher} is `true` — the env-2 launcher path
11110
+ * builds its attach URL from {@link appUrl} + the relay wss, not a scheme URL.
11111
+ * Pass `''` in that case.
11108
11112
  */
11109
11113
  schemeUrl: string;
11114
+ /**
11115
+ * env-2 (AITC Sandbox PWA) attach mode (devtools#774). When `true`, `open()`
11116
+ * builds the attach URL as a **launcher deep-link**
11117
+ * (`buildLauncherAttachUrl(appUrl, wssUrl, totp)` — the same env-2 path the
11118
+ * MCP daemon takes via `prepareAttach(deps, 'relay-mobile', …)`, issue #378)
11119
+ * instead of an intoss-private scheme deep-link. The relay boot, QR
11120
+ * rendering, dashboard, and attach-wait are byte-for-byte identical to env 3;
11121
+ * only the URL family differs. Omitted/false keeps the env-3 scheme path
11122
+ * unchanged (zero behavior change).
11123
+ */
11124
+ attachLauncher?: boolean;
11125
+ /**
11126
+ * The consumer dev server's HTTP tunnel URL (e.g. an sdk-example
11127
+ * `dev:phone:cdp` `*.trycloudflare.com` URL) that the launcher PWA frames.
11128
+ * REQUIRED when {@link attachLauncher} is `true`; ignored otherwise.
11129
+ *
11130
+ * Threaded to the `relay-mobile` `prepareAttach` branch via the
11131
+ * `AIT_TUNNEL_BASE_URL` env var (the same key the MCP daemon reads), so the
11132
+ * exact same env-2 assembly runs — no divergent code path.
11133
+ *
11134
+ * SECRET-HANDLING: this is a tunnel host at the same sensitivity as the relay
11135
+ * wss — NEVER logged. It rides only inside the QR/dashboard attach capsule.
11136
+ */
11137
+ appUrl?: string;
11110
11138
  /**
11111
11139
  * Project root for the `.ait_relay` secret lookup (read-only). Defaults to
11112
11140
  * `process.cwd()`.
@@ -11133,6 +11161,40 @@ interface RelayConnectionFactoryOptions {
11133
11161
  sdkLine: string;
11134
11162
  platform: string;
11135
11163
  };
11164
+ /**
11165
+ * When `true`, injects `__AIT_STUB_BLOCKING__ = true` before the first test
11166
+ * bundle runs (devtools#740, DT-2) — `bundle.ts`'s sdk-redirect module reads
11167
+ * this flag via `isStubBlockingEnabled()` and answers the blocking-UI APIs
11168
+ * in `bridge-stub.ts`'s `STUB_REGISTRY` from fixtures instead of forwarding
11169
+ * them to native. Optional — omitted/false means no flag is injected, which
11170
+ * is byte-for-byte the pre-#740 behavior (the sdk-redirect module treats
11171
+ * absence the same as `false`). Not a secret.
11172
+ */
11173
+ stubBlocking?: boolean;
11174
+ /**
11175
+ * Injects `__AIT_PACE_MS__ = paceMs` before the first test bundle runs
11176
+ * (devtools#767) — `runtime.ts`'s test-execution loop reads this page
11177
+ * global and waits `paceMs` before every test after the first one that
11178
+ * actually runs. Optional — omitted/0 means no flag is injected, which is
11179
+ * byte-for-byte the pre-#767 behavior (the loop treats an absent/non-
11180
+ * positive value as "no pacing"). Not a secret. This is the PAGE-side half
11181
+ * of `--pace`; the RUNNER-side (file-to-file) half is a plain `opts.paceMs`
11182
+ * forwarded to `runTestFilesOverRelay` by the caller (cli.ts) — this
11183
+ * factory only owns the page-global injection.
11184
+ */
11185
+ paceMs?: number;
11186
+ /**
11187
+ * Injects `__AIT_PACE_METHOD_MS__ = paceMethodMs` before the first test
11188
+ * bundle runs (devtools#769) — `bundle.ts`'s sdk-redirect virtual module
11189
+ * reads this page global via `method-pace.ts#getPaceMethodMs()` and paces
11190
+ * every SDK call to a minimum interval BETWEEN calls to the SAME named
11191
+ * function, independent of `--pace`'s test-to-test/file-to-file spacing.
11192
+ * Optional — omitted/0 means no flag is injected, which is byte-for-byte
11193
+ * the pre-#769 behavior (`getPaceMethodMs()` treats an absent/non-positive
11194
+ * value as "no pacing", and `wrapWithMethodPacing` returns the SDK object
11195
+ * untouched in that case). Not a secret.
11196
+ */
11197
+ paceMethodMs?: number;
11136
11198
  /**
11137
11199
  * Overrides the dashboard HTTP server's bind base port (devtools#752).
11138
11200
  * Threaded straight through to `startQrHttpServer`'s `dashboardPort`
@@ -11160,12 +11222,13 @@ interface RelayConnectionFactoryOptions {
11160
11222
  onQrContent: (textChunks: string[]) => void;
11161
11223
  }
11162
11224
  /**
11163
- * Builds a {@link RelayConnectionFactory} that opens a standalone env3 relay
11164
- * connection.
11225
+ * Builds a {@link RelayConnectionFactory} that opens a standalone relay
11226
+ * connection for env 3 (intoss-private scheme, default) or env 2 (AITC Sandbox
11227
+ * PWA launcher deep-link, when `opts.attachLauncher` is set — devtools#774).
11165
11228
  *
11166
11229
  * `open()` performs the full attach lifecycle and BLOCKS while a human scans
11167
- * the rendered QR with their phone — there is no way around the manual scan
11168
- * for env3. By default the wait is UNBOUNDED (`opts.timeoutMs` omitted): the
11230
+ * the rendered QR with their phone — there is no way around the manual scan.
11231
+ * By default the wait is UNBOUNDED (`opts.timeoutMs` omitted): the
11169
11232
  * runner stays up until the user stops it (Ctrl-C/SIGTERM), since QR-scan is
11170
11233
  * a human-paced action with no sound default bound (devtools#735). Passing an
11171
11234
  * explicit `timeoutMs` opts into the old bounded behavior (CI/headless