@copado/agentia-cli 0.111.0-alpha.0 → 0.113.0-alpha.0

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 (282) hide show
  1. package/README.md +1965 -410
  2. package/dist/commands/testing/build/abort.d.ts +7 -1
  3. package/dist/commands/testing/build/abort.js +13 -3
  4. package/dist/commands/testing/build/abort.js.map +1 -1
  5. package/dist/commands/testing/build/get.d.ts +18 -0
  6. package/dist/commands/testing/build/get.js +38 -0
  7. package/dist/commands/testing/build/get.js.map +1 -0
  8. package/dist/commands/testing/build/latest.d.ts +12 -0
  9. package/dist/commands/testing/build/latest.js +40 -0
  10. package/dist/commands/testing/build/latest.js.map +1 -0
  11. package/dist/commands/testing/build/list.d.ts +6 -1
  12. package/dist/commands/testing/build/list.js +23 -1
  13. package/dist/commands/testing/build/list.js.map +1 -1
  14. package/dist/commands/testing/build/logs.d.ts +6 -0
  15. package/dist/commands/testing/build/logs.js +8 -1
  16. package/dist/commands/testing/build/logs.js.map +1 -1
  17. package/dist/commands/testing/build/run.d.ts +27 -4
  18. package/dist/commands/testing/build/run.js +4 -18
  19. package/dist/commands/testing/build/run.js.map +1 -1
  20. package/dist/commands/testing/build/search.d.ts +21 -0
  21. package/dist/commands/testing/build/search.js +57 -0
  22. package/dist/commands/testing/build/search.js.map +1 -0
  23. package/dist/commands/testing/create.d.ts +1 -15
  24. package/dist/commands/testing/create.js +1 -43
  25. package/dist/commands/testing/create.js.map +1 -1
  26. package/dist/commands/testing/data-table/create.d.ts +30 -0
  27. package/dist/commands/testing/data-table/create.js +52 -0
  28. package/dist/commands/testing/data-table/create.js.map +1 -0
  29. package/dist/commands/testing/data-table/delete.d.ts +25 -0
  30. package/dist/commands/testing/data-table/delete.js +40 -0
  31. package/dist/commands/testing/data-table/delete.js.map +1 -0
  32. package/dist/commands/testing/data-table/export.d.ts +33 -0
  33. package/dist/commands/testing/data-table/export.js +59 -0
  34. package/dist/commands/testing/data-table/export.js.map +1 -0
  35. package/dist/commands/testing/data-table/get.d.ts +21 -0
  36. package/dist/commands/testing/data-table/get.js +35 -0
  37. package/dist/commands/testing/data-table/get.js.map +1 -0
  38. package/dist/commands/testing/data-table/list.d.ts +16 -0
  39. package/dist/commands/testing/data-table/list.js +33 -0
  40. package/dist/commands/testing/data-table/list.js.map +1 -0
  41. package/dist/commands/testing/data-table/update.d.ts +34 -0
  42. package/dist/commands/testing/data-table/update.js +64 -0
  43. package/dist/commands/testing/data-table/update.js.map +1 -0
  44. package/dist/commands/testing/job/create.d.ts +18 -0
  45. package/dist/commands/testing/job/create.js +60 -4
  46. package/dist/commands/testing/job/create.js.map +1 -1
  47. package/dist/commands/testing/job/delete.d.ts +6 -1
  48. package/dist/commands/testing/job/delete.js +13 -3
  49. package/dist/commands/testing/job/delete.js.map +1 -1
  50. package/dist/commands/testing/job/refs.d.ts +17 -0
  51. package/dist/commands/testing/job/refs.js +30 -0
  52. package/dist/commands/testing/job/refs.js.map +1 -0
  53. package/dist/commands/testing/job/schedule/get.d.ts +23 -0
  54. package/dist/commands/testing/job/schedule/get.js +31 -0
  55. package/dist/commands/testing/job/schedule/get.js.map +1 -0
  56. package/dist/commands/testing/job/schedule/remove.d.ts +24 -0
  57. package/dist/commands/testing/job/schedule/remove.js +34 -0
  58. package/dist/commands/testing/job/schedule/remove.js.map +1 -0
  59. package/dist/commands/testing/job/schedule/set.d.ts +23 -0
  60. package/dist/commands/testing/job/schedule/set.js +39 -0
  61. package/dist/commands/testing/job/schedule/set.js.map +1 -0
  62. package/dist/commands/testing/job/update.d.ts +2 -0
  63. package/dist/commands/testing/job/update.js +7 -1
  64. package/dist/commands/testing/job/update.js.map +1 -1
  65. package/dist/commands/testing/job/upload.d.ts +6 -0
  66. package/dist/commands/testing/job/upload.js +22 -0
  67. package/dist/commands/testing/job/upload.js.map +1 -1
  68. package/dist/commands/testing/live/continue.d.ts +20 -0
  69. package/dist/commands/testing/live/continue.js +21 -0
  70. package/dist/commands/testing/live/continue.js.map +1 -0
  71. package/dist/commands/testing/live/execute.d.ts +30 -0
  72. package/dist/commands/testing/live/execute.js +43 -0
  73. package/dist/commands/testing/live/execute.js.map +1 -0
  74. package/dist/commands/testing/live/history.d.ts +23 -0
  75. package/dist/commands/testing/live/history.js +35 -0
  76. package/dist/commands/testing/live/history.js.map +1 -0
  77. package/dist/commands/testing/live/inspect.d.ts +18 -0
  78. package/dist/commands/testing/live/inspect.js +30 -0
  79. package/dist/commands/testing/live/inspect.js.map +1 -0
  80. package/dist/commands/testing/live/interrupt.d.ts +20 -0
  81. package/dist/commands/testing/live/interrupt.js +21 -0
  82. package/dist/commands/testing/live/interrupt.js.map +1 -0
  83. package/dist/commands/testing/live/recorder.d.ts +23 -0
  84. package/dist/commands/testing/live/recorder.js +134 -0
  85. package/dist/commands/testing/live/recorder.js.map +1 -0
  86. package/dist/commands/testing/live/reports.d.ts +20 -0
  87. package/dist/commands/testing/live/reports.js +31 -0
  88. package/dist/commands/testing/live/reports.js.map +1 -0
  89. package/dist/commands/testing/live/screenshot.d.ts +23 -0
  90. package/dist/commands/testing/live/screenshot.js +25 -0
  91. package/dist/commands/testing/live/screenshot.js.map +1 -0
  92. package/dist/commands/testing/live/set-options.d.ts +27 -0
  93. package/dist/commands/testing/live/set-options.js +28 -0
  94. package/dist/commands/testing/live/set-options.js.map +1 -0
  95. package/dist/commands/testing/live/start.d.ts +29 -0
  96. package/dist/commands/testing/live/start.js +53 -0
  97. package/dist/commands/testing/live/start.js.map +1 -0
  98. package/dist/commands/testing/live/status.d.ts +19 -0
  99. package/dist/commands/testing/live/status.js +27 -0
  100. package/dist/commands/testing/live/status.js.map +1 -0
  101. package/dist/commands/testing/live/stop.d.ts +24 -0
  102. package/dist/commands/testing/live/stop.js +38 -0
  103. package/dist/commands/testing/live/stop.js.map +1 -0
  104. package/dist/commands/testing/live/view.d.ts +16 -0
  105. package/dist/commands/testing/live/view.js +36 -0
  106. package/dist/commands/testing/live/view.js.map +1 -0
  107. package/dist/commands/testing/logs.d.ts +6 -0
  108. package/dist/commands/testing/logs.js +8 -1
  109. package/dist/commands/testing/logs.js.map +1 -1
  110. package/dist/commands/testing/project/create.d.ts +3 -0
  111. package/dist/commands/testing/project/create.js +17 -1
  112. package/dist/commands/testing/project/create.js.map +1 -1
  113. package/dist/commands/testing/project/delete.d.ts +7 -2
  114. package/dist/commands/testing/project/delete.js +15 -3
  115. package/dist/commands/testing/project/delete.js.map +1 -1
  116. package/dist/commands/testing/project/get.js +1 -1
  117. package/dist/commands/testing/project/get.js.map +1 -1
  118. package/dist/commands/testing/project/permission/get.d.ts +13 -0
  119. package/dist/commands/testing/project/permission/get.js +21 -0
  120. package/dist/commands/testing/project/permission/get.js.map +1 -0
  121. package/dist/commands/testing/project/permission/list.d.ts +13 -0
  122. package/dist/commands/testing/project/permission/list.js +21 -0
  123. package/dist/commands/testing/project/permission/list.js.map +1 -0
  124. package/dist/commands/testing/project/permission/set.d.ts +19 -0
  125. package/dist/commands/testing/project/permission/set.js +33 -0
  126. package/dist/commands/testing/project/permission/set.js.map +1 -0
  127. package/dist/commands/testing/project/permission/update.d.ts +19 -0
  128. package/dist/commands/testing/project/permission/update.js +33 -0
  129. package/dist/commands/testing/project/permission/update.js.map +1 -0
  130. package/dist/commands/testing/project/update.d.ts +14 -0
  131. package/dist/commands/testing/project/update.js +28 -0
  132. package/dist/commands/testing/project/update.js.map +1 -0
  133. package/dist/commands/testing/robot/create.d.ts +4 -1
  134. package/dist/commands/testing/robot/create.js +20 -5
  135. package/dist/commands/testing/robot/create.js.map +1 -1
  136. package/dist/commands/testing/robot/delete.d.ts +6 -1
  137. package/dist/commands/testing/robot/delete.js +13 -3
  138. package/dist/commands/testing/robot/delete.js.map +1 -1
  139. package/dist/commands/testing/robot/update.d.ts +19 -0
  140. package/dist/commands/testing/robot/update.js +49 -0
  141. package/dist/commands/testing/robot/update.js.map +1 -0
  142. package/dist/commands/testing/run.d.ts +27 -4
  143. package/dist/commands/testing/run.js +4 -11
  144. package/dist/commands/testing/run.js.map +1 -1
  145. package/dist/commands/testing/test/run.d.ts +27 -11
  146. package/dist/commands/testing/test/run.js +5 -37
  147. package/dist/commands/testing/test/run.js.map +1 -1
  148. package/dist/commands/testing/update.d.ts +6 -0
  149. package/dist/commands/testing/update.js +10 -0
  150. package/dist/commands/testing/update.js.map +1 -1
  151. package/dist/commands/testing/user-parameter/create.d.ts +19 -0
  152. package/dist/commands/testing/user-parameter/create.js +45 -0
  153. package/dist/commands/testing/user-parameter/create.js.map +1 -0
  154. package/dist/commands/testing/user-parameter/delete.d.ts +21 -0
  155. package/dist/commands/testing/user-parameter/delete.js +33 -0
  156. package/dist/commands/testing/user-parameter/delete.js.map +1 -0
  157. package/dist/commands/testing/user-parameter/get.d.ts +13 -0
  158. package/dist/commands/testing/user-parameter/get.js +21 -0
  159. package/dist/commands/testing/user-parameter/get.js.map +1 -0
  160. package/dist/commands/testing/user-parameter/list.d.ts +12 -0
  161. package/dist/commands/testing/user-parameter/list.js +23 -0
  162. package/dist/commands/testing/user-parameter/list.js.map +1 -0
  163. package/dist/commands/testing/user-parameter/update.d.ts +23 -0
  164. package/dist/commands/testing/user-parameter/update.js +45 -0
  165. package/dist/commands/testing/user-parameter/update.js.map +1 -0
  166. package/dist/commands/testing/variable/create.d.ts +6 -2
  167. package/dist/commands/testing/variable/create.js +14 -5
  168. package/dist/commands/testing/variable/create.js.map +1 -1
  169. package/dist/commands/testing/variable/delete.d.ts +6 -1
  170. package/dist/commands/testing/variable/delete.js +13 -3
  171. package/dist/commands/testing/variable/delete.js.map +1 -1
  172. package/dist/commands/testing/variable/get.d.ts +13 -0
  173. package/dist/commands/testing/variable/get.js +25 -0
  174. package/dist/commands/testing/variable/get.js.map +1 -0
  175. package/dist/commands/testing/variable/list.d.ts +4 -0
  176. package/dist/commands/testing/variable/list.js +15 -3
  177. package/dist/commands/testing/variable/list.js.map +1 -1
  178. package/dist/commands/testing/variable/update.d.ts +6 -2
  179. package/dist/commands/testing/variable/update.js +11 -5
  180. package/dist/commands/testing/variable/update.js.map +1 -1
  181. package/dist/lib/ai-gateway.js +2 -0
  182. package/dist/lib/ai-gateway.js.map +1 -1
  183. package/dist/lib/cicd-gateway.d.ts +4 -2
  184. package/dist/lib/cicd-gateway.js +14 -0
  185. package/dist/lib/cicd-gateway.js.map +1 -1
  186. package/dist/lib/credential-store.d.ts +3 -0
  187. package/dist/lib/credential-store.js +16 -0
  188. package/dist/lib/credential-store.js.map +1 -1
  189. package/dist/lib/mcp/command-id.js +3 -0
  190. package/dist/lib/mcp/command-id.js.map +1 -1
  191. package/dist/lib/mcp/testing-live-tools.d.ts +126 -0
  192. package/dist/lib/mcp/testing-live-tools.js +215 -0
  193. package/dist/lib/mcp/testing-live-tools.js.map +1 -0
  194. package/dist/lib/mcp/testing-tools.d.ts +455 -9
  195. package/dist/lib/mcp/testing-tools.js +677 -37
  196. package/dist/lib/mcp/testing-tools.js.map +1 -1
  197. package/dist/lib/mcp/tools.d.ts +551 -14
  198. package/dist/lib/testing-config.d.ts +3 -0
  199. package/dist/lib/testing-config.js +9 -0
  200. package/dist/lib/testing-config.js.map +1 -1
  201. package/dist/lib/testing-confirmation.d.ts +14 -0
  202. package/dist/lib/testing-confirmation.js +17 -0
  203. package/dist/lib/testing-confirmation.js.map +1 -0
  204. package/dist/lib/testing-data-command.d.ts +21 -0
  205. package/dist/lib/testing-data-command.js +49 -0
  206. package/dist/lib/testing-data-command.js.map +1 -0
  207. package/dist/lib/testing-data-core.d.ts +22 -0
  208. package/dist/lib/testing-data-core.js +208 -0
  209. package/dist/lib/testing-data-core.js.map +1 -0
  210. package/dist/lib/testing-data-export.d.ts +1 -0
  211. package/dist/lib/testing-data-export.js +2 -0
  212. package/dist/lib/testing-data-export.js.map +1 -0
  213. package/dist/lib/testing-data-import.d.ts +1 -0
  214. package/dist/lib/testing-data-import.js +2 -0
  215. package/dist/lib/testing-data-import.js.map +1 -0
  216. package/dist/lib/testing-data-io.d.ts +21 -0
  217. package/dist/lib/testing-data-io.js +236 -0
  218. package/dist/lib/testing-data-io.js.map +1 -0
  219. package/dist/lib/testing-data-operations.d.ts +36 -0
  220. package/dist/lib/testing-data-operations.js +91 -0
  221. package/dist/lib/testing-data-operations.js.map +1 -0
  222. package/dist/lib/testing-data-types.d.ts +41 -0
  223. package/dist/lib/testing-data-types.js +3 -0
  224. package/dist/lib/testing-data-types.js.map +1 -0
  225. package/dist/lib/testing-flags.d.ts +62 -1
  226. package/dist/lib/testing-flags.js +177 -1
  227. package/dist/lib/testing-flags.js.map +1 -1
  228. package/dist/lib/testing-gateway.d.ts +17 -3
  229. package/dist/lib/testing-gateway.js +176 -45
  230. package/dist/lib/testing-gateway.js.map +1 -1
  231. package/dist/lib/testing-live-command.d.ts +24 -0
  232. package/dist/lib/testing-live-command.js +56 -0
  233. package/dist/lib/testing-live-command.js.map +1 -0
  234. package/dist/lib/testing-live-endpoints.d.ts +14 -0
  235. package/dist/lib/testing-live-endpoints.js +34 -0
  236. package/dist/lib/testing-live-endpoints.js.map +1 -0
  237. package/dist/lib/testing-live-history.d.ts +11 -0
  238. package/dist/lib/testing-live-history.js +96 -0
  239. package/dist/lib/testing-live-history.js.map +1 -0
  240. package/dist/lib/testing-live-operations.d.ts +118 -0
  241. package/dist/lib/testing-live-operations.js +676 -0
  242. package/dist/lib/testing-live-operations.js.map +1 -0
  243. package/dist/lib/testing-live-recorder.d.ts +14 -0
  244. package/dist/lib/testing-live-recorder.js +122 -0
  245. package/dist/lib/testing-live-recorder.js.map +1 -0
  246. package/dist/lib/testing-live-robot.d.ts +16 -0
  247. package/dist/lib/testing-live-robot.js +158 -0
  248. package/dist/lib/testing-live-robot.js.map +1 -0
  249. package/dist/lib/testing-live-state.d.ts +4 -0
  250. package/dist/lib/testing-live-state.js +90 -0
  251. package/dist/lib/testing-live-state.js.map +1 -0
  252. package/dist/lib/testing-live-types.d.ts +92 -0
  253. package/dist/lib/testing-live-types.js +2 -0
  254. package/dist/lib/testing-live-types.js.map +1 -0
  255. package/dist/lib/testing-operations.d.ts +157 -5
  256. package/dist/lib/testing-operations.js +830 -66
  257. package/dist/lib/testing-operations.js.map +1 -1
  258. package/dist/lib/testing-run-command.d.ts +25 -0
  259. package/dist/lib/testing-run-command.js +94 -0
  260. package/dist/lib/testing-run-command.js.map +1 -0
  261. package/dist/lib/testing-sanitization.d.ts +11 -0
  262. package/dist/lib/testing-sanitization.js +135 -0
  263. package/dist/lib/testing-sanitization.js.map +1 -0
  264. package/dist/lib/testing-schedule.d.ts +23 -0
  265. package/dist/lib/testing-schedule.js +48 -0
  266. package/dist/lib/testing-schedule.js.map +1 -0
  267. package/dist/lib/testing-secret-input.d.ts +12 -0
  268. package/dist/lib/testing-secret-input.js +56 -0
  269. package/dist/lib/testing-secret-input.js.map +1 -0
  270. package/dist/lib/testing-session.d.ts +8 -0
  271. package/dist/lib/testing-session.js +82 -0
  272. package/dist/lib/testing-session.js.map +1 -0
  273. package/dist/lib/testing-types.d.ts +143 -13
  274. package/dist/lib/testing-types.js +110 -5
  275. package/dist/lib/testing-types.js.map +1 -1
  276. package/dist/lib/testing-watch.d.ts +4 -0
  277. package/dist/lib/testing-watch.js +45 -0
  278. package/dist/lib/testing-watch.js.map +1 -0
  279. package/oclif.manifest.json +8721 -4127
  280. package/package.json +12 -1
  281. package/templates/skills/agentia-testing/SKILL.md +9 -6
  282. package/templates/skills/agentia-testing/references/tests.md +70 -36
