@ait-co/devtools 0.1.137 → 0.1.139

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 (119) 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-CBK7dn8O.js → debug-server-6c22qSWa.js} +132 -9
  20. package/dist/debug-server-6c22qSWa.js.map +1 -0
  21. package/dist/{debug-server-BQplCzf8.js → debug-server-BQHbaKPT.js} +134 -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 +299 -60
  38. package/dist/mcp/cli.js.map +1 -1
  39. package/dist/mcp/server.js +1 -1
  40. package/dist/mock/index.d.ts +79 -1
  41. package/dist/mock/index.d.ts.map +1 -1
  42. package/dist/mock/index.js +218 -14
  43. package/dist/mock/index.js.map +1 -1
  44. package/dist/panel/index.js +171 -10
  45. package/dist/panel/index.js.map +1 -1
  46. package/dist/{pool-CmfvXnwh.d.ts → pool-CrP5CPvU.d.ts} +3 -3
  47. package/dist/{pool-CmfvXnwh.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-DVzpBRxz.js → relay-factory-C1X3G3WY.js} +71 -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-BcVrWwTq.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-DGduVJhs.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-6fy1eaIo.js → relay-worker-BUXI0K0b.js} +8 -4
  73. package/dist/{relay-worker-6fy1eaIo.js.map → relay-worker-BUXI0K0b.js.map} +1 -1
  74. package/dist/{relay-worker-DJnZkXza.d.ts → relay-worker-DcyboK43.d.ts} +30 -5
  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 +376 -38
  79. package/dist/test-runner/bin.js.map +1 -1
  80. package/dist/test-runner/bundle.d.ts +2 -2
  81. package/dist/test-runner/bundle.js +66 -13
  82. package/dist/test-runner/bundle.js.map +1 -1
  83. package/dist/test-runner/capture.d.ts +1 -1
  84. package/dist/test-runner/config.d.ts +58 -5
  85. package/dist/test-runner/config.d.ts.map +1 -1
  86. package/dist/test-runner/config.js +1 -1
  87. package/dist/test-runner/method-pace.d.ts +82 -0
  88. package/dist/test-runner/method-pace.d.ts.map +1 -0
  89. package/dist/test-runner/method-pace.js +120 -0
  90. package/dist/test-runner/method-pace.js.map +1 -0
  91. package/dist/test-runner/pool.d.ts +1 -1
  92. package/dist/test-runner/pool.js +1 -1
  93. package/dist/test-runner/relay-factory.d.ts +57 -4
  94. package/dist/test-runner/relay-factory.d.ts.map +1 -1
  95. package/dist/test-runner/relay-factory.js +70 -14
  96. package/dist/test-runner/relay-factory.js.map +1 -1
  97. package/dist/test-runner/relay-worker.d.ts +1 -1
  98. package/dist/test-runner/relay-worker.js +1 -1
  99. package/dist/test-runner/report.d.ts +3 -3
  100. package/dist/test-runner/rpc.d.ts +2 -2
  101. package/dist/test-runner/runtime.d.ts +2 -2
  102. package/dist/test-runner/runtime.js +83 -6
  103. package/dist/test-runner/runtime.js.map +1 -1
  104. package/dist/test-runner/task-graph.d.ts +1 -1
  105. package/dist/throttle-DKKzX1qC.js +59 -0
  106. package/dist/throttle-DKKzX1qC.js.map +1 -0
  107. package/dist/totp-Dwft0Kz7.js +3 -0
  108. package/package.json +1 -1
  109. package/dist/bundle-BKqyhEK9.d.ts.map +0 -1
  110. package/dist/cell-BIdSQb9T.js.map +0 -1
  111. package/dist/cell-D1y4shoV.js.map +0 -1
  112. package/dist/cell-DHA578lX.js.map +0 -1
  113. package/dist/debug-server-BQplCzf8.js.map +0 -1
  114. package/dist/debug-server-CBK7dn8O.js.map +0 -1
  115. package/dist/debug-server-D2wpPK3_.js.map +0 -1
  116. package/dist/relay-factory-DVzpBRxz.js.map +0 -1
  117. package/dist/relay-worker-DJnZkXza.d.ts.map +0 -1
  118. package/dist/runtime-DfHHZms2.d.ts.map +0 -1
  119. package/dist/totp-D1pulXLa.js +0 -3
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { m as logInfo } from "../attach-orchestrator-CE0S09YU.js";
2
+ import { m as logInfo } from "../attach-orchestrator-BEiz3wAx.js";
3
3
  import { r as generateTotp } from "../totp-CZLLKfOC.js";
4
- import { n as runPermissionPreflight } from "../cell-BIdSQb9T.js";
4
+ import { r as runPermissionPreflight, t as PERMISSION_PREFLIGHT_TIMEOUT_MS } from "../cell-KsRXFdDs.js";
5
5
  import * as path$1 from "node:path";
6
6
  import path, { basename, isAbsolute, resolve } from "node:path";
7
7
  import { parseArgs } from "node:util";
@@ -93,12 +93,13 @@ function partitionManualTests(files) {
93
93
  //#endregion
94
94
  //#region src/test-runner/relay-factory.ts
95
95
  /**
96
- * Builds a {@link RelayConnectionFactory} that opens a standalone env3 relay
97
- * connection.
96
+ * Builds a {@link RelayConnectionFactory} that opens a standalone relay
97
+ * connection for env 3 (intoss-private scheme, default) or env 2 (AITC Sandbox
98
+ * PWA launcher deep-link, when `opts.attachLauncher` is set — devtools#774).
98
99
  *
99
100
  * `open()` performs the full attach lifecycle and BLOCKS while a human scans
100
- * the rendered QR with their phone — there is no way around the manual scan
101
- * for env3. By default the wait is UNBOUNDED (`opts.timeoutMs` omitted): the
101
+ * the rendered QR with their phone — there is no way around the manual scan.
102
+ * By default the wait is UNBOUNDED (`opts.timeoutMs` omitted): the
102
103
  * runner stays up until the user stops it (Ctrl-C/SIGTERM), since QR-scan is
103
104
  * a human-paced action with no sound default bound (devtools#735). Passing an
104
105
  * explicit `timeoutMs` opts into the old bounded behavior (CI/headless
@@ -115,14 +116,17 @@ function createRelayConnectionFactory(opts) {
115
116
  const headless = opts.headless === true;
116
117
  let family;
117
118
  let qrServer;
119
+ let attachWatcher;
118
120
  let phase = "active";
119
121
  let manualPrompt = null;
120
122
  return {
121
123
  async open() {
122
- const { prepareAttach, renderAndMaybeWait, mintAttachUrl } = await import("../attach-orchestrator-CE0S09YU.js").then((n) => n.i);
123
- const { injectDebugIndicator, injectGlobals } = await import("../cell-BIdSQb9T.js").then((n) => n.t);
124
- const { loadRelaySecretReadOnly } = await import("../relay-secret-store-CLEGyHou.js");
125
- const { bootRelayFamily, buildRelayVerifyAuth } = await import("../debug-server-D2wpPK3_.js");
124
+ const { prepareAttach, renderAndMaybeWait, mintAttachUrl } = await import("../attach-orchestrator-BEiz3wAx.js").then((n) => n.i);
125
+ const { injectDebugIndicator, injectGlobals } = await import("../cell-KsRXFdDs.js").then((n) => n.n);
126
+ const { loadRelaySecretReadOnly } = await import("../relay-secret-store-Bmyleu0A.js");
127
+ const { bootRelayFamily, buildRelayVerifyAuth, startAttachWatcher } = await import("../debug-server-DWat7d-9.js");
128
+ const { buildChiiInspectorUrl } = await import("../devtools-opener-CJpEsXXQ.js");
129
+ const { generateTotp } = await import("../totp-CZLLKfOC.js").then((n) => n.i);
126
130
  await loadRelaySecretReadOnly({ projectRoot });
127
131
  let resolveTunnelUp;
128
132
  const tunnelReady = new Promise((resolve) => {
@@ -152,29 +156,73 @@ function createRelayConnectionFactory(opts) {
152
156
  canOpenBrowser: () => !headless
153
157
  };
154
158
  try {
155
- const { startQrHttpServer } = await import("../qr-http-server-C7q8Gn04.js");
159
+ const { startQrHttpServer } = await import("../qr-http-server-DIjCV7h_.js");
156
160
  const getDashboardState = () => ({
157
161
  tunnel: attachDeps.getTunnelStatus(),
158
- pages: null,
162
+ pages: booted.connection.listTargets().map((t) => ({
163
+ id: t.id,
164
+ url: t.url
165
+ })),
159
166
  attachUrl: lastAttachParts ? mintAttachUrl(attachDeps, lastAttachParts) : null,
160
- mode: "relay-dev",
167
+ mode: opts.attachLauncher === true ? "relay-mobile" : "relay-dev",
161
168
  phase,
162
169
  manualPrompt
163
170
  });
164
- qrServer = await startQrHttpServer(getDashboardState, { dashboardPort: opts.dashboardPort });
171
+ const getDirectInspectorUrl = () => {
172
+ if (!booted.relayHttpUrl) return {
173
+ ok: false,
174
+ reason: "relayDown"
175
+ };
176
+ const targets = booted.connection.listTargets();
177
+ if (targets.length === 0) return {
178
+ ok: false,
179
+ reason: "noTarget"
180
+ };
181
+ const totpSecret = process.env.AIT_DEBUG_TOTP_SECRET;
182
+ if (!totpSecret) return {
183
+ ok: false,
184
+ reason: "totpUnavailable"
185
+ };
186
+ const url = buildChiiInspectorUrl(booted.relayHttpUrl, targets[0].id, () => generateTotp(totpSecret, Date.now()));
187
+ if (url === null) return {
188
+ ok: false,
189
+ reason: "totpUnavailable"
190
+ };
191
+ return {
192
+ ok: true,
193
+ url
194
+ };
195
+ };
196
+ qrServer = await startQrHttpServer(getDashboardState, {
197
+ dashboardPort: opts.dashboardPort,
198
+ getDirectInspectorUrl
199
+ });
165
200
  attachDeps.qrHttpServer = qrServer;
166
201
  attachDeps.onAttachUrlBuilt = (parts) => {
167
202
  lastAttachParts = parts;
168
203
  qrServer?.notifyStateChange();
169
204
  };
205
+ attachWatcher = startAttachWatcher(booted.connection, void 0, 1e3, () => qrServer?.notifyStateChange(), () => qrServer?.notifyStateChange());
170
206
  process.stderr.write(`devtools-test: QR dashboard: http://127.0.0.1:${qrServer.port}/\n`);
171
207
  } catch {}
172
208
  const TUNNEL_BOOT_TIMEOUT_MS = 15e3;
173
209
  await Promise.race([tunnelReady, new Promise((resolve) => setTimeout(resolve, TUNNEL_BOOT_TIMEOUT_MS))]);
174
- const prep = await prepareAttach(attachDeps, "relay-dev", { scheme_url: opts.schemeUrl }, booted.connection);
210
+ let prep;
211
+ if (opts.attachLauncher === true) {
212
+ const priorTunnelBaseUrl = process.env.AIT_TUNNEL_BASE_URL;
213
+ process.env.AIT_TUNNEL_BASE_URL = opts.appUrl ?? "";
214
+ try {
215
+ prep = await prepareAttach(attachDeps, "relay-mobile", {}, booted.connection);
216
+ } finally {
217
+ if (priorTunnelBaseUrl === void 0) delete process.env.AIT_TUNNEL_BASE_URL;
218
+ else process.env.AIT_TUNNEL_BASE_URL = priorTunnelBaseUrl;
219
+ }
220
+ } else prep = await prepareAttach(attachDeps, "relay-dev", { scheme_url: opts.schemeUrl }, booted.connection);
175
221
  if (!prep.ok) {
176
222
  booted.stop();
177
223
  family = void 0;
224
+ attachWatcher?.stop();
225
+ attachWatcher = void 0;
178
226
  await qrServer?.close();
179
227
  qrServer = void 0;
180
228
  throw new Error("createRelayConnectionFactory: attach preparation failed — check the scheme_url and that the relay tunnel is up");
@@ -183,6 +231,8 @@ function createRelayConnectionFactory(opts) {
183
231
  if (waitResult.isError) {
184
232
  booted.stop();
185
233
  family = void 0;
234
+ attachWatcher?.stop();
235
+ attachWatcher = void 0;
186
236
  await qrServer?.close();
187
237
  qrServer = void 0;
188
238
  const timeoutSec = Math.round(timeoutMs / 1e3);
@@ -204,6 +254,8 @@ function createRelayConnectionFactory(opts) {
204
254
  if (lastEnableError !== void 0) {
205
255
  booted.stop();
206
256
  family = void 0;
257
+ attachWatcher?.stop();
258
+ attachWatcher = void 0;
207
259
  await qrServer?.close();
208
260
  qrServer = void 0;
209
261
  throw new Error(`createRelayConnectionFactory: page did not become ready after ${PAGE_READY_RETRIES} attempts (${Math.round(PAGE_READY_RETRIES * PAGE_READY_RETRY_DELAY_MS / 1e3)}s) — the mini-app page may have disconnected before enableDomains() could open the CDP websocket`);
@@ -211,6 +263,8 @@ function createRelayConnectionFactory(opts) {
211
263
  await injectDebugIndicator(booted.connection);
212
264
  if (opts.cell !== void 0) await injectGlobals(booted.connection, { __AIT_CELL__: opts.cell });
213
265
  if (opts.stubBlocking === true) await injectGlobals(booted.connection, { __AIT_STUB_BLOCKING__: true });
266
+ if (opts.paceMs !== void 0 && opts.paceMs > 0) await injectGlobals(booted.connection, { __AIT_PACE_MS__: opts.paceMs });
267
+ if (opts.paceMethodMs !== void 0 && opts.paceMethodMs > 0) await injectGlobals(booted.connection, { __AIT_PACE_METHOD_MS__: opts.paceMethodMs });
214
268
  return booted.connection;
215
269
  },
216
270
  onSessionPhase(next) {
@@ -227,11 +281,13 @@ function createRelayConnectionFactory(opts) {
227
281
  qrServer?.notifyStateChange();
228
282
  }
229
283
  try {
230
- const { injectDebugIndicator } = await import("../cell-BIdSQb9T.js").then((n) => n.t);
284
+ const { injectDebugIndicator } = await import("../cell-KsRXFdDs.js").then((n) => n.n);
231
285
  await injectDebugIndicator(connection, { state: "disconnected" });
232
286
  } catch {}
233
287
  family?.stop();
234
288
  family = void 0;
289
+ attachWatcher?.stop();
290
+ attachWatcher = void 0;
235
291
  await qrServer?.close();
236
292
  qrServer = void 0;
237
293
  }
@@ -917,7 +973,63 @@ const SDK_IMPORT_FILTER = new RegExp(`^${SDK_PACKAGE.replace(/[.*+?^${}()|[\]\\]
917
973
  * `--stub-blocking` flag). Default off => zero behavior change: every existing
918
974
  * (non-stub) test run resolves the exact same `window.__sdk` reference it did
919
975
  * before this plugin learned about stubbing.
976
+ *
977
+ * Per-method pacing (devtools#769): BEFORE the stub wrap, `window.__sdk` is
978
+ * run through `wrapWithMethodPacing`, which enforces a minimum interval
979
+ * between calls to the SAME named SDK function (`getPaceMethodMs()` reads the
980
+ * `__AIT_PACE_METHOD_MS__` page global — see `method-pace.ts`). Composition
981
+ * order is pacing INSIDE the stub (`wrapSdkWithStub(wrapWithMethodPacing(sdk,
982
+ * gap), enabled)`): a stubbed name resolves instantly from
983
+ * `bridge-stub.ts`'s fixtures without ever reaching the paced wrapper, and
984
+ * only calls that fall through to the real bridge — the ones that can
985
+ * actually trigger `APP_BRIDGE_THROTTLED` — pay the pacing cost. `gapMs <= 0`
986
+ * (default, no `--pace-method` / non-2.x cell) is a no-op fast path — see
987
+ * `method-pace.ts#wrapWithMethodPacing`'s doc for the full contract.
920
988
  */
989
+ /**
990
+ * Builds the virtual CommonJS-style module contents the `sdk-redirect`
991
+ * esbuild plugin loads for every `@apps-in-toss/web-framework` import.
992
+ *
993
+ * Pulled out to a pure, exported function (rather than inlined at the
994
+ * `build.onLoad` call site) so unit tests can assert the exact composition
995
+ * order (pacing wraps the raw SDK; the stub wraps the paced result) as a
996
+ * string-level contract, independent of esbuild — esbuild cannot run inside
997
+ * this repo's jsdom vitest environment (see `bundleTestFile`'s module doc's
998
+ * lazy-import note), so a real bundling round-trip is out of unit-test reach
999
+ * here; `wrapSdkWithStub`/`wrapWithMethodPacing`'s own composed-call behavior
1000
+ * is covered directly (see `bundle.test.ts`).
1001
+ *
1002
+ * Generates a virtual CommonJS-style module so that esbuild does NOT perform
1003
+ * strict named-export matching. When `format:'iife'` bundles a CJS module, it
1004
+ * wraps it with its own `__toCommonJS` helper and satisfies named imports via
1005
+ * property access on the `module.exports` object — which is our Proxy. This
1006
+ * means `import { getPlatformOS } from '...'` becomes
1007
+ * `__proxy.getPlatformOS` at runtime, which correctly reads from
1008
+ * `window.__sdk`.
1009
+ *
1010
+ * The bridge-stub and method-pace imports are STATIC imports of the real
1011
+ * `bridge-stub.ts`/`method-pace.ts` modules (not inlined JS) so esbuild
1012
+ * bundles each once and the fixture registry / pacing registry have a single
1013
+ * source of truth shared with their own unit tests.
1014
+ *
1015
+ * Exported for unit testing.
1016
+ */
1017
+ function buildSdkRedirectModuleContents() {
1018
+ return `
1019
+ import { wrapSdkWithStub, isStubBlockingEnabled } from ${JSON.stringify(getBridgeStubPath())};
1020
+ import { wrapWithMethodPacing, getPaceMethodMs } from ${JSON.stringify(getMethodPacePath())};
1021
+ var __rawSdk = (typeof window !== 'undefined' && window.__sdk)
1022
+ ? window.__sdk
1023
+ : new Proxy({}, {
1024
+ get: function(_t, p) {
1025
+ throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));
1026
+ }
1027
+ });
1028
+ var __pacedSdk = wrapWithMethodPacing(__rawSdk, getPaceMethodMs());
1029
+ var __proxy = wrapSdkWithStub(__pacedSdk, isStubBlockingEnabled());
1030
+ module.exports = __proxy;
1031
+ `;
1032
+ }
921
1033
  function sdkRedirectPlugin() {
922
1034
  return {
923
1035
  name: "sdk-redirect",
@@ -930,18 +1042,7 @@ function sdkRedirectPlugin() {
930
1042
  filter: /.*/,
931
1043
  namespace: "sdk-redirect"
932
1044
  }, () => ({
933
- contents: `
934
- import { wrapSdkWithStub, isStubBlockingEnabled } from ${JSON.stringify(getBridgeStubPath())};
935
- var __rawSdk = (typeof window !== 'undefined' && window.__sdk)
936
- ? window.__sdk
937
- : new Proxy({}, {
938
- get: function(_t, p) {
939
- throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));
940
- }
941
- });
942
- var __proxy = wrapSdkWithStub(__rawSdk, isStubBlockingEnabled());
943
- module.exports = __proxy;
944
- `,
1045
+ contents: buildSdkRedirectModuleContents(),
945
1046
  loader: "js",
946
1047
  resolveDir: process.cwd()
947
1048
  }));
@@ -1142,6 +1243,14 @@ function getBridgeStubPath() {
1142
1243
  return getPageSideModulePath("bridge-stub");
1143
1244
  }
1144
1245
  /**
1246
+ * Absolute path to the per-method pacing wrapper (devtools#769) —
1247
+ * `wrapWithMethodPacing`/`getPaceMethodMs`, statically imported by the
1248
+ * `sdk-redirect` virtual module.
1249
+ */
1250
+ function getMethodPacePath() {
1251
+ return getPageSideModulePath("method-pace");
1252
+ }
1253
+ /**
1145
1254
  * Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.
1146
1255
  *
1147
1256
  * The IIFE installs `window.__testBundle` (or the custom `globalName`) with:
@@ -1415,13 +1524,17 @@ async function runTestFilesOverRelay(connection, files, opts) {
1415
1524
  let manualIndex = 0;
1416
1525
  const stubBlockingFiles = opts?.stubBlockingFiles;
1417
1526
  let preflightPermissions;
1418
- if (files.length > 0) preflightPermissions = await runPermissionPreflight(connection);
1527
+ if (files.length > 0) preflightPermissions = await runPermissionPreflight(connection, PERMISSION_PREFLIGHT_TIMEOUT_MS, opts?.preflightSdkLine !== "3.x");
1528
+ const paceMs = opts?.paceMs;
1529
+ let ranFirstFile = false;
1419
1530
  try {
1420
1531
  for (const file of files) {
1421
1532
  if (pendingReconnectCheck) {
1422
1533
  await attemptReconnect("next-file");
1423
1534
  pendingReconnectCheck = false;
1424
1535
  }
1536
+ if (paceMs !== void 0 && paceMs > 0 && ranFirstFile) await new Promise((resolve) => setTimeout(resolve, paceMs));
1537
+ ranFirstFile = true;
1425
1538
  const isStubbed = stubBlockingFiles?.has(file) === true;
1426
1539
  const isManual = !isStubbed && manualFiles?.has(file) === true;
1427
1540
  if (isManual) {
@@ -1835,7 +1948,21 @@ USAGE
1835
1948
 
1836
1949
  OPTIONS
1837
1950
  --scheme-url <url> intoss-private:// URL from \`ait deploy --scheme-only\`
1838
- (required for standalone relay attach / env3)
1951
+ (required for standalone relay attach / env3). Unused
1952
+ and ignored when --attach-launcher is set.
1953
+ --attach-launcher Attach over the env-2 AITC Sandbox PWA launcher
1954
+ (real-device WebKit) instead of the env-3 intoss
1955
+ scheme deep-link. The relay/QR/dashboard/attach-wait
1956
+ are identical; only the QR is a launcher deep-link.
1957
+ Requires --app-url. The SDK still hits the mock in
1958
+ env 2, so this measures the mock + standard Web API
1959
+ layer's engine-attributable behavior (env1↔env2
1960
+ equivalence), NOT native-bridge fidelity (that is env3).
1961
+ --app-url <url> The consumer dev server's HTTP tunnel URL (e.g. the
1962
+ *.trycloudflare.com URL from \`pnpm dev:phone:cdp\`)
1963
+ that the launcher PWA frames. REQUIRED with
1964
+ --attach-launcher; ignored otherwise. SECRET: a tunnel
1965
+ host — never printed; it rides only inside the QR.
1839
1966
  --timeout <ms> Per-file evaluate timeout in ms (default: 60000).
1840
1967
  Controls how long a single test file is allowed to run
1841
1968
  before it is considered hung. Does NOT affect how long
@@ -1847,7 +1974,10 @@ OPTIONS
1847
1974
  Pass a value to bound the wait for CI/headless runs.
1848
1975
  --cell-sdk-line <line> SDK line to inject as __AIT_CELL__.sdkLine (2.x|3.x)
1849
1976
  --cell-platform <plat> Platform to inject as __AIT_CELL__.platform
1850
- (mock|ios|android, default: AIT_CELL_PLATFORM env)
1977
+ (mock|ios|android|ios-pwa, default: AIT_CELL_PLATFORM
1978
+ env). Use ios-pwa for env-2 (--attach-launcher) runs
1979
+ so env1(mock@desktop) and env2(mock@WebKit) captures
1980
+ stay distinguishable. An unknown value is rejected.
1851
1981
  --report-dir <dir> Persist a runner-agnostic report + captures to <dir>
1852
1982
  (report: <sdkLine>.<platform>.json; captures:
1853
1983
  <dir>/.ait-capture/<category>.<sdkLine>.<platform>.json).
@@ -1862,6 +1992,28 @@ OPTIONS
1862
1992
  --headless Disable browser auto-open (text QR only)
1863
1993
  --project-root <dir> Project root for .ait_relay secret lookup
1864
1994
  (default: current working directory)
1995
+ --pace <ms> Minimum delay in ms between test-to-test AND
1996
+ file-to-file bridge calls (default: 0, i.e. no
1997
+ added delay — today's behavior byte-for-byte).
1998
+ Falls back to the AIT_PACE env var when omitted
1999
+ (--pace takes precedence over the env var when both
2000
+ are given). Use on a 2.x cell scan when the native
2001
+ per-method bridge rate limit (APP_BRIDGE_THROTTLED,
2002
+ devtools#767) is rejecting rapid same-method calls —
2003
+ 3.x cells are unaffected by that limiter and do not
2004
+ need this flag.
2005
+ --pace-method <ms> Minimum delay in ms BETWEEN calls to the SAME named
2006
+ SDK function — paces a same-method burst WITHIN a
2007
+ single test body (e.g. a clipboard happy-path loop
2008
+ calling setClipboardText/getClipboardText 8 times
2009
+ back to back), which --pace's test/file spacing
2010
+ cannot reach (devtools#769). Falls back to the
2011
+ AIT_PACE_METHOD env var when omitted (--pace-method
2012
+ takes precedence over the env var when both are
2013
+ given). Default: 250ms when --cell-sdk-line is 2.x
2014
+ (unset --cell-sdk-line also defaults to 2.x — see
2015
+ --cell-sdk-line), 0 (no added delay) otherwise. Pass
2016
+ --pace-method 0 to opt out even on a 2.x cell.
1865
2017
  --manual-blocking Run manual-tagged test files (*.manual.ait.test.ts)
1866
2018
  LAST, after all regular files, with a human present.
1867
2019
  Before each manual file, the QR dashboard is pushed
@@ -1914,7 +2066,7 @@ DESCRIPTION
1914
2066
  The test files run against the live relay connection started by this process;
1915
2067
  no separate MCP daemon is required.
1916
2068
 
1917
- EXAMPLE
2069
+ EXAMPLE (env 3 — intoss-private scheme)
1918
2070
  devtools-test 'src/**/*.ait.test.ts' \\
1919
2071
  --scheme-url "intoss-private://..." \\
1920
2072
  --cell-sdk-line 3.x \\
@@ -1922,6 +2074,14 @@ EXAMPLE
1922
2074
  --report-dir .ait-report \\
1923
2075
  --timeout 60000
1924
2076
 
2077
+ EXAMPLE (env 2 — AITC Sandbox PWA launcher)
2078
+ devtools-test 'src/**/*.ait.test.ts' \\
2079
+ --attach-launcher \\
2080
+ --app-url "https://<subdomain>.trycloudflare.com" \\
2081
+ --cell-sdk-line 3.x \\
2082
+ --cell-platform ios-pwa \\
2083
+ --report-dir .ait-report
2084
+
1925
2085
  `.trimStart();
1926
2086
  /**
1927
2087
  * Grace period (ms) for the exit backstop armed around Step 6 teardown
@@ -1970,6 +2130,144 @@ function resolveDashboardPort(raw) {
1970
2130
  return port;
1971
2131
  }
1972
2132
  /**
2133
+ * Rewrites a lone `--pace <value>` or `--pace-method <value>` pair from space
2134
+ * syntax to `=` syntax (`--pace=<value>` / `--pace-method=<value>`) when
2135
+ * `<value>` starts with `-` (devtools#768 review; extended to `--pace-method`
2136
+ * in devtools#769).
2137
+ *
2138
+ * Node's `util.parseArgs` treats a `type: 'string'` option's value as
2139
+ * "ambiguous" and throws its OWN parser error whenever that value starts with
2140
+ * a dash and was passed via the space form (`--pace -1`) — it never reaches
2141
+ * this module's `resolvePace`/`resolvePaceMethod`, so the friendly
2142
+ * `'--pace must be a non-negative integer'` message (and its unit-tested path)
2143
+ * was unreachable from the real CLI entry point for the single most natural
2144
+ * way a user would try a negative value, even though every other flag in
2145
+ * {@link USAGE} is documented in space syntax.
2146
+ *
2147
+ * Scoped narrowly to `--pace`/`--pace=...` and `--pace-method`/
2148
+ * `--pace-method=...` tokens only — every other flag is untouched, and
2149
+ * positionals/other options keep flowing through `parseArgs` unchanged. Only
2150
+ * rewrites the space form; `--pace=-1`/`--pace-method=-1` already parse fine
2151
+ * and are passed through untouched.
2152
+ *
2153
+ * Exported for unit testing.
2154
+ */
2155
+ function normalizePaceArgv(argv) {
2156
+ const out = [];
2157
+ for (let i = 0; i < argv.length; i++) {
2158
+ const arg = argv[i];
2159
+ const next = argv[i + 1];
2160
+ if ((arg === "--pace" || arg === "--pace-method") && next !== void 0 && next.startsWith("-") && next !== "--") {
2161
+ out.push(`${arg}=${next}`);
2162
+ i++;
2163
+ continue;
2164
+ }
2165
+ out.push(arg);
2166
+ }
2167
+ return out;
2168
+ }
2169
+ /**
2170
+ * Parses `--pace` (falling back to the `AIT_PACE` env var when the flag is
2171
+ * omitted) into a validated millisecond delay (devtools#767).
2172
+ *
2173
+ * Opt-in, zero-diff-when-absent: BOTH omitted resolves to `0` — no pacing —
2174
+ * which is byte-for-byte today's behavior (`runtime.ts` treats `0`/absent
2175
+ * `__AIT_PACE_MS__` identically, and `relay-worker.ts`'s file-to-file gap is
2176
+ * skipped entirely when the resolved value is 0). `--pace` takes precedence
2177
+ * over `AIT_PACE` when both are present, mirroring `--cell-platform` /
2178
+ * `AIT_CELL_PLATFORM`'s existing flag-over-env precedent.
2179
+ *
2180
+ * Returns an error string on invalid input (non-integer or negative), or the
2181
+ * resolved delay (`>= 0`) on success. Exported for unit testing.
2182
+ */
2183
+ function resolvePace(rawFlag, rawEnv) {
2184
+ const raw = rawFlag ?? rawEnv;
2185
+ if (raw === void 0) return 0;
2186
+ const ms = parseInt(raw, 10);
2187
+ if (Number.isNaN(ms) || ms < 0) return "--pace must be a non-negative integer";
2188
+ return ms;
2189
+ }
2190
+ /**
2191
+ * Parses `--pace-method` (falling back to the `AIT_PACE_METHOD` env var when
2192
+ * the flag is omitted) into a validated millisecond per-method minimum
2193
+ * interval (devtools#769).
2194
+ *
2195
+ * Precedence, highest first: `--pace-method` flag > `AIT_PACE_METHOD` env >
2196
+ * sdkLine-aware default (`{@link DEFAULT_PACE_METHOD_MS_2X}` for a 2.x cell,
2197
+ * `0` otherwise) — an EXPLICIT flag or env value always wins, including
2198
+ * `--pace-method 0` to opt out on a 2.x cell. Mirrors `resolvePace`'s
2199
+ * flag-over-env precedent, with the sdkLine default spliced in only when
2200
+ * BOTH are absent.
2201
+ *
2202
+ * Returns an error string on invalid input (non-integer or negative), or the
2203
+ * resolved delay (`>= 0`) on success. Exported for unit testing.
2204
+ *
2205
+ * @param rawFlag - The raw `--pace-method` string value, if passed.
2206
+ * @param rawEnv - The raw `AIT_PACE_METHOD` env value, if set.
2207
+ * @param resolvedCellSdkLine - The effective `cell.sdkLine` value (already
2208
+ * defaulted to '2.x' by the caller when `--cell-sdk-line` was omitted — see
2209
+ * `main()`'s `cell` construction) — used ONLY to pick the sdkLine-aware
2210
+ * default when both `rawFlag` and `rawEnv` are absent.
2211
+ */
2212
+ function resolvePaceMethod(rawFlag, rawEnv, resolvedCellSdkLine) {
2213
+ const raw = rawFlag ?? rawEnv;
2214
+ if (raw === void 0) return resolvedCellSdkLine === "2.x" ? 250 : 0;
2215
+ const ms = parseInt(raw, 10);
2216
+ if (Number.isNaN(ms) || ms < 0) return "--pace-method must be a non-negative integer";
2217
+ return ms;
2218
+ }
2219
+ /**
2220
+ * The set of `__AIT_CELL__.platform` values the runner accepts (devtools#774).
2221
+ *
2222
+ * `mock`/`ios`/`android` mirror sdk-example `aitCapture`'s existing `Platform`
2223
+ * union; `ios-pwa` is the new env-2 axis value (issue #774) — env 1 is
2224
+ * mock@desktop-Chromium and env 2 is mock@real-device-WebKit, so the two share
2225
+ * `sdkLine`/`ios` but must be DISTINGUISHABLE in the capture cell to diff
2226
+ * engine-attributable differences. `ios-pwa` names "mock SDK, real-device iOS
2227
+ * WebKit via the launcher PWA" — it is NOT `ios` (which is env 3's native
2228
+ * bridge). The consuming `aitCapture` type extension is a separate sdk-example
2229
+ * PR (out of scope here); the runner only needs to inject + validate the label.
2230
+ *
2231
+ * Kept as a validated allowlist (rather than a free string) purely as a
2232
+ * typo guard: `platform` flows into report/capture FILENAMES
2233
+ * (`<sdkLine>.<platform>.json`), so a silent typo would fork the artifact set
2234
+ * and break offline diffing. This is the first validation on `--cell-platform`
2235
+ * — before #774 it was an unvalidated free string defaulting to `mock`.
2236
+ */
2237
+ const ALLOWED_CELL_PLATFORMS = [
2238
+ "mock",
2239
+ "ios",
2240
+ "android",
2241
+ "ios-pwa"
2242
+ ];
2243
+ /**
2244
+ * Validates a resolved `--cell-platform` value against {@link ALLOWED_CELL_PLATFORMS}.
2245
+ *
2246
+ * `undefined` (flag + `AIT_CELL_PLATFORM` env both absent) passes through as
2247
+ * `{ ok: true, value: undefined }` — the caller applies its own `'mock'`
2248
+ * default, so an omitted platform is never an error. A present-but-unknown
2249
+ * value returns `{ ok: false, error }` naming the allowed set.
2250
+ *
2251
+ * Uses a tagged result (not the `string`-is-error convention of
2252
+ * `resolveDashboardPort`/`resolvePace`) because BOTH the success value and the
2253
+ * error message are strings here — a bare `string` return would be ambiguous.
2254
+ * Exported for unit testing.
2255
+ */
2256
+ function resolveCellPlatform(raw) {
2257
+ if (raw === void 0) return {
2258
+ ok: true,
2259
+ value: void 0
2260
+ };
2261
+ if (ALLOWED_CELL_PLATFORMS.includes(raw)) return {
2262
+ ok: true,
2263
+ value: raw
2264
+ };
2265
+ return {
2266
+ ok: false,
2267
+ error: `--cell-platform must be one of ${ALLOWED_CELL_PLATFORMS.join("|")} (got "${raw}")`
2268
+ };
2269
+ }
2270
+ /**
1973
2271
  * Renders per-file result lines and the aggregate totals line to a string.
1974
2272
  *
1975
2273
  * Each file gets one line:
@@ -2038,8 +2336,8 @@ function shouldSuppressQr(noQrFlag) {
2038
2336
  *
2039
2337
  * 1. Parse args: globs, --timeout (per-file evaluate), --attach-timeout (QR
2040
2338
  * scan wait), --cell-sdk-line, --cell-platform, --scheme-url (required for
2041
- * env3), --report-dir, --dashboard-port, --no-qr-stdout, --headless,
2042
- * --project-root.
2339
+ * env3) OR --attach-launcher + --app-url (env2, devtools#774), --report-dir,
2340
+ * --dashboard-port, --no-qr-stdout, --headless, --project-root.
2043
2341
  * 2. Discover test files; exit 1 if none.
2044
2342
  * 3. factory.open() — boot relay → render QR (suppressed on non-interactive
2045
2343
  * stdout) → wait for phone (up to attachTimeoutMs) → inject cell →
@@ -2060,7 +2358,7 @@ async function main(argv = process.argv.slice(2)) {
2060
2358
  let parsed;
2061
2359
  try {
2062
2360
  parsed = parseArgs({
2063
- args: argv,
2361
+ args: normalizePaceArgv(argv),
2064
2362
  options: {
2065
2363
  help: {
2066
2364
  type: "boolean",
@@ -2069,6 +2367,8 @@ async function main(argv = process.argv.slice(2)) {
2069
2367
  timeout: { type: "string" },
2070
2368
  "attach-timeout": { type: "string" },
2071
2369
  "scheme-url": { type: "string" },
2370
+ "attach-launcher": { type: "boolean" },
2371
+ "app-url": { type: "string" },
2072
2372
  "cell-sdk-line": { type: "string" },
2073
2373
  "cell-platform": { type: "string" },
2074
2374
  "report-dir": { type: "string" },
@@ -2076,6 +2376,8 @@ async function main(argv = process.argv.slice(2)) {
2076
2376
  "no-qr-stdout": { type: "boolean" },
2077
2377
  headless: { type: "boolean" },
2078
2378
  "project-root": { type: "string" },
2379
+ pace: { type: "string" },
2380
+ "pace-method": { type: "string" },
2079
2381
  "manual-blocking": { type: "boolean" },
2080
2382
  "stub-blocking": { type: "boolean" }
2081
2383
  },
@@ -2098,9 +2400,17 @@ async function main(argv = process.argv.slice(2)) {
2098
2400
  return;
2099
2401
  }
2100
2402
  const { evaluateTimeoutMs, attachTimeoutMs } = timeouts;
2403
+ const attachLauncher = vals["attach-launcher"] === true;
2101
2404
  const schemeUrl = typeof vals["scheme-url"] === "string" ? vals["scheme-url"] : "";
2102
- if (schemeUrl === "") {
2103
- process.stderr.write("devtools-test: --scheme-url is required for standalone relay attach.\n Pass the intoss-private:// URL from `ait deploy --scheme-only`.\n");
2405
+ const appUrl = typeof vals["app-url"] === "string" ? vals["app-url"] : "";
2406
+ if (attachLauncher) {
2407
+ if (appUrl === "") {
2408
+ process.stderr.write("devtools-test: --app-url is required with --attach-launcher (env 2).\n Pass the consumer dev server's tunnel URL (e.g. the HTTP *.trycloudflare.com\n URL printed by `pnpm dev:phone:cdp`). --scheme-url is not used in this mode.\n");
2409
+ process.exitCode = 1;
2410
+ return;
2411
+ }
2412
+ } else if (schemeUrl === "") {
2413
+ process.stderr.write("devtools-test: --scheme-url is required for standalone relay attach.\n Pass the intoss-private:// URL from `ait deploy --scheme-only`.\n (For env 2 / AITC Sandbox PWA, use --attach-launcher --app-url <tunnel-url> instead.)\n");
2104
2414
  process.exitCode = 1;
2105
2415
  return;
2106
2416
  }
@@ -2113,16 +2423,34 @@ async function main(argv = process.argv.slice(2)) {
2113
2423
  process.exitCode = 1;
2114
2424
  return;
2115
2425
  }
2426
+ const paceMs = resolvePace(typeof vals.pace === "string" ? vals.pace : void 0, process.env.AIT_PACE);
2427
+ if (typeof paceMs === "string") {
2428
+ process.stderr.write(`devtools-test: ${paceMs}\n`);
2429
+ process.exitCode = 1;
2430
+ return;
2431
+ }
2116
2432
  const suppressQr = shouldSuppressQr(vals["no-qr-stdout"] === true);
2117
2433
  const stubBlocking = vals["stub-blocking"] === true;
2118
2434
  const manualBlocking = vals["manual-blocking"] === true || stubBlocking;
2119
2435
  const cellSdkLine = typeof vals["cell-sdk-line"] === "string" ? vals["cell-sdk-line"] : void 0;
2120
- const cellPlatform = typeof vals["cell-platform"] === "string" ? vals["cell-platform"] : process.env.AIT_CELL_PLATFORM;
2436
+ const cellPlatformResult = resolveCellPlatform(typeof vals["cell-platform"] === "string" ? vals["cell-platform"] : process.env.AIT_CELL_PLATFORM);
2437
+ if (!cellPlatformResult.ok) {
2438
+ process.stderr.write(`devtools-test: ${cellPlatformResult.error}\n`);
2439
+ process.exitCode = 1;
2440
+ return;
2441
+ }
2442
+ const cellPlatform = cellPlatformResult.value;
2121
2443
  const hasCell = cellSdkLine !== void 0 || cellPlatform !== void 0;
2122
2444
  const cell = {
2123
2445
  sdkLine: cellSdkLine ?? "2.x",
2124
2446
  platform: cellPlatform ?? "mock"
2125
2447
  };
2448
+ const paceMethodMs = resolvePaceMethod(typeof vals["pace-method"] === "string" ? vals["pace-method"] : void 0, process.env.AIT_PACE_METHOD, cell.sdkLine);
2449
+ if (typeof paceMethodMs === "string") {
2450
+ process.stderr.write(`devtools-test: ${paceMethodMs}\n`);
2451
+ process.exitCode = 1;
2452
+ return;
2453
+ }
2126
2454
  const globs = parsed.positionals;
2127
2455
  if (globs.length === 0) {
2128
2456
  process.stderr.write(`devtools-test: at least one glob pattern is required\n`);
@@ -2142,14 +2470,22 @@ async function main(argv = process.argv.slice(2)) {
2142
2470
  process.stderr.write(manualBlocking && manual.length > 0 ? `devtools-test: found ${regular.length} regular + ${manual.length} manual (${MANUAL_TEST_SUFFIX}) test file(s)\n` : `devtools-test: found ${files.length} test file(s)\n`);
2143
2471
  if (hasCell) process.stderr.write(`devtools-test: injecting __AIT_CELL__ = ${JSON.stringify(cell)}\n`);
2144
2472
  if (stubBlocking) process.stderr.write("devtools-test: --stub-blocking enabled — blocking-UI calls in manual-tagged files will be answered from fixtures (devtools#740), not forwarded to native UI\n");
2473
+ if (paceMs > 0) process.stderr.write(`devtools-test: --pace ${paceMs}ms enabled (devtools#767)\n`);
2474
+ if (paceMethodMs > 0) process.stderr.write(`devtools-test: --pace-method ${paceMethodMs}ms enabled (devtools#769)\n`);
2145
2475
  const factory = createRelayConnectionFactory({
2146
2476
  schemeUrl,
2477
+ ...attachLauncher ? {
2478
+ attachLauncher: true,
2479
+ appUrl
2480
+ } : {},
2147
2481
  projectRoot,
2148
2482
  ...attachTimeoutMs !== void 0 ? { timeoutMs: attachTimeoutMs } : {},
2149
2483
  ...dashboardPort !== void 0 ? { dashboardPort } : {},
2150
2484
  headless,
2151
2485
  cell: hasCell ? cell : void 0,
2152
2486
  stubBlocking,
2487
+ paceMs,
2488
+ paceMethodMs,
2153
2489
  onQrContent: (chunks) => {
2154
2490
  if (suppressQr) {
2155
2491
  process.stdout.write("QR suppressed (non-interactive)\n");
@@ -2173,6 +2509,8 @@ async function main(argv = process.argv.slice(2)) {
2173
2509
  timeoutMs: evaluateTimeoutMs,
2174
2510
  printSummary: true,
2175
2511
  collectCaptures: reportDir !== void 0,
2512
+ paceMs,
2513
+ preflightSdkLine: cell.sdkLine,
2176
2514
  manualFiles: manualBlocking && manualFileSet.size > 0 ? manualFileSet : void 0,
2177
2515
  stubBlockingFiles: stubBlocking && manualFileSet.size > 0 ? manualFileSet : void 0,
2178
2516
  onManualFile: (file, index, total) => {