@@ -0,0 +1,676 @@
1
+ /* eslint-disable no-await-in-loop -- CRT readiness polling is deliberately sequential. */
2
+ import { randomUUID } from 'node:crypto';
3
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { basename, dirname, resolve, sep } from 'node:path';
5
+ import { deleteCrtLiveViewerSecret, setCrtLiveViewerSecret, } from './credential-store.js';
6
+ import { normalizeCrtDomain, resolveTestingCredentials } from './testing-config.js';
7
+ import { resolveTestingSessionUrl, TestingGateway, TestingGatewayError, } from './testing-gateway.js';
8
+ import { TESTING_LIVE_API_ENDPOINTS, testingLiveJobSessionEndpoint, testingLiveSessionPath, validateTestingLiveSessionPath, } from './testing-live-endpoints.js';
9
+ import { appendTestingLiveHistory, readTestingLiveHistory } from './testing-live-history.js';
10
+ import { connectTestingLiveWebSocket, feedTestingLiveDomFrame, parseTestingLiveRecorderFrame, TESTING_LIVE_MAX_DOM_BYTES, } from './testing-live-recorder.js';
11
+ import { escapeTestingLiveXpathEquals, parseTestingRobot, summarizeTestingLiveKeywordLines, validateKeywordLines, } from './testing-live-robot.js';
12
+ import { clearTestingLiveState, readTestingLiveState, writeTestingLiveState, } from './testing-live-state.js';
13
+ import { sanitizeTestingOutput, sanitizeTestingUrl, testingProviderErrorMessage, } from './testing-sanitization.js';
14
+ import { exchangeTestingSession, testingSessionAuth } from './testing-session.js';
15
+ export const TESTING_LIVE_MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
16
+ export const TESTING_LIVE_MAX_REPORT_BYTES = 20 * 1024 * 1024;
17
+ export const TESTING_LIVE_SYSTEM_STATE_MAX_WAIT_MS = 45_000;
18
+ const TESTING_LIVE_SYSTEM_STATE_MAX_ATTEMPTS = 7;
19
+ const TESTING_LIVE_SYSTEM_STATE_REQUEST_TIMEOUT_MS = 15_000;
20
+ const TESTING_LIVE_SYSTEM_STATE_RETRY_BASE_MS = 500;
21
+ const TESTING_LIVE_SYSTEM_STATE_RETRY_MAX_MS = 4000;
22
+ const DEFAULT_POLL_INTERVAL_MS = 3000;
23
+ const DEFAULT_START_TIMEOUT_MS = 120_000;
24
+ const defaultGlobalEnvironment = (library, openBrowser) => {
25
+ const rows = [`*** Settings ***`, `Library ${library}`];
26
+ if (openBrowser) {
27
+ rows.push('Suite Setup Open Browser about:blank chrome', 'Suite Teardown Close All Browsers');
28
+ }
29
+ return rows.join('\n');
30
+ };
31
+ const DEFAULT_QWEB_CONFIG = [
32
+ 'SetConfig ClickToFocus True',
33
+ 'SetConfig PartialMatch True',
34
+ 'SetConfig HighlightColor teal',
35
+ ];
36
+ const OPEN_BROWSER_KEYWORD = 'Open Browser about:blank chrome';
37
+ const DOM_KEYWORD = 'GetAttribute //html outerHTML';
38
+ let testingLiveSystemStateDelay = delay;
39
+ export function setTestingLiveSystemStateDelayForTests(delayFunction) {
40
+ testingLiveSystemStateDelay = delayFunction ?? delay;
41
+ }
42
+ // The bounded bootstrap state machine intentionally validates optional legacy fields in one boundary.
43
+ // eslint-disable-next-line complexity
44
+ export async function startTestingLive(options) {
45
+ if (options.file === undefined && (options.section !== undefined || options.sectionIndex !== undefined)) {
46
+ throw new Error('CRT live Robot section selection requires --file.');
47
+ }
48
+ const existing = await readTestingLiveState();
49
+ if (existing !== undefined) {
50
+ throw new Error(`CRT live session ${existing.sessionId} is already active. Stop it before starting another.`);
51
+ }
52
+ const response = await TestingGateway.call({
53
+ body: {
54
+ action: 'start',
55
+ ...(options.description === undefined ? {} : { description: boundedLabel(options.description, 500) }),
56
+ ...(options.japIme === true ? { japIME: true } : {}),
57
+ ...(options.screen === undefined ? {} : { screen: options.screen }),
58
+ ...(options.sessionVersion === undefined ? {} : { version: boundedLabel(options.sessionVersion, 100) }),
59
+ type: 'live-testing',
60
+ useRDP: true,
61
+ },
62
+ endpoint: testingLiveJobSessionEndpoint(options.projectId, options.jobId),
63
+ method: 'PUT',
64
+ service: 'pace',
65
+ }, options);
66
+ const data = unwrapRecord(response);
67
+ const sessionId = positiveNumber(data.id, 'session');
68
+ const sessionPath = normalizeReturnedSessionPath(data.url, sessionId, resolveTestingCredentials(gatewayOptions(options)).domain);
69
+ const now = new Date().toISOString();
70
+ const metadata = {
71
+ createdAt: now,
72
+ jobId: options.jobId,
73
+ projectId: options.projectId,
74
+ sessionId,
75
+ sessionPath,
76
+ status: 'starting',
77
+ updatedAt: now,
78
+ };
79
+ for (const key of ['rdpAccessKey', 'guacamoleToken', 'encryptionKey']) {
80
+ const secret = typeof data[key] === 'string' ? data[key].trim() : '';
81
+ if (secret.length > 0) {
82
+ try {
83
+ setCrtLiveViewerSecret(sessionId, secret);
84
+ }
85
+ catch (error) {
86
+ await teardownCreatedTestingLive(metadata, undefined, options);
87
+ throw error;
88
+ }
89
+ break;
90
+ }
91
+ }
92
+ if (options.noBootstrap === true) {
93
+ try {
94
+ return await writeTestingLiveState({ ...metadata, status: 'ready', updatedAt: new Date().toISOString() });
95
+ }
96
+ catch (error) {
97
+ await teardownCreatedTestingLive(metadata, undefined, options);
98
+ throw error;
99
+ }
100
+ }
101
+ let session;
102
+ try {
103
+ session = await exchangeTestingSession(gatewayOptions(options));
104
+ await pollTestingLiveReady({ metadata, session }, options.timeoutMs, options.pollIntervalMs, options);
105
+ const parsed = options.file === undefined
106
+ ? undefined
107
+ : parseTestingRobot(readBoundedRobotFile(options.file), {
108
+ section: options.section,
109
+ sectionIndex: options.sectionIndex,
110
+ });
111
+ const library = options.library ?? 'QWeb';
112
+ const opensBrowser = library !== 'QMobile' && options.noBrowser !== true;
113
+ await startSessionEnvironment(metadata, session, {
114
+ files: [],
115
+ globalEnvironment: parsed?.globalEnvironment ?? defaultGlobalEnvironment(library, opensBrowser),
116
+ scriptPath: '',
117
+ testCaseName: parsed?.sectionName ?? 'LiveTest',
118
+ }, options);
119
+ if (library === 'QWeb' || library === 'QForce')
120
+ await executeLines(metadata, session, DEFAULT_QWEB_CONFIG, options);
121
+ if (opensBrowser) {
122
+ await executeLines(metadata, session, [OPEN_BROWSER_KEYWORD], options);
123
+ await pollTestingLiveBrowserReady({ metadata, session }, Math.min(options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS, 60_000), options.pollIntervalMs, options);
124
+ }
125
+ if ((parsed?.lines.length ?? 0) > 0)
126
+ await executeLines(metadata, session, parsed.lines, options);
127
+ return await writeTestingLiveState({ ...metadata, status: 'ready', updatedAt: new Date().toISOString() });
128
+ }
129
+ catch (error) {
130
+ await teardownCreatedTestingLive(metadata, session, options);
131
+ throw error;
132
+ }
133
+ }
134
+ export async function stopTestingLive(options) {
135
+ const metadata = await requireTestingLiveState(options);
136
+ try {
137
+ const session = await exchangeTestingSession(gatewayOptions(options));
138
+ await sessionCall(metadata, session, TESTING_LIVE_API_ENDPOINTS.stop, 'GET', undefined, options);
139
+ }
140
+ catch {
141
+ // Pace teardown is authoritative and still runs when exchange or host-side shutdown fails.
142
+ }
143
+ try {
144
+ await TestingGateway.call({
145
+ body: { action: 'stop' },
146
+ endpoint: testingLiveJobSessionEndpoint(metadata.projectId, metadata.jobId),
147
+ method: 'PUT',
148
+ service: 'pace',
149
+ }, gatewayOptions(options));
150
+ }
151
+ finally {
152
+ await clearTestingLive(metadata);
153
+ }
154
+ return {
155
+ jobId: metadata.jobId,
156
+ projectId: metadata.projectId,
157
+ sessionId: metadata.sessionId,
158
+ stopped: true,
159
+ };
160
+ }
161
+ export async function getTestingLiveStatus(options) {
162
+ const connection = await loadTestingLiveConnection(options);
163
+ try {
164
+ const response = await sessionCall(connection.metadata, connection.session, TESTING_LIVE_API_ENDPOINTS.status, 'GET', undefined, options);
165
+ const status = normalizeStatus(response);
166
+ const metadata = await writeTestingLiveState({
167
+ ...connection.metadata,
168
+ status: status.server.inShutdownState ? 'stopping' : 'ready',
169
+ updatedAt: new Date().toISOString(),
170
+ });
171
+ return { metadata, status };
172
+ }
173
+ catch (error) {
174
+ if (error instanceof TestingGatewayError && [404, 410].includes(error.statusCode ?? 0)) {
175
+ await clearTestingLive(connection.metadata);
176
+ }
177
+ throw error;
178
+ }
179
+ }
180
+ export async function executeTestingLive(options) {
181
+ const connection = await loadTestingLiveConnection(options);
182
+ let lines;
183
+ let environment;
184
+ if (options.file !== undefined) {
185
+ const parsed = parseTestingRobot(readBoundedRobotFile(options.file), options);
186
+ lines = parsed.lines;
187
+ environment = parsed.globalEnvironment;
188
+ }
189
+ else if (options.lines !== undefined && options.lines.length > 0) {
190
+ lines = validateKeywordLines(options.lines);
191
+ }
192
+ else if (options.keyword === undefined) {
193
+ throw new Error('Provide --keyword, --line, or --file for live execution.');
194
+ }
195
+ else {
196
+ lines = validateKeywordLines([
197
+ [options.keyword.trim(), ...(options.args ?? [])].filter(Boolean).join(' '),
198
+ ]);
199
+ }
200
+ if (environment !== undefined) {
201
+ await startSessionEnvironment(connection.metadata, connection.session, { files: [], globalEnvironment: environment, scriptPath: '', testCaseName: 'LiveTest' }, options);
202
+ }
203
+ const startedAt = new Date().toISOString();
204
+ try {
205
+ const response = await executeLines(connection.metadata, connection.session, lines, options);
206
+ const record = unwrapRecord(response);
207
+ const result = {
208
+ executionId: typeof record.executionId === 'string' && record.executionId.length > 0
209
+ ? record.executionId
210
+ : randomUUID(),
211
+ jobId: connection.metadata.jobId,
212
+ lines: summarizeTestingLiveKeywordLines(lines),
213
+ projectId: connection.metadata.projectId,
214
+ sessionId: connection.metadata.sessionId,
215
+ status: record.status === 200 ? 'completed' : 'failed',
216
+ };
217
+ appendTestingLiveHistory({
218
+ completedAt: new Date().toISOString(),
219
+ id: result.executionId,
220
+ jobId: result.jobId,
221
+ lines: summarizeTestingLiveKeywordLines(lines),
222
+ projectId: result.projectId,
223
+ startedAt,
224
+ status: result.status,
225
+ });
226
+ return result;
227
+ }
228
+ catch (error) {
229
+ appendTestingLiveHistory({
230
+ completedAt: new Date().toISOString(),
231
+ id: randomUUID(),
232
+ jobId: connection.metadata.jobId,
233
+ lines: summarizeTestingLiveKeywordLines(lines),
234
+ projectId: connection.metadata.projectId,
235
+ startedAt,
236
+ status: 'failed',
237
+ });
238
+ throw error;
239
+ }
240
+ }
241
+ export async function controlTestingLive(action, options) {
242
+ const connection = await loadTestingLiveConnection(options);
243
+ const endpoint = action === 'continue' ? TESTING_LIVE_API_ENDPOINTS.continue : TESTING_LIVE_API_ENDPOINTS.interrupt;
244
+ const response = unwrapRecord(await sessionCall(connection.metadata, connection.session, endpoint, 'POST', undefined, options));
245
+ return {
246
+ action,
247
+ message: typeof response.message === 'string' ? sanitizeTestingOutput(response.message).slice(0, 2000) : action,
248
+ sessionId: connection.metadata.sessionId,
249
+ };
250
+ }
251
+ export async function inspectTestingLive(options) {
252
+ const connection = await loadTestingLiveConnection(options);
253
+ if (options.dom === true) {
254
+ if (options.output === undefined)
255
+ throw new Error('Raw DOM requires an explicit --output path.');
256
+ const target = validateExplicitFilePath(options.output);
257
+ const dom = await collectTestingLiveDom(connection, options);
258
+ mkdirSync(dirname(target), { recursive: true });
259
+ writeFileSync(target, dom, { encoding: 'utf8', mode: 0o600 });
260
+ return { bytes: Buffer.byteLength(dom), path: target, sessionId: connection.metadata.sessionId };
261
+ }
262
+ const record = await fetchTestingLiveSystemState(connection, options, TESTING_LIVE_MAX_SCREENSHOT_BYTES * 2);
263
+ return {
264
+ changeOrigin: boundedString(record.changeOrigin, 200),
265
+ sessionId: connection.metadata.sessionId,
266
+ systemState: sanitizeSystemState(record.systemState),
267
+ timestamp: boundedString(record.timestamp, 100),
268
+ url: sanitizeUntrustedPageUrl(record.url),
269
+ };
270
+ }
271
+ export async function saveTestingLiveScreenshot(options) {
272
+ const connection = await loadTestingLiveConnection(options);
273
+ const record = await fetchTestingLiveSystemState(connection, options, TESTING_LIVE_MAX_SCREENSHOT_BYTES * 2);
274
+ const screenshot = typeof record.screenshot === 'string' ? record.screenshot : '';
275
+ const encoded = screenshot.includes(',') ? screenshot.slice(screenshot.indexOf(',') + 1) : screenshot;
276
+ if (encoded.length === 0 || !/^[a-z\d+/=\s]+$/iu.test(encoded))
277
+ throw new Error('CRT session did not return a valid screenshot.');
278
+ const bytes = Buffer.from(encoded, 'base64');
279
+ if (bytes.length === 0 || bytes.length > TESTING_LIVE_MAX_SCREENSHOT_BYTES) {
280
+ throw new Error('CRT screenshot exceeded the safety limit.');
281
+ }
282
+ const target = validateExplicitFilePath(options.output);
283
+ mkdirSync(dirname(target), { recursive: true });
284
+ writeFileSync(target, bytes, { mode: 0o600 });
285
+ return {
286
+ bytes: bytes.length,
287
+ path: target,
288
+ sessionId: connection.metadata.sessionId,
289
+ ...(typeof record.timestamp === 'string' ? { timestamp: boundedString(record.timestamp, 100) } : {}),
290
+ ...(typeof record.url === 'string' ? { url: sanitizeUntrustedPageUrl(record.url) } : {}),
291
+ };
292
+ }
293
+ export async function getTestingLiveReports(options) {
294
+ const connection = await loadTestingLiveConnection(options);
295
+ const response = unwrapRecord(await sessionCall(connection.metadata, connection.session, TESTING_LIVE_API_ENDPOINTS.download, 'GET', undefined, { ...options, maxResponseBytes: TESTING_LIVE_MAX_REPORT_BYTES * 3 }));
296
+ const files = [];
297
+ const root = options.outputDir === undefined ? undefined : validateExplicitDirectory(options.outputDir);
298
+ const mappings = [['log', 'log.html'], ['report', 'report.html'], ['output', 'output.xml']];
299
+ for (const [key, name] of mappings) {
300
+ const content = response[key];
301
+ if (typeof content !== 'string')
302
+ continue;
303
+ const bytes = Buffer.byteLength(content);
304
+ if (bytes > TESTING_LIVE_MAX_REPORT_BYTES)
305
+ throw new Error(`CRT report ${name} exceeded the safety limit.`);
306
+ if (root === undefined)
307
+ files.push({ bytes, name });
308
+ else {
309
+ const target = resolve(root, basename(name));
310
+ mkdirSync(root, { recursive: true });
311
+ writeFileSync(target, content, { encoding: 'utf8', mode: 0o600 });
312
+ files.push({ bytes, name, path: target });
313
+ }
314
+ }
315
+ return { files, sessionId: connection.metadata.sessionId };
316
+ }
317
+ export async function setTestingLiveOptions(options) {
318
+ const connection = await loadTestingLiveConnection(options);
319
+ const response = unwrapRecord(await sessionCall(connection.metadata, connection.session, TESTING_LIVE_API_ENDPOINTS.setOptions, 'POST', { recorder: options.recorder }, options));
320
+ return {
321
+ recorder: options.recorder,
322
+ sessionId: connection.metadata.sessionId,
323
+ status: typeof response.status === 'number' ? response.status : 0,
324
+ };
325
+ }
326
+ export function getTestingLiveHistory(options) {
327
+ const executions = readTestingLiveHistory(options);
328
+ return { count: executions.length, executions, jobId: options.jobId, projectId: options.projectId };
329
+ }
330
+ export async function testingLiveViewUrl(options) {
331
+ const metadata = await requireTestingLiveState(options);
332
+ const domain = normalizeCrtDomain(resolveTestingCredentials(gatewayOptions(options)).domain);
333
+ const url = new URL(`/live-testing/session/${metadata.sessionId}`, `https://${domain}`);
334
+ if (options.interactive === true)
335
+ url.searchParams.set('interactive', 'true');
336
+ return url.toString();
337
+ }
338
+ export async function loadTestingLiveConnection(options) {
339
+ return {
340
+ metadata: await requireTestingLiveState(options),
341
+ session: await exchangeTestingSession(gatewayOptions(options)),
342
+ };
343
+ }
344
+ export async function clearTestingLive(metadata) {
345
+ const active = metadata ?? await readTestingLiveState();
346
+ if (active !== undefined)
347
+ deleteCrtLiveViewerSecret(active.sessionId);
348
+ await clearTestingLiveState();
349
+ }
350
+ async function requireTestingLiveState(options) {
351
+ const metadata = await readTestingLiveState();
352
+ if (metadata === undefined)
353
+ throw new Error('No active CRT live session. Run `agentia testing live start` first.');
354
+ if (metadata.projectId !== options.projectId)
355
+ throw new Error('Active CRT live session belongs to another project.');
356
+ if (options.jobId !== undefined && metadata.jobId !== options.jobId) {
357
+ throw new Error('Requested CRT job does not match the active live session.');
358
+ }
359
+ if (options.sessionId !== undefined && metadata.sessionId !== options.sessionId) {
360
+ throw new Error('Requested CRT session does not match the active live session.');
361
+ }
362
+ return metadata;
363
+ }
364
+ // Keeping the explicit request tuple visible makes every live endpoint/auth call auditable.
365
+ // eslint-disable-next-line max-params
366
+ async function sessionCall(metadata, session, endpoint, method, body, options) {
367
+ return TestingGateway.call({
368
+ auth: testingSessionAuth(session),
369
+ ...(body === undefined ? {} : { body }),
370
+ endpoint,
371
+ maxResponseBytes: typeof options.maxResponseBytes === 'number' ? options.maxResponseBytes : undefined,
372
+ method,
373
+ service: 'session',
374
+ sessionPath: metadata.sessionPath,
375
+ }, gatewayOptions(options));
376
+ }
377
+ async function executeLines(metadata, session, lines, options) {
378
+ const processed = validateKeywordLines(lines).map((line) => escapeTestingLiveXpathEquals(line));
379
+ const response = await sessionCall(metadata, session, TESTING_LIVE_API_ENDPOINTS.keyword, 'POST', {
380
+ auxCode: '',
381
+ context: 'normal',
382
+ filepath: '',
383
+ internalKeyword: false,
384
+ lines: processed,
385
+ sequenceId: -1,
386
+ }, options);
387
+ assertTestingLiveResponseStatus(response, 'keyword execution');
388
+ return response;
389
+ }
390
+ async function startSessionEnvironment(metadata, session, body, options) {
391
+ const response = await sessionCall(metadata, session, TESTING_LIVE_API_ENDPOINTS.start, 'POST', body, options);
392
+ assertTestingLiveResponseStatus(response, 'session bootstrap');
393
+ return response;
394
+ }
395
+ async function teardownCreatedTestingLive(metadata, session, options) {
396
+ if (session !== undefined) {
397
+ await sessionCall(metadata, session, TESTING_LIVE_API_ENDPOINTS.stop, 'GET', undefined, options).catch(() => { });
398
+ }
399
+ try {
400
+ await TestingGateway.call({
401
+ body: { action: 'stop' },
402
+ endpoint: testingLiveJobSessionEndpoint(metadata.projectId, metadata.jobId),
403
+ method: 'PUT',
404
+ service: 'pace',
405
+ }, gatewayOptions(options));
406
+ }
407
+ catch {
408
+ // Local cleanup is mandatory even when Pace teardown fails.
409
+ }
410
+ finally {
411
+ await clearTestingLive(metadata);
412
+ }
413
+ }
414
+ function assertTestingLiveResponseStatus(response, action) {
415
+ const record = unwrapRecord(response);
416
+ const { status } = record;
417
+ if (status === 200)
418
+ return;
419
+ if (typeof status !== 'number') {
420
+ throw new TypeError(`CRT live ${action} failed because the response status was missing or invalid.`);
421
+ }
422
+ const detail = testingProviderErrorMessage(JSON.stringify(record), status);
423
+ throw new Error(`CRT live ${action} failed. ${detail}`);
424
+ }
425
+ async function pollTestingLiveReady(connection, timeoutMs = DEFAULT_START_TIMEOUT_MS, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, options) {
426
+ const boundedTimeout = boundedInteger(timeoutMs, 1000, 10 * 60_000, 'timeout');
427
+ const interval = boundedInteger(pollIntervalMs, 250, 30_000, 'poll interval');
428
+ const deadline = Date.now() + boundedTimeout;
429
+ while (Date.now() < deadline) {
430
+ try {
431
+ const status = normalizeStatus(await sessionCall(connection.metadata, connection.session, TESTING_LIVE_API_ENDPOINTS.status, 'GET', undefined, { ...options, timeoutMs: Math.min(interval, 10_000) }));
432
+ if (!status.server.inShutdownState && status.server.ambleApiVersion.length > 0)
433
+ return;
434
+ }
435
+ catch {
436
+ // Host may not accept connections until its live environment is ready.
437
+ }
438
+ await delay(interval);
439
+ }
440
+ throw new Error(`CRT live session did not become ready within ${Math.ceil(boundedTimeout / 1000)} seconds.`);
441
+ }
442
+ async function pollTestingLiveBrowserReady(connection, timeoutMs, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, options) {
443
+ const boundedTimeout = boundedInteger(timeoutMs, 1000, 60_000, 'browser timeout');
444
+ const interval = boundedInteger(pollIntervalMs, 250, 30_000, 'poll interval');
445
+ const deadline = Date.now() + boundedTimeout;
446
+ while (Date.now() < deadline) {
447
+ try {
448
+ const status = normalizeStatus(await sessionCall(connection.metadata, connection.session, TESTING_LIVE_API_ENDPOINTS.status, 'GET', undefined, { ...options, timeoutMs: Math.min(interval, 10_000) }));
449
+ if (status.env.isBrowserStarted || status.env.browsersRunning.length > 0)
450
+ return;
451
+ }
452
+ catch {
453
+ // The live host may briefly reject status while Chrome is starting.
454
+ }
455
+ await delay(interval);
456
+ }
457
+ throw new Error(`CRT live browser did not become ready within ${Math.ceil(boundedTimeout / 1000)} seconds.`);
458
+ }
459
+ async function collectTestingLiveDom(connection, options) {
460
+ const { domain } = resolveTestingCredentials(gatewayOptions(options));
461
+ const socket = connectTestingLiveWebSocket(connection.metadata.sessionPath, domain, connection.session);
462
+ await new Promise((resolvePromise, reject) => {
463
+ socket.once('open', resolvePromise);
464
+ socket.once('error', reject);
465
+ });
466
+ await executeLines(connection.metadata, connection.session, [DOM_KEYWORD], options);
467
+ return new Promise((resolvePromise, reject) => {
468
+ let dom;
469
+ let quiet;
470
+ const budget = setTimeout(() => finish(new Error('CRT session did not return the page DOM.')), 30_000);
471
+ const finish = (error) => {
472
+ clearTimeout(budget);
473
+ if (quiet !== undefined)
474
+ clearTimeout(quiet);
475
+ socket.close();
476
+ if (error !== undefined)
477
+ reject(error);
478
+ else if (dom === undefined)
479
+ reject(new Error('CRT session did not return the page DOM.'));
480
+ else
481
+ resolvePromise(dom);
482
+ };
483
+ socket.on('message', (frame) => {
484
+ try {
485
+ const event = parseTestingLiveRecorderFrame(frame);
486
+ if (event === undefined)
487
+ return;
488
+ dom = feedTestingLiveDomFrame(dom, event);
489
+ if (dom !== undefined) {
490
+ if (quiet !== undefined)
491
+ clearTimeout(quiet);
492
+ quiet = setTimeout(() => finish(), 1500);
493
+ }
494
+ }
495
+ catch (error) {
496
+ finish(error);
497
+ }
498
+ });
499
+ socket.once('error', () => finish(new Error('CRT live DOM stream failed.')));
500
+ });
501
+ }
502
+ function normalizeReturnedSessionPath(value, sessionId, domain) {
503
+ if (typeof value !== 'string' || value.trim().length === 0)
504
+ return testingLiveSessionPath(sessionId);
505
+ const candidate = value.trim();
506
+ if (/^https:\/\//iu.test(candidate)) {
507
+ return validateTestingLiveSessionPath(resolveTestingSessionUrl(candidate, domain).pathname, sessionId);
508
+ }
509
+ return validateTestingLiveSessionPath(candidate, sessionId);
510
+ }
511
+ function normalizeStatus(value) {
512
+ const root = unwrapRecord(value);
513
+ const env = record(root.env);
514
+ const recorder = record(root.recorder);
515
+ const server = record(root.server);
516
+ const session = record(root.session);
517
+ return {
518
+ env: {
519
+ browsersRunning: stringArray(env.browsersRunning, 20),
520
+ initialBrowser: boundedString(env.initialBrowser, 100),
521
+ isBrowserStarted: env.isBrowserStarted === true,
522
+ isWindowOpen: env.isWindowOpen === true,
523
+ },
524
+ recorder: { connected: recorder.connected === true },
525
+ server: {
526
+ ambleApiVersion: boundedString(server.ambleApiVersion, 100),
527
+ clientsConnected: safeNumber(server.clientsConnected),
528
+ inFatalErrorState: server.inFatalErrorState === true,
529
+ inShutdownState: server.inShutdownState === true,
530
+ serverTimeUtc: boundedString(server.serverTimeUtc, 100),
531
+ serverUptime: safeNumber(server.serverUptime),
532
+ },
533
+ session: {
534
+ activeRequests: safeNumber(session.activeRequests),
535
+ currentIdleTime: safeNumber(session.currentIdleTime),
536
+ isKeywordExecutionPending: session.isKeywordExecutionPending === true,
537
+ isRobotExecutorRunning: session.isRobotExecutorRunning === true,
538
+ sessionIdleTimeout: safeNumber(session.sessionIdleTimeout),
539
+ timeToExpire: safeNumber(session.timeToExpire),
540
+ totalElapsedTime: safeNumber(session.totalElapsedTime),
541
+ },
542
+ };
543
+ }
544
+ function sanitizeSystemState(value) {
545
+ if (typeof value !== 'string')
546
+ return sanitizeTestingOutput(value);
547
+ if (Buffer.byteLength(value) > TESTING_LIVE_MAX_DOM_BYTES)
548
+ throw new Error('CRT system state exceeded the safety limit.');
549
+ try {
550
+ return sanitizeTestingOutput(JSON.parse(value));
551
+ }
552
+ catch {
553
+ return { available: true, bytes: Buffer.byteLength(value) };
554
+ }
555
+ }
556
+ async function fetchTestingLiveSystemState(connection, options, maxResponseBytes) {
557
+ const startedAt = Date.now();
558
+ let scheduledDelayMs = 0;
559
+ let lastError;
560
+ for (let attempt = 0; attempt < TESTING_LIVE_SYSTEM_STATE_MAX_ATTEMPTS; attempt++) {
561
+ const remainingMs = TESTING_LIVE_SYSTEM_STATE_MAX_WAIT_MS - Math.max(Date.now() - startedAt, scheduledDelayMs);
562
+ if (remainingMs <= 0)
563
+ break;
564
+ try {
565
+ return unwrapRecord(await sessionCall(connection.metadata, connection.session, TESTING_LIVE_API_ENDPOINTS.systemState, 'GET', undefined, {
566
+ ...options,
567
+ maxResponseBytes,
568
+ timeoutMs: Math.min(TESTING_LIVE_SYSTEM_STATE_REQUEST_TIMEOUT_MS, remainingMs),
569
+ }));
570
+ }
571
+ catch (error) {
572
+ if (!(error instanceof TestingGatewayError) ||
573
+ (error.statusCode !== undefined && ![409, 422].includes(error.statusCode))) {
574
+ throw error;
575
+ }
576
+ lastError = error;
577
+ }
578
+ const retryDelayMs = Math.min(TESTING_LIVE_SYSTEM_STATE_RETRY_BASE_MS * (2 ** attempt), TESTING_LIVE_SYSTEM_STATE_RETRY_MAX_MS);
579
+ scheduledDelayMs += retryDelayMs;
580
+ if (attempt === TESTING_LIVE_SYSTEM_STATE_MAX_ATTEMPTS - 1 ||
581
+ scheduledDelayMs >= TESTING_LIVE_SYSTEM_STATE_MAX_WAIT_MS) {
582
+ break;
583
+ }
584
+ await testingLiveSystemStateDelay(retryDelayMs);
585
+ }
586
+ throw lastError ?? new Error('CRT system state was not available within the bounded retry window.');
587
+ }
588
+ function sanitizeUntrustedPageUrl(value) {
589
+ if (typeof value !== 'string' || value.trim().length === 0)
590
+ return undefined;
591
+ try {
592
+ const url = new URL(value);
593
+ if (!['http:', 'https:'].includes(url.protocol) || url.username.length > 0 || url.password.length > 0)
594
+ return undefined;
595
+ return sanitizeTestingUrl(url);
596
+ }
597
+ catch {
598
+ return undefined;
599
+ }
600
+ }
601
+ function readBoundedRobotFile(path) {
602
+ const contents = readFileSync(path, 'utf8');
603
+ if (Buffer.byteLength(contents) > 1024 * 1024)
604
+ throw new Error('Robot input file exceeds the safety limit.');
605
+ return contents;
606
+ }
607
+ function validateExplicitFilePath(path) {
608
+ if (path.trim().length === 0 || path.includes('\0'))
609
+ throw new Error('CRT output path must not be empty.');
610
+ return resolve(path);
611
+ }
612
+ function validateExplicitDirectory(path) {
613
+ const target = resolve(path);
614
+ if (path.trim().length === 0 || path.includes('\0') || target === sep) {
615
+ throw new Error('CRT output directory is invalid.');
616
+ }
617
+ return target;
618
+ }
619
+ function unwrapRecord(value) {
620
+ const root = record(value);
621
+ return record(root.data ?? root.result ?? root);
622
+ }
623
+ function record(value) {
624
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
625
+ ? value
626
+ : {};
627
+ }
628
+ function positiveNumber(value, label) {
629
+ const parsed = typeof value === 'number' ? value : Number(value);
630
+ if (!Number.isSafeInteger(parsed) || parsed <= 0)
631
+ throw new Error(`CRT ${label} ID is missing or invalid.`);
632
+ return parsed;
633
+ }
634
+ function boundedString(value, max) {
635
+ return typeof value === 'string' ? sanitizeTestingOutput(value).slice(0, max) : '';
636
+ }
637
+ function boundedLabel(value, max) {
638
+ const clean = value.replaceAll('\0', '').trim();
639
+ if (clean.length === 0 || clean.length > max)
640
+ throw new Error(`CRT live value must contain 1-${max} characters.`);
641
+ return clean;
642
+ }
643
+ function boundedInteger(value, minimum, maximum, label) {
644
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
645
+ throw new Error(`CRT live ${label} must be between ${minimum} and ${maximum} milliseconds.`);
646
+ }
647
+ return value;
648
+ }
649
+ function safeNumber(value) {
650
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
651
+ }
652
+ function stringArray(value, limit) {
653
+ return Array.isArray(value)
654
+ ? value.slice(0, limit).filter((item) => typeof item === 'string').map((item) => item.slice(0, 100))
655
+ : [];
656
+ }
657
+ async function delay(milliseconds) {
658
+ await new Promise((resolvePromise) => {
659
+ setTimeout(resolvePromise, milliseconds);
660
+ });
661
+ }
662
+ function gatewayOptions(options) {
663
+ const result = {};
664
+ if (typeof options.domain === 'string')
665
+ result.domain = options.domain;
666
+ if (typeof options.org === 'string' || typeof options.org === 'number')
667
+ result.org = options.org;
668
+ if (typeof options.pak === 'string')
669
+ result.pak = options.pak;
670
+ if (typeof options.pat === 'string')
671
+ result.pat = options.pat;
672
+ if (typeof options.timeoutMs === 'number')
673
+ result.timeoutMs = options.timeoutMs;
674
+ return result;
675
+ }
676
+ //# sourceMappingURL=testing-live-operations.js.map