@vostride/agent-qa-core 0.2.2 → 0.2.4
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.
- package/README.md +78 -0
- package/dist/index.cjs +400 -700
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -423
- package/dist/index.d.ts +20 -423
- package/dist/index.js +362 -655
- package/dist/index.js.map +1 -1
- package/dist/run-text-output.cjs +86 -0
- package/dist/run-text-output.cjs.map +1 -0
- package/dist/run-text-output.d.cts +13 -0
- package/dist/run-text-output.d.ts +13 -0
- package/dist/run-text-output.js +55 -0
- package/dist/run-text-output.js.map +1 -0
- package/package.json +10 -6
package/dist/index.js
CHANGED
|
@@ -122,7 +122,9 @@ var NamedLLMConfigSchema = ModelConfigBaseSchema.extend({
|
|
|
122
122
|
var TimeoutConfigSchema = z2.object({
|
|
123
123
|
step: DurationString,
|
|
124
124
|
test: DurationString,
|
|
125
|
-
navigation: DurationString
|
|
125
|
+
navigation: DurationString.describe(
|
|
126
|
+
"How long a web page load or URL wait can take. Default 2m. Android and iOS runs do not use it."
|
|
127
|
+
)
|
|
126
128
|
}).strict();
|
|
127
129
|
var BrowserConfigSchema = z2.object({
|
|
128
130
|
name: z2.enum(["chromium", "firefox", "webkit"]),
|
|
@@ -132,7 +134,6 @@ var BrowserConfigSchema = z2.object({
|
|
|
132
134
|
height: z2.number()
|
|
133
135
|
}).strict().optional()
|
|
134
136
|
}).strict();
|
|
135
|
-
var TransportSchema = z2.enum(["local", "browserstack"]);
|
|
136
137
|
var PlannerConfigSchema = z2.object({
|
|
137
138
|
maxActions: z2.number().int().positive().optional(),
|
|
138
139
|
maxPlanningTurns: z2.number().int().positive().optional(),
|
|
@@ -156,69 +157,28 @@ var DashboardConfigSchema = z3.object({
|
|
|
156
157
|
port: z3.number().optional(),
|
|
157
158
|
dbPath: z3.string().optional(),
|
|
158
159
|
artifactsDir: z3.string().optional(),
|
|
159
|
-
//
|
|
160
|
-
// is a card with no `url` at all rather than a link to a route nothing serves. The card
|
|
161
|
-
// producer reads these through the masked config reader and renders
|
|
162
|
-
// `ENTITY_URL_ROUTE` (@vostride/agent-qa-schema) against them.
|
|
163
|
-
//
|
|
164
|
-
// They live on the EXISTING `services.dashboard` block rather than on a new top-level
|
|
165
|
-
// `dashboard` key: this project already has one dashboard configuration home, and a second
|
|
166
|
-
// one would be two places an operator could reasonably write the same fact.
|
|
167
|
-
//
|
|
168
|
-
// 2026-09-01 (Phase 408 plan 10, WR-04 — T-408-16): REFINED TO AN ABSOLUTE http(s) URL, because
|
|
169
|
-
// this value stops being a display string in M5. It composes the tokenless live link
|
|
170
|
-
// (`live-link.ts`'s `liveRunLinkOf`, which only strips trailing slashes), and that link is
|
|
171
|
-
// assigned to an `href` INSIDE the served App document — which runs with `allow-scripts` at an
|
|
172
|
-
// opaque origin — as well as appended verbatim to every carrier tool result's model-readable
|
|
173
|
-
// text. A `javascript:` base therefore composed a script-executing anchor. The trust boundary is
|
|
174
|
-
// the operator, so this is not remote-triggerable; the finding is that a URL-shaped value reached
|
|
175
|
-
// an `href` with no scheme check at any hop, and the guard costs one refinement.
|
|
176
|
-
//
|
|
177
|
-
// AT THE PARSE BOUNDARY, so there is exactly one place that decides. The defensive drop at the
|
|
178
|
-
// anchor itself is the SECOND half of the same finding and lives in `app-wrapper.ts`.
|
|
179
|
-
//
|
|
180
|
-
// STILL `.optional()`: an absent base is a real and honest posture — a standalone `agent-qa mcp`
|
|
181
|
-
// with no dashboard — and the carrier result names that absence rather than fabricating a URL.
|
|
182
|
-
// An EMPTY STRING is now REFUSED, which is stricter than before: `""` is not an absence an
|
|
183
|
-
// operator wrote by leaving the key out, it is a value that composes a relative fragment reading
|
|
184
|
-
// as a link and resolving against nothing.
|
|
160
|
+
// Only an absolute http(s) URL: the base composes an `href` inside the served App document.
|
|
185
161
|
baseUrl: z3.string().refine(
|
|
186
162
|
isAbsoluteHttpUrl,
|
|
187
163
|
{ message: "services.dashboard.baseUrl must be an absolute http(s) URL" }
|
|
188
164
|
).optional(),
|
|
189
165
|
workspace: z3.string().optional(),
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
// which REFUSES any non-loopback value — unconditionally since the phase-402 verification round
|
|
195
|
-
// (a declared run-service credential authenticates the three run-service paths and nothing else,
|
|
196
|
-
// so it was never evidence that the rest of this server's surface was authed).
|
|
197
|
-
//
|
|
198
|
-
// DELIBERATELY NOT `LoopbackHostSchema` even so. That schema refuses at PARSE time, which would
|
|
199
|
-
// make the runtime refusal unreachable and would report the problem as a config-shape error
|
|
200
|
-
// rather than as the security refusal it is — and it would also refuse a loopback SPELLING this
|
|
201
|
-
// field exists to allow (`localhost`, `::1`, `127.0.0.2` is refused on purpose).
|
|
166
|
+
// The run service and the other local doors are tokenless, so this block declares no token key.
|
|
167
|
+
// The interface the dashboard binds. A plain string, because the pre-listen bind guard refuses
|
|
168
|
+
// any non-loopback value at runtime, as the security refusal it is rather than a config-shape
|
|
169
|
+
// error, and a loopback spelling other than the default (`localhost`, `::1`) stays configurable.
|
|
202
170
|
host: z3.string().trim().min(1).optional()
|
|
203
171
|
}).strict();
|
|
204
172
|
var LOOPBACK_HOSTS = Object.freeze(["localhost", "127.0.0.1", "::1"]);
|
|
205
173
|
function isLoopbackHost(host) {
|
|
206
174
|
return LOOPBACK_HOSTS.includes(host.trim());
|
|
207
175
|
}
|
|
208
|
-
var LoopbackHostSchema = z3.string().trim().min(1).refine(
|
|
209
|
-
isLoopbackHost,
|
|
210
|
-
{ message: "MCP host must be localhost, 127.0.0.1, or ::1 in local-only mode" }
|
|
211
|
-
);
|
|
212
176
|
var McpConfigSchema = z3.object({
|
|
213
177
|
enabled: z3.boolean().optional(),
|
|
214
|
-
|
|
215
|
-
host: LoopbackHostSchema.optional(),
|
|
216
|
-
port: z3.number().int().min(1).max(65535).optional(),
|
|
217
|
-
path: z3.string().trim().min(1).regex(/^\//, "MCP path must start with /").optional()
|
|
178
|
+
port: z3.number().int().min(1).max(65535).optional()
|
|
218
179
|
}).strict();
|
|
219
180
|
var CacheConfigSchema = z3.object({
|
|
220
|
-
dir: z3.string()
|
|
221
|
-
ttl: DurationString
|
|
181
|
+
dir: z3.string()
|
|
222
182
|
}).strict();
|
|
223
183
|
var AuthStateConfigSchema = z3.object({
|
|
224
184
|
dir: z3.string().trim().min(1)
|
|
@@ -233,7 +193,7 @@ var RecordingConfigSchema = z3.object({
|
|
|
233
193
|
var AccessibilityConfigSchema = z3.object({
|
|
234
194
|
enabled: z3.boolean(),
|
|
235
195
|
standard: z3.enum(["wcag2a", "wcag2aa", "wcag2aaa"]).optional(),
|
|
236
|
-
runAfter: z3.enum(["every-step", "
|
|
196
|
+
runAfter: z3.enum(["every-step", "test-end"]).optional(),
|
|
237
197
|
failOnViolation: z3.boolean().optional(),
|
|
238
198
|
disableRules: z3.array(z3.string()).optional(),
|
|
239
199
|
exclude: z3.array(z3.string()).optional()
|
|
@@ -260,14 +220,7 @@ var isRepoRelative = (dir) => !/^[/\\]|^[A-Za-z]:[\\/]/.test(dir) && !/^\.\.(?:[
|
|
|
260
220
|
var KnowledgeConfigSchema = z4.object({
|
|
261
221
|
dir: z4.string().trim().min(1).refine(isRepoRelative, {
|
|
262
222
|
message: "knowledge.dir must be repo-relative \u2014 the committed knowledge root must live inside the repo for PR review and the committed .gitignore split"
|
|
263
|
-
}).default("agent-qa-knowledge")
|
|
264
|
-
// D-08: closed enum — deliberately no 'full' mode; full-res artifacts ride the
|
|
265
|
-
// artifact store/git-LFS side channel per knowledge-store.md §5.
|
|
266
|
-
commitArtifacts: z4.enum(["none", "thumbnails"]).default("none"),
|
|
267
|
-
// Absent = no cap: the engine's derived budget serves everything it ranks useful; when set
|
|
268
|
-
// it is a hard ceiling min(cap, derived), clamped above min_brief — efficacy over token
|
|
269
|
-
// saving (D-09, binds Phase 380 brief budgeting).
|
|
270
|
-
contextBudgetTokens: z4.number().int().positive().optional()
|
|
223
|
+
}).default("agent-qa-knowledge")
|
|
271
224
|
}).strict();
|
|
272
225
|
|
|
273
226
|
// src/schema/retired-keys.ts
|
|
@@ -288,6 +241,39 @@ var RETIRED_MEMORY_KEY_MESSAGES = {
|
|
|
288
241
|
circuitBreakerBaselineSize: "services.memory.circuitBreakerBaselineSize was removed \u2014 no circuit breaker exists",
|
|
289
242
|
circuitBreakerThreshold: "services.memory.circuitBreakerThreshold was removed \u2014 no circuit breaker exists"
|
|
290
243
|
};
|
|
244
|
+
var RETIRED_EVAL_KEY_MESSAGES = {
|
|
245
|
+
gate1: "eval.gate1 was removed: memory is no longer screened by replay before promotion. Delete this block.",
|
|
246
|
+
gate2: "eval.gate2 was removed: completed runs no longer replay served memory. Delete this block.",
|
|
247
|
+
adversarial: "eval.adversarial was removed: runs no longer stage adversarial probes. Delete this block.",
|
|
248
|
+
seeding: "eval.seeding was removed: runs no longer seed an eval corpus. Delete this block.",
|
|
249
|
+
judge: "eval.judge was removed: pass --judge <name> to agent-qa eval replay. Delete this key.",
|
|
250
|
+
judges: "eval.judges was removed: repeat --judge <name> on agent-qa eval replay. Delete this key.",
|
|
251
|
+
judgeAllowSameFamily: "eval.judgeAllowSameFamily was removed: pass --judge-allow-same-family to agent-qa eval replay. Delete this key.",
|
|
252
|
+
k: "eval.k was removed: pass --k <n> to agent-qa eval replay. Delete this key.",
|
|
253
|
+
seed: "eval.seed was removed: pass --seed <n> to agent-qa eval replay. Delete this key."
|
|
254
|
+
};
|
|
255
|
+
var RETIRED_STEP_KEY_MESSAGES = {
|
|
256
|
+
retries: "steps[].retries was removed: a test retries as a whole; set meta.retries. Delete this line.",
|
|
257
|
+
screenshot: "steps[].screenshot was removed: screenshots are set per run; pass --screenshot-mode every-step to agent-qa run. Delete this line."
|
|
258
|
+
};
|
|
259
|
+
var RETIRED_EVAL_BLOCK_MESSAGE = "eval was removed: agent-qa eval replay takes its settings as flags. Delete this block.";
|
|
260
|
+
var RETIRED_ALLOW_PRODUCTION_MESSAGE = "allowProduction was removed: production targets run like any other target. Delete this line.";
|
|
261
|
+
var RETIRED_REGISTRY_PROVIDERS_MESSAGE = "registry.providers was removed: BrowserStack is no longer supported; runs use local Playwright and Appium. Delete this block.";
|
|
262
|
+
var RETIRED_DEVICE_TRANSPORT_MESSAGE = "registry.devices.<name>.transport was removed: devices run on local Appium. Delete this line.";
|
|
263
|
+
var RETIRED_APP_BROWSERSTACK_MESSAGE = "registry.targets.<name>.app.browserstack was removed: BrowserStack is no longer supported; app.path is the app file. Delete this line.";
|
|
264
|
+
var RETIRED_CACHE_TTL_MESSAGE = "services.cache.ttl was removed: cached plans do not expire by age. Delete this line.";
|
|
265
|
+
var RETIRED_MCP_KEY_MESSAGES = {
|
|
266
|
+
host: "services.mcp.host was removed: agent-qa mcp always listens on 127.0.0.1. Delete this line.",
|
|
267
|
+
path: "services.mcp.path was removed: the MCP server answers on any path. Delete this line.",
|
|
268
|
+
transport: "services.mcp.transport was removed: agent-qa mcp uses stdio, or HTTP with --http. Delete this line."
|
|
269
|
+
};
|
|
270
|
+
var RETIRED_RUN_AFTER_NAVIGATION_MESSAGE = "services.accessibility.runAfter: navigation was removed: it never ran a check. Use test-end, or delete the line for every-step.";
|
|
271
|
+
var RETIRED_KNOWLEDGE_KEY_MESSAGES = {
|
|
272
|
+
contextBudgetTokens: "knowledge.contextBudgetTokens was removed: agent-qa sizes each run's knowledge itself. Delete this line.",
|
|
273
|
+
commitArtifacts: "knowledge.commitArtifacts was removed: knowledge bundles never commit images. Delete this line."
|
|
274
|
+
};
|
|
275
|
+
var RETIRED_LOCAL_PROVIDERS_MESSAGE = "providers was removed: BrowserStack is no longer supported. Delete this block.";
|
|
276
|
+
var RETIRED_LOCAL_APP_BROWSERSTACK_MESSAGE = "apps.<name>.browserstack was removed: BrowserStack is no longer supported; apps.<name>.path is the app file. Delete this line.";
|
|
291
277
|
var RETIRED_MAX_SUB_ACTIONS_MESSAGE = "use.planner.maxSubActions was removed in v2 \u2014 it was a refilling iteration count; set use.planner.maxActions (default 30) and use.planner.maxPlanningTurns (default 30)";
|
|
292
278
|
var RETIRED_PLANNER_KEY = "maxSubActions";
|
|
293
279
|
function isPlainObject(value) {
|
|
@@ -301,7 +287,7 @@ var retiredKeyScan = z5.any().superRefine((value, ctx) => {
|
|
|
301
287
|
if (isPlainObject(value.services) && value.services.memory !== void 0) {
|
|
302
288
|
ctx.addIssue({
|
|
303
289
|
code: z5.ZodIssueCode.custom,
|
|
304
|
-
params: { retired: true },
|
|
290
|
+
params: { retired: true, hardCut: true },
|
|
305
291
|
path: ["services", "memory"],
|
|
306
292
|
message: "services.memory was removed in v2 \u2014 the memory config surface collapsed into the top-level knowledge block"
|
|
307
293
|
});
|
|
@@ -312,7 +298,7 @@ var retiredKeyScan = z5.any().superRefine((value, ctx) => {
|
|
|
312
298
|
if (message) {
|
|
313
299
|
ctx.addIssue({
|
|
314
300
|
code: z5.ZodIssueCode.custom,
|
|
315
|
-
params: { retired: true },
|
|
301
|
+
params: { retired: true, hardCut: true },
|
|
316
302
|
path: ["services", "memory", key],
|
|
317
303
|
message
|
|
318
304
|
});
|
|
@@ -323,30 +309,164 @@ var retiredKeyScan = z5.any().superRefine((value, ctx) => {
|
|
|
323
309
|
if (isPlainObject(value.workspace) && value.workspace.agentRules !== void 0) {
|
|
324
310
|
ctx.addIssue({
|
|
325
311
|
code: z5.ZodIssueCode.custom,
|
|
326
|
-
params: { retired: true },
|
|
312
|
+
params: { retired: true, hardCut: true },
|
|
327
313
|
path: ["workspace", "agentRules"],
|
|
328
314
|
message: "workspace.agentRules was removed in v2 \u2014 rules dissolve into agent-qa-knowledge/policies/<scope>.md files"
|
|
329
315
|
});
|
|
330
316
|
}
|
|
331
|
-
if (
|
|
317
|
+
if (Object.hasOwn(value, "eval")) {
|
|
318
|
+
let named = 0;
|
|
319
|
+
if (isPlainObject(value.eval)) {
|
|
320
|
+
for (const key of Object.keys(value.eval)) {
|
|
321
|
+
if (Object.hasOwn(RETIRED_EVAL_KEY_MESSAGES, key)) {
|
|
322
|
+
named++;
|
|
323
|
+
ctx.addIssue({
|
|
324
|
+
code: z5.ZodIssueCode.custom,
|
|
325
|
+
params: { retired: true },
|
|
326
|
+
path: ["eval", key],
|
|
327
|
+
message: RETIRED_EVAL_KEY_MESSAGES[key]
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (named === 0) {
|
|
333
|
+
ctx.addIssue({
|
|
334
|
+
code: z5.ZodIssueCode.custom,
|
|
335
|
+
params: { retired: true },
|
|
336
|
+
path: ["eval"],
|
|
337
|
+
message: RETIRED_EVAL_BLOCK_MESSAGE
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (Object.hasOwn(value, "allowProduction")) {
|
|
332
342
|
ctx.addIssue({
|
|
333
343
|
code: z5.ZodIssueCode.custom,
|
|
334
344
|
params: { retired: true },
|
|
345
|
+
path: ["allowProduction"],
|
|
346
|
+
message: RETIRED_ALLOW_PRODUCTION_MESSAGE
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
if (isPlainObject(value.registry) && Object.hasOwn(value.registry, "providers")) {
|
|
350
|
+
ctx.addIssue({
|
|
351
|
+
code: z5.ZodIssueCode.custom,
|
|
352
|
+
params: { retired: true },
|
|
353
|
+
path: ["registry", "providers"],
|
|
354
|
+
message: RETIRED_REGISTRY_PROVIDERS_MESSAGE
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (isPlainObject(value.registry) && isPlainObject(value.registry.devices)) {
|
|
358
|
+
for (const [name, device] of Object.entries(value.registry.devices)) {
|
|
359
|
+
if (isPlainObject(device) && Object.hasOwn(device, "transport")) {
|
|
360
|
+
ctx.addIssue({
|
|
361
|
+
code: z5.ZodIssueCode.custom,
|
|
362
|
+
params: { retired: true },
|
|
363
|
+
path: ["registry", "devices", name, "transport"],
|
|
364
|
+
message: RETIRED_DEVICE_TRANSPORT_MESSAGE
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (isPlainObject(value.registry) && isPlainObject(value.registry.targets)) {
|
|
370
|
+
for (const [name, target] of Object.entries(value.registry.targets)) {
|
|
371
|
+
if (isPlainObject(target) && isPlainObject(target.app) && Object.hasOwn(target.app, "browserstack")) {
|
|
372
|
+
ctx.addIssue({
|
|
373
|
+
code: z5.ZodIssueCode.custom,
|
|
374
|
+
params: { retired: true },
|
|
375
|
+
path: ["registry", "targets", name, "app", "browserstack"],
|
|
376
|
+
message: RETIRED_APP_BROWSERSTACK_MESSAGE
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (isPlainObject(value.services) && isPlainObject(value.services.cache) && Object.hasOwn(value.services.cache, "ttl")) {
|
|
382
|
+
ctx.addIssue({
|
|
383
|
+
code: z5.ZodIssueCode.custom,
|
|
384
|
+
params: { retired: true },
|
|
385
|
+
path: ["services", "cache", "ttl"],
|
|
386
|
+
message: RETIRED_CACHE_TTL_MESSAGE
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
if (isPlainObject(value.services) && isPlainObject(value.services.mcp)) {
|
|
390
|
+
for (const key of Object.keys(value.services.mcp)) {
|
|
391
|
+
if (Object.hasOwn(RETIRED_MCP_KEY_MESSAGES, key)) {
|
|
392
|
+
ctx.addIssue({
|
|
393
|
+
code: z5.ZodIssueCode.custom,
|
|
394
|
+
params: { retired: true },
|
|
395
|
+
path: ["services", "mcp", key],
|
|
396
|
+
message: RETIRED_MCP_KEY_MESSAGES[key]
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (isPlainObject(value.services) && isPlainObject(value.services.accessibility) && value.services.accessibility.runAfter === "navigation") {
|
|
402
|
+
ctx.addIssue({
|
|
403
|
+
code: z5.ZodIssueCode.custom,
|
|
404
|
+
params: { retired: true },
|
|
405
|
+
path: ["services", "accessibility", "runAfter"],
|
|
406
|
+
message: RETIRED_RUN_AFTER_NAVIGATION_MESSAGE
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
if (isPlainObject(value.knowledge)) {
|
|
410
|
+
for (const key of Object.keys(value.knowledge)) {
|
|
411
|
+
if (Object.hasOwn(RETIRED_KNOWLEDGE_KEY_MESSAGES, key)) {
|
|
412
|
+
ctx.addIssue({
|
|
413
|
+
code: z5.ZodIssueCode.custom,
|
|
414
|
+
params: { retired: true },
|
|
415
|
+
path: ["knowledge", key],
|
|
416
|
+
message: RETIRED_KNOWLEDGE_KEY_MESSAGES[key]
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (hasRetiredPlannerKey(value.use)) {
|
|
422
|
+
ctx.addIssue({
|
|
423
|
+
code: z5.ZodIssueCode.custom,
|
|
424
|
+
params: { retired: true, hardCut: true },
|
|
335
425
|
path: ["use", "planner", RETIRED_PLANNER_KEY],
|
|
336
426
|
message: RETIRED_MAX_SUB_ACTIONS_MESSAGE
|
|
337
427
|
});
|
|
338
428
|
}
|
|
339
429
|
});
|
|
430
|
+
function refuseRetiredLocalBindings(raw, filePath) {
|
|
431
|
+
if (!isPlainObject(raw)) return;
|
|
432
|
+
const issues = [];
|
|
433
|
+
if (Object.hasOwn(raw, "providers")) {
|
|
434
|
+
issues.push({ path: "providers", message: RETIRED_LOCAL_PROVIDERS_MESSAGE });
|
|
435
|
+
}
|
|
436
|
+
if (isPlainObject(raw.apps)) {
|
|
437
|
+
for (const [name, app] of Object.entries(raw.apps)) {
|
|
438
|
+
if (isPlainObject(app) && Object.hasOwn(app, "browserstack")) {
|
|
439
|
+
issues.push({ path: `apps.${name}.browserstack`, message: RETIRED_LOCAL_APP_BROWSERSTACK_MESSAGE });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (issues.length === 0) return;
|
|
444
|
+
throw new Error(
|
|
445
|
+
[`${filePath}:`, ...issues.map((issue) => ` ${issue.path}: ${issue.message}`)].join("\n")
|
|
446
|
+
);
|
|
447
|
+
}
|
|
340
448
|
var retiredUseKeyScan = z5.any().superRefine((value, ctx) => {
|
|
341
449
|
if (hasRetiredPlannerKey(value)) {
|
|
342
450
|
ctx.addIssue({
|
|
343
451
|
code: z5.ZodIssueCode.custom,
|
|
344
|
-
params: { retired: true },
|
|
452
|
+
params: { retired: true, hardCut: true },
|
|
345
453
|
path: ["planner", RETIRED_PLANNER_KEY],
|
|
346
454
|
message: RETIRED_MAX_SUB_ACTIONS_MESSAGE
|
|
347
455
|
});
|
|
348
456
|
}
|
|
349
457
|
});
|
|
458
|
+
var retiredStepKeyScan = z5.any().superRefine((value, ctx) => {
|
|
459
|
+
if (!isPlainObject(value)) return;
|
|
460
|
+
for (const [key, message] of Object.entries(RETIRED_STEP_KEY_MESSAGES)) {
|
|
461
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
462
|
+
ctx.addIssue({
|
|
463
|
+
code: z5.ZodIssueCode.custom,
|
|
464
|
+
params: { retired: true },
|
|
465
|
+
path: [key],
|
|
466
|
+
message
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
});
|
|
350
470
|
|
|
351
471
|
// src/schema/registry-schema.ts
|
|
352
472
|
import { z as z7 } from "zod";
|
|
@@ -376,8 +496,7 @@ function isAbsoluteAppPath(value) {
|
|
|
376
496
|
return value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
377
497
|
}
|
|
378
498
|
var AppInstallSchema = z7.object({
|
|
379
|
-
path: z7.string().refine((v) => !isAbsoluteAppPath(v), { message: "app.path must be relative" }).optional()
|
|
380
|
-
browserstack: z7.string().optional()
|
|
499
|
+
path: z7.string().refine((v) => !isAbsoluteAppPath(v), { message: "app.path must be relative" }).optional()
|
|
381
500
|
}).strict();
|
|
382
501
|
var TargetSchema = z7.object({
|
|
383
502
|
product: z7.string().refine(
|
|
@@ -390,15 +509,8 @@ var TargetSchema = z7.object({
|
|
|
390
509
|
appActivity: z7.string().optional(),
|
|
391
510
|
app: AppInstallSchema.optional(),
|
|
392
511
|
url: z7.string().optional(),
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
// (D-18) undeclared means non-production, decided where the production refusal reads it (the
|
|
396
|
-
// run gate that decided "shared" is deleted). A default here would make silence and an honest
|
|
397
|
-
// declaration indistinguishable at every later reader, and
|
|
398
|
-
// `.strict()` above is what turns a typoed key into a refusal rather than a silent undeclared.
|
|
399
|
-
// 432 D-18: `production` is the third declared class; only top-level `allowProduction: true`
|
|
400
|
-
// opts a run into it.
|
|
401
|
-
environment: z7.enum(["local", "shared", "production"]).optional()
|
|
512
|
+
// A label only: agent-qa runs every target the same way, whatever it declares.
|
|
513
|
+
environment: z7.enum(["local", "shared", "production"]).optional().describe("A label for this target's environment. agent-qa runs every target the same way.")
|
|
402
514
|
}).strict().superRefine((val, ctx) => {
|
|
403
515
|
if (val.platform === "web" && !val.url) {
|
|
404
516
|
ctx.addIssue({ code: z7.ZodIssueCode.custom, message: "Web targets must have a url" });
|
|
@@ -406,30 +518,25 @@ var TargetSchema = z7.object({
|
|
|
406
518
|
});
|
|
407
519
|
var DeviceProfileSchema = z7.object({
|
|
408
520
|
platform: z7.enum(["android", "ios"]),
|
|
409
|
-
transport: TransportSchema,
|
|
410
521
|
match: z7.record(z7.string(), z7.unknown()).optional().default({})
|
|
411
522
|
}).strict().superRefine((val, ctx) => {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
});
|
|
423
|
-
}
|
|
523
|
+
const androidOnly = ["avd", "serial", "appPackage", "appActivity", "automationName", "browserName", "platformVersion"];
|
|
524
|
+
const iosOnly = ["udid", "bundleId", "automationName", "platformVersion"];
|
|
525
|
+
const allowed = val.platform === "android" ? androidOnly : iosOnly;
|
|
526
|
+
for (const key of Object.keys(val.match)) {
|
|
527
|
+
if (!allowed.includes(key)) {
|
|
528
|
+
ctx.addIssue({
|
|
529
|
+
code: z7.ZodIssueCode.custom,
|
|
530
|
+
path: ["match", key],
|
|
531
|
+
message: `Match field "${key}" is not valid for an ${val.platform} device. Allowed: ${allowed.join(", ")}`
|
|
532
|
+
});
|
|
424
533
|
}
|
|
425
534
|
}
|
|
426
535
|
});
|
|
427
|
-
var ProviderConfigSchema = z7.record(z7.string(), z7.unknown());
|
|
428
536
|
var RegistrySchema = z7.object({
|
|
429
537
|
llms: z7.array(NamedLLMConfigSchema).optional().default([]),
|
|
430
538
|
targets: z7.record(TargetNameSchema, TargetSchema).optional(),
|
|
431
|
-
devices: z7.record(z7.string(), DeviceProfileSchema).optional()
|
|
432
|
-
providers: z7.record(z7.string(), ProviderConfigSchema).optional()
|
|
539
|
+
devices: z7.record(z7.string(), DeviceProfileSchema).optional()
|
|
433
540
|
}).strict();
|
|
434
541
|
|
|
435
542
|
// src/schema/use-schema.ts
|
|
@@ -473,28 +580,11 @@ var UseSchema = z8.object({
|
|
|
473
580
|
logCapture: LogCaptureConfigSchema.optional(),
|
|
474
581
|
cache: z8.boolean().optional(),
|
|
475
582
|
llm: z8.string().optional(),
|
|
476
|
-
|
|
477
|
-
* Names the app-under-test build for the DCAY-01 buildTag-to-ordinal mapping.
|
|
478
|
-
* Precedence: CLI --build-tag > AGENT_QA_BUILD_TAG > this config key.
|
|
479
|
-
*/
|
|
583
|
+
// Precedence: CLI `--build-tag`, then `AGENT_QA_BUILD_TAG`, then this key.
|
|
480
584
|
buildTag: z8.string().trim().min(1).optional(),
|
|
481
|
-
|
|
482
|
-
* 402-03 (D-12): run the members of one run CONCURRENTLY. Absent or false is one member at a
|
|
483
|
-
* time — today's behaviour, byte for byte.
|
|
484
|
-
*/
|
|
585
|
+
// Runs one run's members concurrently; absent or false runs one member at a time.
|
|
485
586
|
parallel: z8.boolean().optional(),
|
|
486
|
-
|
|
487
|
-
* 402-03 (D-12): the ceiling on concurrent members when `parallel` is true. Absent resolves to
|
|
488
|
-
* `DEFAULT_PARALLEL_WORKERS` (3). Effective concurrency is `min(memberCount, workers)`, decided
|
|
489
|
-
* in exactly one place — `resolveEffectiveWorkers` in `@vostride/agent-qa-schema`.
|
|
490
|
-
*
|
|
491
|
-
* A POSITIVE INTEGER, refused otherwise: an unbounded or nonsensical count here is the only
|
|
492
|
-
* reachable input to a concurrency denial-of-service (T-402-12), and it is a config typo rather
|
|
493
|
-
* than a caller's field — `RunsStartInput` names neither of these two.
|
|
494
|
-
*
|
|
495
|
-
* NOT `.default(...)`: a defaulted key changes the PARSED config type and every hand-built
|
|
496
|
-
* config fixture would then need it. The default is the resolver's, not the schema's.
|
|
497
|
-
*/
|
|
587
|
+
// Not defaulted, so the parsed type is unchanged; `resolveEffectiveWorkers` owns the default.
|
|
498
588
|
workers: z8.number().int().positive().optional()
|
|
499
589
|
}).strict();
|
|
500
590
|
var UseOverrideSchema = z8.object({
|
|
@@ -509,7 +599,9 @@ var UseOverrideSchema = z8.object({
|
|
|
509
599
|
timeout: z8.object({
|
|
510
600
|
step: DurationString.optional(),
|
|
511
601
|
test: DurationString.optional(),
|
|
512
|
-
navigation: DurationString.optional()
|
|
602
|
+
navigation: DurationString.optional().describe(
|
|
603
|
+
"How long a web page load or URL wait can take. Default 2m. Android and iOS runs do not use it."
|
|
604
|
+
)
|
|
513
605
|
}).strict().optional(),
|
|
514
606
|
planner: z8.object({
|
|
515
607
|
maxActions: z8.number().int().positive().optional(),
|
|
@@ -525,7 +617,7 @@ var UseOverrideSchema = z8.object({
|
|
|
525
617
|
mobile: MobileUseOverrideSchema.optional(),
|
|
526
618
|
llm: z8.string().optional(),
|
|
527
619
|
parallel: z8.boolean().optional(),
|
|
528
|
-
|
|
620
|
+
// The override half of `use.workers`, shaped like `UseSchema`'s.
|
|
529
621
|
workers: z8.number().int().positive().optional(),
|
|
530
622
|
device: z8.string().optional()
|
|
531
623
|
}).strict();
|
|
@@ -569,82 +661,15 @@ var PluginsSchema = z10.object({
|
|
|
569
661
|
var AgentQaConfigSchemaInner = z10.object({
|
|
570
662
|
workspace: WorkspaceSchema,
|
|
571
663
|
services: ServicesSchema.prefault({}).optional(),
|
|
572
|
-
//
|
|
573
|
-
// configs always materialize knowledge defaults (zod 4 .default() short-circuits without
|
|
574
|
-
// parsing — .prefault() runs the inner defaults) — matches the providers `.default([])`
|
|
575
|
-
// precedent in spirit.
|
|
664
|
+
// `.prefault` runs the inner defaults; zod 4's `.default()` short-circuits without parsing.
|
|
576
665
|
knowledge: KnowledgeConfigSchema.prefault({}),
|
|
577
666
|
registry: RegistrySchema.optional(),
|
|
578
667
|
use: UseSchema.optional(),
|
|
579
668
|
plugins: PluginsSchema.optional(),
|
|
580
|
-
analytics: AnalyticsSchema.optional()
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
allowProduction: z10.boolean().optional(),
|
|
584
|
-
// 2026-10-01 (432-22, D-15/D-18/D-22): the `safety` section (`safety.trustMode`) is deleted with
|
|
585
|
-
// trust mode. `allowProduction` above is the only production opt-in, and a file that still
|
|
586
|
-
// declares `safety` gets the normal strict unrecognized-key error.
|
|
587
|
-
// D-03 (Phase 372): eval substrate params. `judge` references a NAMED registry.llms entry;
|
|
588
|
-
// strict so unknown eval subkeys are rejected.
|
|
589
|
-
eval: z10.object({
|
|
590
|
-
judge: z10.string().min(1).refine((name) => name.trim().length > 0).optional(),
|
|
591
|
-
judges: z10.array(z10.string().min(1).refine((name) => name.trim().length > 0)).max(3).optional(),
|
|
592
|
-
// Owner override of D-10 (2026-07-12): explicit default-off opt-in that lets a
|
|
593
|
-
// KNOWN-same-family judge run; the eligibility law discloses the override on every
|
|
594
|
-
// scored entry (familyOverride) and the report projection renders one honesty line.
|
|
595
|
-
// Unknown families stay forbidden regardless.
|
|
596
|
-
judgeAllowSameFamily: z10.boolean().optional(),
|
|
597
|
-
k: z10.number().int().positive().optional(),
|
|
598
|
-
seed: z10.number().int().optional(),
|
|
599
|
-
// 2026-09-07, 417 D-19: Gate-1 screen cap; recorded default lives in the CLI.
|
|
600
|
-
// 2026-09-11, 422 D-14: `maxPerJob` bounds how many Gate-1 CALLS one post-run proposal job
|
|
601
|
-
// may make; recorded default lives beside the handler (PROPOSE_DEFAULT_MAX_GATE1_PER_JOB).
|
|
602
|
-
// A SECOND axis beside `maxScreens`, not a refinement of it — that one bounds how much a
|
|
603
|
-
// single screening looks at, this one bounds how many screenings a run's tail buys — and an
|
|
604
|
-
// operator can legitimately want a generous first and a tight second. Positive-integer only,
|
|
605
|
-
// for the sharper version of the usual reason: a bound that silently fell back to the
|
|
606
|
-
// recorded default because the operator typed `0` would buy replay work they refused. There
|
|
607
|
-
// is deliberately NO CLI flag: the bound is charged durably per job and read back by a
|
|
608
|
-
// resumed attempt, so a per-invocation flag would be a bound that could change between the
|
|
609
|
-
// attempt that spent it and the attempt that has to honour what was spent.
|
|
610
|
-
gate1: z10.object({
|
|
611
|
-
maxScreens: z10.number().int().positive().optional(),
|
|
612
|
-
maxPerJob: z10.number().int().positive().optional()
|
|
613
|
-
}).strict().optional(),
|
|
614
|
-
// 2026-09-07, 418 D-04/D-13/D-14: Gate-2 ablation cap, canary horizon (build ordinals)
|
|
615
|
-
// and per-scope lower-trust cap; recorded defaults live in the CLI (GATE2_DEFAULT_*).
|
|
616
|
-
gate2: z10.object({
|
|
617
|
-
maxAblations: z10.number().int().positive().optional(),
|
|
618
|
-
canaryHorizonBuilds: z10.number().int().positive().optional(),
|
|
619
|
-
maxLowerTrustPerScope: z10.number().int().positive().optional()
|
|
620
|
-
}).strict().optional(),
|
|
621
|
-
// 2026-09-11, 422 D-04: how many NOVEL seeds one adversarial refresh may derive from
|
|
622
|
-
// before the rest are counted and deferred; recorded default lives in eval core
|
|
623
|
-
// (ADVERSARIAL_DEFAULT_MAX_SEEDS_PER_REFRESH). Strict, and positive-integer only: a cap
|
|
624
|
-
// that silently fell back to the default because the operator typed `0` is not a cap.
|
|
625
|
-
// 2026-09-11, 422 D-04/D-10: `maxProbeCalls` bounds PROVIDER CALLS one refresh may make to
|
|
626
|
-
// author probes; recorded default lives in eval-local
|
|
627
|
-
// (ADVERSARIAL_DEFAULT_MAX_PROBE_CALLS). It is a SECOND budget beside the seed cap on
|
|
628
|
-
// purpose — deterministic seeds cost CPU and authored probes cost money no later run can
|
|
629
|
-
// recover — and it is held to the same positive-integer strictness for the sharper version
|
|
630
|
-
// of the same reason: a cap that silently fell back to the default because the operator
|
|
631
|
-
// typed `0` would buy calls they refused.
|
|
632
|
-
adversarial: z10.object({
|
|
633
|
-
maxSeedsPerRefresh: z10.number().int().positive().optional(),
|
|
634
|
-
maxProbeCalls: z10.number().int().positive().optional()
|
|
635
|
-
}).strict().optional(),
|
|
636
|
-
seeding: z10.object({
|
|
637
|
-
silverPerTestCap: z10.number().int().positive().optional()
|
|
638
|
-
}).strict().optional()
|
|
639
|
-
}).strict().optional()
|
|
669
|
+
analytics: AnalyticsSchema.optional()
|
|
670
|
+
// There is no `safety` section: a file that still declares one gets the normal strict
|
|
671
|
+
// unrecognized-key error.
|
|
640
672
|
}).strict().superRefine((val, ctx) => {
|
|
641
|
-
if (val.eval?.judge !== void 0 && val.eval.judges !== void 0) {
|
|
642
|
-
ctx.addIssue({
|
|
643
|
-
code: z10.ZodIssueCode.custom,
|
|
644
|
-
path: ["eval"],
|
|
645
|
-
message: "Set either eval.judge or eval.judges, never both; remove one declaration."
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
673
|
const llms = val.registry?.llms ?? [];
|
|
649
674
|
const names = llms.map((c) => c.name);
|
|
650
675
|
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
|
|
@@ -669,13 +694,6 @@ var AgentQaConfigSchemaInner = z10.object({
|
|
|
669
694
|
message: `use.llm "${val.use.llm}" does not match any name in registry.llms`
|
|
670
695
|
});
|
|
671
696
|
}
|
|
672
|
-
if (!MobileAppStateSchema.safeParse(val.use?.mobile?.appState).success) {
|
|
673
|
-
ctx.addIssue({
|
|
674
|
-
code: z10.ZodIssueCode.custom,
|
|
675
|
-
path: ["use", "mobile", "appState"],
|
|
676
|
-
message: "use.mobile.appState is required and must be one of: preserve | reset"
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
697
|
});
|
|
680
698
|
var AgentQaConfigSchema = retiredKeyScan.pipe(AgentQaConfigSchemaInner);
|
|
681
699
|
|
|
@@ -698,8 +716,6 @@ var TestStepSchema = z11.union([
|
|
|
698
716
|
z11.object({
|
|
699
717
|
step: z11.string(),
|
|
700
718
|
timeout: OptionalDuration,
|
|
701
|
-
retries: z11.number().optional(),
|
|
702
|
-
screenshot: z11.boolean().optional(),
|
|
703
719
|
capture: CaptureConfigSchema.optional()
|
|
704
720
|
}).strict()
|
|
705
721
|
]);
|
|
@@ -722,19 +738,9 @@ var TestDefinitionSchema = z11.object({
|
|
|
722
738
|
meta: TestMetaSchema.optional(),
|
|
723
739
|
setup: z11.array(HookIdSchema).optional(),
|
|
724
740
|
teardown: z11.array(HookIdSchema).optional(),
|
|
725
|
-
steps: z11.array(TestStepSchema).min(1)
|
|
741
|
+
steps: z11.array(retiredStepKeyScan.pipe(TestStepSchema)).min(1)
|
|
726
742
|
}).strict();
|
|
727
743
|
|
|
728
|
-
// src/schema/action-schema.ts
|
|
729
|
-
import { z as z12 } from "zod";
|
|
730
|
-
var ActionPlanSchema = z12.object({
|
|
731
|
-
reasoning: z12.string().describe("Brief explanation of why this action was chosen"),
|
|
732
|
-
action: z12.object({ type: z12.string() }).passthrough(),
|
|
733
|
-
confidence: z12.number().describe("Confidence level from 0 to 1"),
|
|
734
|
-
stepComplete: z12.boolean().default(false).describe("Whether this action completes the step goal. Set true when the step objective is fully accomplished, false when more actions are needed."),
|
|
735
|
-
stepFailed: z12.boolean().optional().default(false).describe("Set true when the step goal cannot be achieved from the current screen state. When true, no action is executed and the step fails with the reasoning as the error message.")
|
|
736
|
-
});
|
|
737
|
-
|
|
738
744
|
// src/schema/resolve-knowledge-root.ts
|
|
739
745
|
import path from "path";
|
|
740
746
|
function resolveKnowledgeRoot(knowledge, configDir) {
|
|
@@ -742,66 +748,66 @@ function resolveKnowledgeRoot(knowledge, configDir) {
|
|
|
742
748
|
}
|
|
743
749
|
|
|
744
750
|
// src/schema/suite-schema.ts
|
|
745
|
-
import { z as
|
|
751
|
+
import { z as z12 } from "zod";
|
|
746
752
|
import { isCanonicalHookId as isCanonicalHookId2, isCanonicalSuiteId, isCanonicalTestId as isCanonicalTestId2 } from "@vostride/agent-qa-ids";
|
|
747
|
-
var SuiteTestEntrySchema =
|
|
748
|
-
test:
|
|
749
|
-
id:
|
|
753
|
+
var SuiteTestEntrySchema = z12.object({
|
|
754
|
+
test: z12.string(),
|
|
755
|
+
id: z12.string().refine((value) => isCanonicalTestId2(value), {
|
|
750
756
|
message: "Test ID must be t_ followed by 10 id-agent words"
|
|
751
757
|
})
|
|
752
758
|
}).strict();
|
|
753
|
-
var HookIdSchema2 =
|
|
759
|
+
var HookIdSchema2 = z12.string().refine((value) => isCanonicalHookId2(value), {
|
|
754
760
|
message: "Hook ID must be h_ followed by 10 id-agent words"
|
|
755
761
|
});
|
|
756
|
-
var SuiteDefinitionSchema =
|
|
757
|
-
"suite-id":
|
|
762
|
+
var SuiteDefinitionSchema = z12.object({
|
|
763
|
+
"suite-id": z12.string().refine((value) => isCanonicalSuiteId(value), {
|
|
758
764
|
message: "Suite ID must be s_ followed by 10 id-agent words"
|
|
759
765
|
}).optional(),
|
|
760
|
-
name:
|
|
761
|
-
target:
|
|
762
|
-
context:
|
|
763
|
-
setup:
|
|
764
|
-
teardown:
|
|
765
|
-
tests:
|
|
766
|
+
name: z12.string(),
|
|
767
|
+
target: z12.string(),
|
|
768
|
+
context: z12.string().optional(),
|
|
769
|
+
setup: z12.array(HookIdSchema2).optional(),
|
|
770
|
+
teardown: z12.array(HookIdSchema2).optional(),
|
|
771
|
+
tests: z12.array(SuiteTestEntrySchema).min(1),
|
|
766
772
|
use: retiredUseKeyScan.pipe(UseOverrideSchema).optional()
|
|
767
773
|
}).strict();
|
|
768
774
|
|
|
769
775
|
// src/hooks/schema.ts
|
|
770
|
-
import { z as
|
|
776
|
+
import { z as z13 } from "zod";
|
|
771
777
|
import ms3 from "ms";
|
|
772
778
|
import { isCanonicalHookId as isCanonicalHookId3 } from "@vostride/agent-qa-ids";
|
|
773
|
-
var HookDurationString =
|
|
779
|
+
var HookDurationString = z13.string().refine((v) => ms3(v) !== void 0, {
|
|
774
780
|
message: 'Invalid duration format (e.g., "30s", "2m", "1h")'
|
|
775
781
|
}).transform((v) => ms3(v));
|
|
776
782
|
var HOOK_ID_MESSAGE = "Hook ID must be h_ followed by 10 id-agent words";
|
|
777
|
-
var HookDefinitionSchema =
|
|
778
|
-
id:
|
|
783
|
+
var HookDefinitionSchema = z13.object({
|
|
784
|
+
id: z13.string().refine((value) => isCanonicalHookId3(value), {
|
|
779
785
|
message: HOOK_ID_MESSAGE
|
|
780
786
|
}),
|
|
781
|
-
name:
|
|
782
|
-
runtime:
|
|
783
|
-
file:
|
|
784
|
-
deps:
|
|
785
|
-
packageFile:
|
|
787
|
+
name: z13.string().min(1),
|
|
788
|
+
runtime: z13.enum(["node", "bun", "python", "bash"]),
|
|
789
|
+
file: z13.string().min(1),
|
|
790
|
+
deps: z13.array(z13.string()).optional().default([]),
|
|
791
|
+
packageFile: z13.string().optional(),
|
|
786
792
|
timeout: HookDurationString,
|
|
787
|
-
network:
|
|
793
|
+
network: z13.boolean().optional().default(true)
|
|
788
794
|
});
|
|
789
|
-
var HooksFileSchema =
|
|
790
|
-
hooks:
|
|
795
|
+
var HooksFileSchema = z13.object({
|
|
796
|
+
hooks: z13.array(HookDefinitionSchema)
|
|
791
797
|
}).superRefine((val, ctx) => {
|
|
792
798
|
const ids = /* @__PURE__ */ new Set();
|
|
793
799
|
const names = /* @__PURE__ */ new Set();
|
|
794
800
|
for (let i = 0; i < val.hooks.length; i++) {
|
|
795
801
|
if (ids.has(val.hooks[i].id)) {
|
|
796
802
|
ctx.addIssue({
|
|
797
|
-
code:
|
|
803
|
+
code: z13.ZodIssueCode.custom,
|
|
798
804
|
path: ["hooks", i, "id"],
|
|
799
805
|
message: `Duplicate hook id: "${val.hooks[i].id}"`
|
|
800
806
|
});
|
|
801
807
|
}
|
|
802
808
|
if (names.has(val.hooks[i].name)) {
|
|
803
809
|
ctx.addIssue({
|
|
804
|
-
code:
|
|
810
|
+
code: z13.ZodIssueCode.custom,
|
|
805
811
|
path: ["hooks", i, "name"],
|
|
806
812
|
message: `Duplicate hook name: "${val.hooks[i].name}"`
|
|
807
813
|
});
|
|
@@ -1167,7 +1173,7 @@ var PatchOpUnsupportedTargetError = class extends Error {
|
|
|
1167
1173
|
var PatchedDocumentInvalidError = class extends Error {
|
|
1168
1174
|
constructor(phase, detail) {
|
|
1169
1175
|
super(
|
|
1170
|
-
`cst-patch: the ${phase} document is not a valid test: ${detail}; a patched document is schema-validated before any caller may write it
|
|
1176
|
+
`cst-patch: the ${phase} document is not a valid test: ${detail}; a patched document is schema-validated before any caller may write it`
|
|
1171
1177
|
);
|
|
1172
1178
|
this.phase = phase;
|
|
1173
1179
|
this.detail = detail;
|
|
@@ -1263,7 +1269,7 @@ function resolveSeqSlot(doc, pointer, segments, op) {
|
|
|
1263
1269
|
if (segments.length < 2) {
|
|
1264
1270
|
throw new PatchOpUnsupportedTargetError(
|
|
1265
1271
|
pointer,
|
|
1266
|
-
`${op} operates on a sequence item, and that path names a top-level key; adding or removing a key is a document rewrite
|
|
1272
|
+
`${op} operates on a sequence item, and that path names a top-level key; adding or removing a key is a document rewrite, not a patch`
|
|
1267
1273
|
);
|
|
1268
1274
|
}
|
|
1269
1275
|
const parent = resolveNode(doc, segments.slice(0, -1));
|
|
@@ -1932,9 +1938,6 @@ var MobileSetupError = class extends Error {
|
|
|
1932
1938
|
function isMobilePlatform(value) {
|
|
1933
1939
|
return value === "android" || value === "ios";
|
|
1934
1940
|
}
|
|
1935
|
-
function isMobileTransport(value) {
|
|
1936
|
-
return value === "local" || value === "browserstack";
|
|
1937
|
-
}
|
|
1938
1941
|
function asOptionalString(value) {
|
|
1939
1942
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1940
1943
|
}
|
|
@@ -2006,11 +2009,6 @@ function resolveAppInstall(target, input, platform, deviceName, sourceTrace) {
|
|
|
2006
2009
|
const pathValue = localPath ?? targetPath;
|
|
2007
2010
|
const pathSource = localPath ? `agent-qa.local.yaml apps.${input.targetName}.path` : targetPath ? `agent-qa.config.yaml registry.targets.${input.targetName}.app.path` : void 0;
|
|
2008
2011
|
const pathBaseDir = localPath ? baseDirFor(input.localConfigFilePath ?? input.localBindings?.filePath) : targetPath ? baseDirFor(input.configFilePath) : void 0;
|
|
2009
|
-
const localBrowserStack = asOptionalString(localApp?.browserstack);
|
|
2010
|
-
const targetBrowserStack = asOptionalString(target.app?.browserstack);
|
|
2011
|
-
const browserstackValue = localBrowserStack ?? targetBrowserStack;
|
|
2012
|
-
const browserstackSource = localBrowserStack ? `agent-qa.local.yaml apps.${input.targetName}.browserstack` : targetBrowserStack ? `agent-qa.config.yaml registry.targets.${input.targetName}.app.browserstack` : void 0;
|
|
2013
|
-
const browserstackBaseDir = localBrowserStack ? baseDirFor(input.localConfigFilePath ?? input.localBindings?.filePath) : targetBrowserStack ? baseDirFor(input.configFilePath) : void 0;
|
|
2014
2012
|
const install = { sourceTrace: installSourceTrace };
|
|
2015
2013
|
if (pathValue) {
|
|
2016
2014
|
if (isAbsoluteAppInstallValue(pathValue)) {
|
|
@@ -2036,23 +2034,7 @@ function resolveAppInstall(target, input, platform, deviceName, sourceTrace) {
|
|
|
2036
2034
|
sourceTrace.push(`app.path=${pathSource}`);
|
|
2037
2035
|
}
|
|
2038
2036
|
}
|
|
2039
|
-
|
|
2040
|
-
if (isAbsoluteAppInstallValue(browserstackValue)) {
|
|
2041
|
-
throw appInstallError(`Configured BrowserStack app value must be relative or a BrowserStack reference: ${browserstackValue}`, input, {
|
|
2042
|
-
platform,
|
|
2043
|
-
deviceName,
|
|
2044
|
-
appId: browserstackValue,
|
|
2045
|
-
sourceTrace: [...sourceTrace, `app.browserstack=${browserstackSource}`]
|
|
2046
|
-
});
|
|
2047
|
-
}
|
|
2048
|
-
install.browserstack = browserstackValue;
|
|
2049
|
-
install.browserstackBaseDir = browserstackBaseDir;
|
|
2050
|
-
if (browserstackSource) {
|
|
2051
|
-
installSourceTrace["app.browserstack"] = browserstackSource;
|
|
2052
|
-
sourceTrace.push(`app.browserstack=${browserstackSource}`);
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
return install.path || install.browserstack ? install : void 0;
|
|
2037
|
+
return install.path ? install : void 0;
|
|
2056
2038
|
}
|
|
2057
2039
|
function resolveMobileRunConfig(input) {
|
|
2058
2040
|
const targets = input.config.registry?.targets;
|
|
@@ -2106,7 +2088,6 @@ function resolveMobileRunConfig(input) {
|
|
|
2106
2088
|
{ platform, deviceName }
|
|
2107
2089
|
);
|
|
2108
2090
|
}
|
|
2109
|
-
const transport = isMobileTransport(deviceProfile.transport) ? deviceProfile.transport : "local";
|
|
2110
2091
|
const localMatch = input.localBindings?.devices?.[deviceName] ?? null;
|
|
2111
2092
|
const mergedMatch = { ...deviceProfile.match ?? {}, ...localMatch ?? {} };
|
|
2112
2093
|
const appSourceTrace = {};
|
|
@@ -2148,11 +2129,9 @@ function resolveMobileRunConfig(input) {
|
|
|
2148
2129
|
platform,
|
|
2149
2130
|
targetName: input.targetName,
|
|
2150
2131
|
deviceName,
|
|
2151
|
-
transport,
|
|
2152
2132
|
device: {
|
|
2153
2133
|
name: deviceName,
|
|
2154
2134
|
platform,
|
|
2155
|
-
transport,
|
|
2156
2135
|
match: mergedMatch
|
|
2157
2136
|
},
|
|
2158
2137
|
app,
|
|
@@ -3120,7 +3099,7 @@ function clearLoadedProviderPluginModules() {
|
|
|
3120
3099
|
}
|
|
3121
3100
|
|
|
3122
3101
|
// src/logging/types.ts
|
|
3123
|
-
import { z as
|
|
3102
|
+
import { z as z14 } from "zod";
|
|
3124
3103
|
var LOG_LEVEL_PRIORITY = {
|
|
3125
3104
|
silent: 0,
|
|
3126
3105
|
error: 1,
|
|
@@ -3128,46 +3107,45 @@ var LOG_LEVEL_PRIORITY = {
|
|
|
3128
3107
|
info: 3,
|
|
3129
3108
|
debug: 4
|
|
3130
3109
|
};
|
|
3131
|
-
var AgentLogDataSchema =
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
elementCount: z15.number().optional()
|
|
3110
|
+
var AgentLogDataSchema = z14.object({
|
|
3111
|
+
hasScreenshot: z14.boolean().optional(),
|
|
3112
|
+
duration: z14.number().optional(),
|
|
3113
|
+
elementCount: z14.number().optional()
|
|
3136
3114
|
});
|
|
3137
|
-
var AdapterLogDataSchema =
|
|
3138
|
-
actionType:
|
|
3139
|
-
selector:
|
|
3140
|
-
coordinates:
|
|
3141
|
-
elementProperties:
|
|
3142
|
-
duration:
|
|
3115
|
+
var AdapterLogDataSchema = z14.object({
|
|
3116
|
+
actionType: z14.string().optional(),
|
|
3117
|
+
selector: z14.string().optional(),
|
|
3118
|
+
coordinates: z14.object({ x: z14.number(), y: z14.number() }).optional(),
|
|
3119
|
+
elementProperties: z14.record(z14.string(), z14.unknown()).optional(),
|
|
3120
|
+
duration: z14.number().optional()
|
|
3143
3121
|
});
|
|
3144
|
-
var CacheLogDataSchema =
|
|
3145
|
-
operation:
|
|
3146
|
-
stepHash:
|
|
3147
|
-
screenHash:
|
|
3148
|
-
hit:
|
|
3149
|
-
age:
|
|
3150
|
-
entriesDeleted:
|
|
3122
|
+
var CacheLogDataSchema = z14.object({
|
|
3123
|
+
operation: z14.enum(["get", "set", "invalidate", "bypass", "batch-purge"]),
|
|
3124
|
+
stepHash: z14.string().optional(),
|
|
3125
|
+
screenHash: z14.string().optional(),
|
|
3126
|
+
hit: z14.boolean().optional(),
|
|
3127
|
+
age: z14.number().optional(),
|
|
3128
|
+
entriesDeleted: z14.number().optional()
|
|
3151
3129
|
});
|
|
3152
|
-
var PlannerLogDataSchema =
|
|
3153
|
-
model:
|
|
3154
|
-
promptTokens:
|
|
3155
|
-
completionTokens:
|
|
3156
|
-
latencyMs:
|
|
3157
|
-
confidence:
|
|
3158
|
-
actionType:
|
|
3130
|
+
var PlannerLogDataSchema = z14.object({
|
|
3131
|
+
model: z14.string().optional(),
|
|
3132
|
+
promptTokens: z14.number().optional(),
|
|
3133
|
+
completionTokens: z14.number().optional(),
|
|
3134
|
+
latencyMs: z14.number().optional(),
|
|
3135
|
+
confidence: z14.number().optional(),
|
|
3136
|
+
actionType: z14.string().optional()
|
|
3159
3137
|
});
|
|
3160
|
-
var HookLogDataSchema =
|
|
3161
|
-
hookName:
|
|
3162
|
-
phase:
|
|
3163
|
-
duration:
|
|
3164
|
-
success:
|
|
3138
|
+
var HookLogDataSchema = z14.object({
|
|
3139
|
+
hookName: z14.string().optional(),
|
|
3140
|
+
phase: z14.enum(["setup", "teardown"]).optional(),
|
|
3141
|
+
duration: z14.number().optional(),
|
|
3142
|
+
success: z14.boolean().optional()
|
|
3165
3143
|
});
|
|
3166
|
-
var RunnerLogDataSchema =
|
|
3167
|
-
stepIndex:
|
|
3168
|
-
totalSteps:
|
|
3169
|
-
testName:
|
|
3170
|
-
variablesCaptured:
|
|
3144
|
+
var RunnerLogDataSchema = z14.object({
|
|
3145
|
+
stepIndex: z14.number().optional(),
|
|
3146
|
+
totalSteps: z14.number().optional(),
|
|
3147
|
+
testName: z14.string().optional(),
|
|
3148
|
+
variablesCaptured: z14.number().optional()
|
|
3171
3149
|
});
|
|
3172
3150
|
var LogSourceDataSchemas = {
|
|
3173
3151
|
agent: AgentLogDataSchema,
|
|
@@ -3198,12 +3176,60 @@ function formatLogEntry(entry) {
|
|
|
3198
3176
|
return `${time} ${level} ${source} ${entry.message}${dataSuffix}`;
|
|
3199
3177
|
}
|
|
3200
3178
|
|
|
3179
|
+
// src/logging/run-text-output.ts
|
|
3180
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
3181
|
+
var STDOUT_RESERVATIONS = /* @__PURE__ */ Symbol.for("agent-qa.run-text-output.stdout-reservations");
|
|
3182
|
+
function stdoutReservations() {
|
|
3183
|
+
const holder = globalThis;
|
|
3184
|
+
holder[STDOUT_RESERVATIONS] ??= { count: 0 };
|
|
3185
|
+
return holder[STDOUT_RESERVATIONS];
|
|
3186
|
+
}
|
|
3187
|
+
function isGitHubActions(env = process.env) {
|
|
3188
|
+
const value = env.GITHUB_ACTIONS;
|
|
3189
|
+
return value !== void 0 && value !== "" && value.toLowerCase() !== "false" && value !== "0";
|
|
3190
|
+
}
|
|
3191
|
+
function guardRunText(text, env = process.env) {
|
|
3192
|
+
if (!isGitHubActions(env)) return text;
|
|
3193
|
+
const token = randomBytes2(16).toString("hex");
|
|
3194
|
+
const block = text.endsWith("\n") ? text : `${text}
|
|
3195
|
+
`;
|
|
3196
|
+
return `::stop-commands::${token}
|
|
3197
|
+
${block}::${token}::
|
|
3198
|
+
`;
|
|
3199
|
+
}
|
|
3200
|
+
function guardJsonText(serialized, env = process.env) {
|
|
3201
|
+
return isGitHubActions(env) ? serialized.replaceAll("#", "\\u0023") : serialized;
|
|
3202
|
+
}
|
|
3203
|
+
function reserveStdoutForData() {
|
|
3204
|
+
const reservations = stdoutReservations();
|
|
3205
|
+
reservations.count += 1;
|
|
3206
|
+
let released = false;
|
|
3207
|
+
return () => {
|
|
3208
|
+
if (released) return;
|
|
3209
|
+
released = true;
|
|
3210
|
+
reservations.count -= 1;
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
function printRunText(text, print = (line) => console.log(line), options = {}) {
|
|
3214
|
+
const env = options.env ?? process.env;
|
|
3215
|
+
if (!isGitHubActions(env)) {
|
|
3216
|
+
print(text);
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
const block = guardRunText(text, env);
|
|
3220
|
+
if (options.stdoutIsData ?? stdoutReservations().count > 0) {
|
|
3221
|
+
process.stderr.write(block);
|
|
3222
|
+
return;
|
|
3223
|
+
}
|
|
3224
|
+
process.stdout.write(block);
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3201
3227
|
// src/logging/log-manager.ts
|
|
3202
3228
|
import { redactSecretValue } from "@vostride/agent-qa-runner";
|
|
3203
3229
|
var MemberLogScopeUnavailableError = class extends Error {
|
|
3204
3230
|
constructor() {
|
|
3205
3231
|
super(
|
|
3206
|
-
"LogManager.runInMemberScope requires a memberScope store. Construct the manager with `memberScope: new AsyncLocalStorage()` \u2014 without it, concurrent members would silently share one run id
|
|
3232
|
+
"LogManager.runInMemberScope requires a memberScope store. Construct the manager with `memberScope: new AsyncLocalStorage()` \u2014 without it, concurrent members would silently share one run id."
|
|
3207
3233
|
);
|
|
3208
3234
|
this.name = "MemberLogScopeUnavailableError";
|
|
3209
3235
|
}
|
|
@@ -3218,6 +3244,7 @@ var LogManager = class {
|
|
|
3218
3244
|
ndjson;
|
|
3219
3245
|
redactor;
|
|
3220
3246
|
memberScope;
|
|
3247
|
+
env;
|
|
3221
3248
|
constructor(opts) {
|
|
3222
3249
|
this.runId = opts.runId ?? "";
|
|
3223
3250
|
this.runIdSet = !!opts.runId;
|
|
@@ -3226,21 +3253,12 @@ var LogManager = class {
|
|
|
3226
3253
|
this.ndjson = opts.ndjson ?? false;
|
|
3227
3254
|
this.redactor = opts.redactor;
|
|
3228
3255
|
this.memberScope = opts.memberScope;
|
|
3256
|
+
this.env = opts.env;
|
|
3229
3257
|
}
|
|
3230
3258
|
setRedactor(redactor) {
|
|
3231
3259
|
this.redactor = redactor;
|
|
3232
3260
|
}
|
|
3233
|
-
|
|
3234
|
-
* Run `fn` with its own run-id binding, so concurrent members cannot overwrite each other.
|
|
3235
|
-
*
|
|
3236
|
-
* ONE `LogManager` SERVES EVERY MEMBER of a run — `commands/run.ts` builds it once, outside the
|
|
3237
|
-
* per-member closure — and since 402-02/402-03 the direct path runs members concurrently. Without
|
|
3238
|
-
* a scope, `setRunId` is a single mutable field plus a whole-buffer backfill, so the LAST member
|
|
3239
|
-
* to mint an id claims every line in the buffer, its siblings' included (measured, not inferred).
|
|
3240
|
-
*
|
|
3241
|
-
* Outside a scope nothing changes: `setRunId` keeps its original whole-buffer behaviour, which is
|
|
3242
|
-
* what the sequential path and the run-level `onRunCreated` stamp rely on.
|
|
3243
|
-
*/
|
|
3261
|
+
// One manager serves every concurrent member; outside a scope `setRunId` backfills the buffer.
|
|
3244
3262
|
runInMemberScope(fn) {
|
|
3245
3263
|
if (!this.memberScope) throw new MemberLogScopeUnavailableError();
|
|
3246
3264
|
return this.memberScope.run({ runId: this.runId, pending: [] }, fn);
|
|
@@ -3260,7 +3278,14 @@ var LogManager = class {
|
|
|
3260
3278
|
this.buffer.push(entry);
|
|
3261
3279
|
if (scope && !entry.runId) scope.pending.push(entry);
|
|
3262
3280
|
if (this.shouldDisplay(level)) {
|
|
3263
|
-
|
|
3281
|
+
printRunText(
|
|
3282
|
+
formatLogEntry(entry),
|
|
3283
|
+
(line) => {
|
|
3284
|
+
process.stderr.write(`${line}
|
|
3285
|
+
`);
|
|
3286
|
+
},
|
|
3287
|
+
{ env: this.env }
|
|
3288
|
+
);
|
|
3264
3289
|
}
|
|
3265
3290
|
if (this.ndjson) {
|
|
3266
3291
|
this.emitNdjson(entry);
|
|
@@ -3281,7 +3306,7 @@ var LogManager = class {
|
|
|
3281
3306
|
*/
|
|
3282
3307
|
emitNdjson(entry) {
|
|
3283
3308
|
process.stdout.write(
|
|
3284
|
-
`AGENT_QA_EVENT:${JSON.stringify({ type: "log", ...entry })}
|
|
3309
|
+
`AGENT_QA_EVENT:${guardJsonText(JSON.stringify({ type: "log", ...entry }), this.env)}
|
|
3285
3310
|
`
|
|
3286
3311
|
);
|
|
3287
3312
|
}
|
|
@@ -3329,263 +3354,6 @@ var LogManager = class {
|
|
|
3329
3354
|
}
|
|
3330
3355
|
};
|
|
3331
3356
|
|
|
3332
|
-
// src/farm/credentials.ts
|
|
3333
|
-
var ENV_MAP = {
|
|
3334
|
-
browserstack: {
|
|
3335
|
-
username: "BROWSERSTACK_USERNAME",
|
|
3336
|
-
accessKey: "BROWSERSTACK_ACCESS_KEY"
|
|
3337
|
-
}
|
|
3338
|
-
};
|
|
3339
|
-
var DISPLAY_NAMES = {
|
|
3340
|
-
browserstack: "BrowserStack"
|
|
3341
|
-
};
|
|
3342
|
-
function resolveFarmCredentials(provider, providerConfig) {
|
|
3343
|
-
const envKeys = ENV_MAP[provider];
|
|
3344
|
-
if (!envKeys) {
|
|
3345
|
-
throw new Error(`Unknown farm provider: ${provider}`);
|
|
3346
|
-
}
|
|
3347
|
-
const envUsername = process.env[envKeys.username];
|
|
3348
|
-
const envAccessKey = process.env[envKeys.accessKey];
|
|
3349
|
-
if (envUsername && envAccessKey) {
|
|
3350
|
-
return { username: envUsername, accessKey: envAccessKey };
|
|
3351
|
-
}
|
|
3352
|
-
if (providerConfig?.username && providerConfig?.accessKey) {
|
|
3353
|
-
return { username: providerConfig.username, accessKey: providerConfig.accessKey };
|
|
3354
|
-
}
|
|
3355
|
-
const displayName = DISPLAY_NAMES[provider] ?? provider;
|
|
3356
|
-
throw new Error(
|
|
3357
|
-
`${displayName} credentials not found. Set ${envKeys.username} and ${envKeys.accessKey} env vars, or add provider credentials to agent-qa.local.yaml.`
|
|
3358
|
-
);
|
|
3359
|
-
}
|
|
3360
|
-
|
|
3361
|
-
// src/farm/capability-mapper.ts
|
|
3362
|
-
import { createRequire as createRequire2 } from "module";
|
|
3363
|
-
var BROWSER_MAP = {
|
|
3364
|
-
chromium: "playwright-chromium",
|
|
3365
|
-
firefox: "playwright-firefox",
|
|
3366
|
-
webkit: "playwright-webkit"
|
|
3367
|
-
};
|
|
3368
|
-
function detectPlaywrightVersion() {
|
|
3369
|
-
try {
|
|
3370
|
-
const require4 = createRequire2(typeof __filename === "string" ? __filename : import.meta.url);
|
|
3371
|
-
const pkg = require4("playwright-core/package.json");
|
|
3372
|
-
return pkg.version;
|
|
3373
|
-
} catch {
|
|
3374
|
-
return "1.59.1";
|
|
3375
|
-
}
|
|
3376
|
-
}
|
|
3377
|
-
function mapWebCapabilities(browser, credentials, opts) {
|
|
3378
|
-
if (!browser.viewport) {
|
|
3379
|
-
throw new Error("Farm mode requires browser.viewport to be set (e.g. { width: 1280, height: 720 })");
|
|
3380
|
-
}
|
|
3381
|
-
const bstackBrowser = BROWSER_MAP[browser.name];
|
|
3382
|
-
if (!bstackBrowser) {
|
|
3383
|
-
throw new Error(`Unsupported browser for BrowserStack: "${browser.name}". Supported: ${Object.keys(BROWSER_MAP).join(", ")}`);
|
|
3384
|
-
}
|
|
3385
|
-
return {
|
|
3386
|
-
browser: bstackBrowser,
|
|
3387
|
-
os: "os x",
|
|
3388
|
-
os_version: "sonoma",
|
|
3389
|
-
resolution: "1920x1080",
|
|
3390
|
-
"browserstack.username": credentials.username,
|
|
3391
|
-
"browserstack.accessKey": credentials.accessKey,
|
|
3392
|
-
"client.playwrightVersion": opts?.playwrightVersion ?? detectPlaywrightVersion(),
|
|
3393
|
-
project: "agent-qa",
|
|
3394
|
-
build: "agent-qa-" + Date.now(),
|
|
3395
|
-
name: opts?.testName
|
|
3396
|
-
};
|
|
3397
|
-
}
|
|
3398
|
-
function mapMobileCapabilities(match, platform, credentials, opts) {
|
|
3399
|
-
if (!opts?.testTimeout) {
|
|
3400
|
-
throw new Error("Farm mode requires timeout.test to be set in config");
|
|
3401
|
-
}
|
|
3402
|
-
return {
|
|
3403
|
-
"bstack:options": {
|
|
3404
|
-
userName: credentials.username,
|
|
3405
|
-
accessKey: credentials.accessKey,
|
|
3406
|
-
deviceName: match.deviceName,
|
|
3407
|
-
osVersion: match.platformVersion,
|
|
3408
|
-
projectName: "agent-qa",
|
|
3409
|
-
buildName: "agent-qa-" + Date.now(),
|
|
3410
|
-
sessionName: opts.testName,
|
|
3411
|
-
idleTimeout: Math.ceil(opts.testTimeout / 1e3)
|
|
3412
|
-
}
|
|
3413
|
-
};
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
// src/farm/browserstack.ts
|
|
3417
|
-
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
3418
|
-
import { basename as basename2, resolve as resolvePath2 } from "path";
|
|
3419
|
-
function isAbsoluteAppValue(value) {
|
|
3420
|
-
return value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
3421
|
-
}
|
|
3422
|
-
function isExplicitRelativePath(value) {
|
|
3423
|
-
return value.startsWith("./") || value.startsWith("../");
|
|
3424
|
-
}
|
|
3425
|
-
async function resolveBrowserStackApp(config) {
|
|
3426
|
-
if (!config.app) return void 0;
|
|
3427
|
-
if (isAbsoluteAppValue(config.app)) {
|
|
3428
|
-
throw new MobileSetupError({
|
|
3429
|
-
category: "app-install",
|
|
3430
|
-
message: `BrowserStack app value must be a BrowserStack reference or relative upload path: ${config.app}`,
|
|
3431
|
-
platform: config.platform,
|
|
3432
|
-
appId: config.app,
|
|
3433
|
-
sourceTrace: config.appSourceTrace
|
|
3434
|
-
});
|
|
3435
|
-
}
|
|
3436
|
-
const appBaseDir = config.appBaseDir ?? process.cwd();
|
|
3437
|
-
const resolvedPath = resolvePath2(appBaseDir, config.app);
|
|
3438
|
-
const mustExist = isExplicitRelativePath(config.app);
|
|
3439
|
-
if (!existsSync5(resolvedPath)) {
|
|
3440
|
-
if (mustExist) {
|
|
3441
|
-
throw new MobileSetupError({
|
|
3442
|
-
category: "app-install",
|
|
3443
|
-
message: `Configured BrowserStack app upload path not found: ${resolvedPath}`,
|
|
3444
|
-
platform: config.platform,
|
|
3445
|
-
appId: config.app,
|
|
3446
|
-
sourceTrace: config.appSourceTrace
|
|
3447
|
-
});
|
|
3448
|
-
}
|
|
3449
|
-
return config.app;
|
|
3450
|
-
}
|
|
3451
|
-
const auth = Buffer.from(`${config.credentials.username}:${config.credentials.accessKey}`).toString("base64");
|
|
3452
|
-
const form = new FormData();
|
|
3453
|
-
form.append("file", new Blob([readFileSync2(resolvedPath)]), basename2(resolvedPath));
|
|
3454
|
-
let res;
|
|
3455
|
-
try {
|
|
3456
|
-
res = await fetch("https://api-cloud.browserstack.com/app-automate/upload", {
|
|
3457
|
-
method: "POST",
|
|
3458
|
-
headers: { Authorization: `Basic ${auth}` },
|
|
3459
|
-
body: form
|
|
3460
|
-
});
|
|
3461
|
-
} catch (err) {
|
|
3462
|
-
throw new MobileSetupError({
|
|
3463
|
-
category: "app-install",
|
|
3464
|
-
message: `Failed to upload BrowserStack app: ${err instanceof Error ? err.message : String(err)}`,
|
|
3465
|
-
platform: config.platform,
|
|
3466
|
-
appId: config.app,
|
|
3467
|
-
sourceTrace: config.appSourceTrace,
|
|
3468
|
-
cause: err
|
|
3469
|
-
});
|
|
3470
|
-
}
|
|
3471
|
-
if (!res.ok) {
|
|
3472
|
-
throw new MobileSetupError({
|
|
3473
|
-
category: "app-install",
|
|
3474
|
-
message: `Failed to upload BrowserStack app: HTTP ${res.status}`,
|
|
3475
|
-
platform: config.platform,
|
|
3476
|
-
appId: config.app,
|
|
3477
|
-
sourceTrace: config.appSourceTrace
|
|
3478
|
-
});
|
|
3479
|
-
}
|
|
3480
|
-
const body = await res.json();
|
|
3481
|
-
if (!body.app_url) {
|
|
3482
|
-
throw new MobileSetupError({
|
|
3483
|
-
category: "app-install",
|
|
3484
|
-
message: "Failed to upload BrowserStack app: response missing app_url",
|
|
3485
|
-
platform: config.platform,
|
|
3486
|
-
appId: config.app,
|
|
3487
|
-
sourceTrace: config.appSourceTrace
|
|
3488
|
-
});
|
|
3489
|
-
}
|
|
3490
|
-
return body.app_url;
|
|
3491
|
-
}
|
|
3492
|
-
var browserstackProvider = {
|
|
3493
|
-
name: "BrowserStack",
|
|
3494
|
-
slug: "browserstack",
|
|
3495
|
-
async resolveWebEndpoint(config) {
|
|
3496
|
-
const caps = mapWebCapabilities(config.browser, config.credentials, {
|
|
3497
|
-
testName: config.testName,
|
|
3498
|
-
playwrightVersion: config.playwrightVersion
|
|
3499
|
-
});
|
|
3500
|
-
const wsEndpoint = `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify(caps))}`;
|
|
3501
|
-
return { wsEndpoint, capabilities: caps };
|
|
3502
|
-
},
|
|
3503
|
-
async resolveMobileCapabilities(config) {
|
|
3504
|
-
const bstackCaps = mapMobileCapabilities(config.match, config.platform, config.credentials, {
|
|
3505
|
-
testName: config.testName,
|
|
3506
|
-
testTimeout: config.testTimeout
|
|
3507
|
-
});
|
|
3508
|
-
const automationName = config.match.automationName ?? (config.platform === "android" ? "UiAutomator2" : "XCUITest");
|
|
3509
|
-
const platformName = config.platform === "android" ? "Android" : "iOS";
|
|
3510
|
-
const capabilities = {
|
|
3511
|
-
platformName,
|
|
3512
|
-
"appium:automationName": automationName
|
|
3513
|
-
};
|
|
3514
|
-
for (const [key, value] of Object.entries(config.match)) {
|
|
3515
|
-
if (key !== "automationName" && key !== "noReset") {
|
|
3516
|
-
capabilities[`appium:${key}`] = value;
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
Object.assign(capabilities, bstackCaps);
|
|
3520
|
-
const browserName = typeof config.match.browserName === "string" ? config.match.browserName.trim() : "";
|
|
3521
|
-
const appStateNoReset = config.appState === "preserve" ? true : config.appState === "reset" ? false : void 0;
|
|
3522
|
-
if (!browserName && appStateNoReset !== void 0) {
|
|
3523
|
-
capabilities["appium:noReset"] = appStateNoReset;
|
|
3524
|
-
}
|
|
3525
|
-
const resolvedAppValue = await resolveBrowserStackApp(config);
|
|
3526
|
-
if (resolvedAppValue) {
|
|
3527
|
-
capabilities["appium:app"] = resolvedAppValue;
|
|
3528
|
-
}
|
|
3529
|
-
return {
|
|
3530
|
-
hostname: "hub.browserstack.com",
|
|
3531
|
-
port: 443,
|
|
3532
|
-
path: "/wd/hub",
|
|
3533
|
-
capabilities
|
|
3534
|
-
};
|
|
3535
|
-
},
|
|
3536
|
-
resolveCredentials(providerConfig) {
|
|
3537
|
-
return resolveFarmCredentials("browserstack", providerConfig);
|
|
3538
|
-
},
|
|
3539
|
-
async validateDevice(deviceName, platform) {
|
|
3540
|
-
let credentials;
|
|
3541
|
-
try {
|
|
3542
|
-
credentials = resolveFarmCredentials("browserstack");
|
|
3543
|
-
} catch {
|
|
3544
|
-
return { valid: false, suggestions: ["Could not validate \u2014 credentials not configured"] };
|
|
3545
|
-
}
|
|
3546
|
-
const auth = Buffer.from(`${credentials.username}:${credentials.accessKey}`).toString("base64");
|
|
3547
|
-
try {
|
|
3548
|
-
const res = await fetch("https://api-cloud.browserstack.com/app-automate/devices.json", {
|
|
3549
|
-
headers: { Authorization: `Basic ${auth}` }
|
|
3550
|
-
});
|
|
3551
|
-
if (!res.ok) {
|
|
3552
|
-
return { valid: false, suggestions: ["Could not validate \u2014 API returned " + res.status] };
|
|
3553
|
-
}
|
|
3554
|
-
const devices = await res.json();
|
|
3555
|
-
const targetOs = platform === "android" ? "android" : "ios";
|
|
3556
|
-
const match = devices.find(
|
|
3557
|
-
(d) => d.device.toLowerCase() === deviceName.toLowerCase() && d.os.toLowerCase() === targetOs
|
|
3558
|
-
);
|
|
3559
|
-
if (match) return { valid: true };
|
|
3560
|
-
const similar = devices.filter((d) => d.os.toLowerCase() === targetOs).filter((d) => d.device.toLowerCase().includes(deviceName.toLowerCase().split(" ")[0])).map((d) => d.device).slice(0, 5);
|
|
3561
|
-
return { valid: false, suggestions: similar.length > 0 ? similar : void 0 };
|
|
3562
|
-
} catch {
|
|
3563
|
-
return { valid: false, suggestions: ["Could not validate \u2014 network error"] };
|
|
3564
|
-
}
|
|
3565
|
-
},
|
|
3566
|
-
getSessionUrl(sessionId, type) {
|
|
3567
|
-
if (type === "web") {
|
|
3568
|
-
return `https://automate.browserstack.com/sessions/${sessionId}`;
|
|
3569
|
-
}
|
|
3570
|
-
return `https://app-automate.browserstack.com/sessions/${sessionId}`;
|
|
3571
|
-
}
|
|
3572
|
-
};
|
|
3573
|
-
|
|
3574
|
-
// src/farm/registry.ts
|
|
3575
|
-
var providers = /* @__PURE__ */ new Map();
|
|
3576
|
-
function registerProvider(provider) {
|
|
3577
|
-
providers.set(provider.slug, provider);
|
|
3578
|
-
}
|
|
3579
|
-
function getProvider(slug) {
|
|
3580
|
-
return providers.get(slug);
|
|
3581
|
-
}
|
|
3582
|
-
function listProviders() {
|
|
3583
|
-
return Array.from(providers.values());
|
|
3584
|
-
}
|
|
3585
|
-
function registerAllProviders() {
|
|
3586
|
-
registerProvider(browserstackProvider);
|
|
3587
|
-
}
|
|
3588
|
-
|
|
3589
3357
|
// src/suite/member.ts
|
|
3590
3358
|
function isPlainObject2(val) {
|
|
3591
3359
|
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
@@ -3621,7 +3389,7 @@ function patchTestForSuite(test, suite, _index) {
|
|
|
3621
3389
|
|
|
3622
3390
|
// src/validation/validate.ts
|
|
3623
3391
|
import { readFile as readFile8 } from "fs/promises";
|
|
3624
|
-
import { existsSync as
|
|
3392
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3625
3393
|
import path6 from "path";
|
|
3626
3394
|
import { parse as parseYaml3, LineCounter as LineCounter2, parseDocument } from "yaml";
|
|
3627
3395
|
import { glob as glob2 } from "glob";
|
|
@@ -3670,7 +3438,7 @@ function isPathInsideDir(childPath, parentDir) {
|
|
|
3670
3438
|
}
|
|
3671
3439
|
|
|
3672
3440
|
// src/workspace/workspace-paths.ts
|
|
3673
|
-
import { existsSync as
|
|
3441
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3674
3442
|
import path5 from "path";
|
|
3675
3443
|
import { glob } from "glob";
|
|
3676
3444
|
function toForwardSlash(value) {
|
|
@@ -3785,7 +3553,7 @@ function resolveWorkspacePaths(input) {
|
|
|
3785
3553
|
["workspace.envFile", resolved.envFile],
|
|
3786
3554
|
["workspace.secretsFile", resolved.secretsFile]
|
|
3787
3555
|
]) {
|
|
3788
|
-
if (!
|
|
3556
|
+
if (!existsSync5(file.absolutePath)) {
|
|
3789
3557
|
throw new Error(`Configured workspace file not found: ${key} -> ${file.absolutePath}`);
|
|
3790
3558
|
}
|
|
3791
3559
|
}
|
|
@@ -3816,7 +3584,7 @@ async function resolveWorkspaceFileTarget(input) {
|
|
|
3816
3584
|
})) {
|
|
3817
3585
|
throw new Error(`Workspace ${input.kind} file is not matched by configured workspace patterns: ${workspaceRelativePath}`);
|
|
3818
3586
|
}
|
|
3819
|
-
if (input.requireExisting && !
|
|
3587
|
+
if (input.requireExisting && !existsSync5(absolutePath)) {
|
|
3820
3588
|
throw new Error(`Workspace ${input.kind} file not found: ${workspaceRelativePath}`);
|
|
3821
3589
|
}
|
|
3822
3590
|
return {
|
|
@@ -3938,7 +3706,7 @@ function validateSuiteFile(filePath, content, basedir) {
|
|
|
3938
3706
|
continue;
|
|
3939
3707
|
}
|
|
3940
3708
|
const resolved = path6.resolve(basedir, testPath);
|
|
3941
|
-
if (!
|
|
3709
|
+
if (!existsSync6(resolved)) {
|
|
3942
3710
|
let line = 1;
|
|
3943
3711
|
let column = 1;
|
|
3944
3712
|
const seqNode = testsNode;
|
|
@@ -3959,13 +3727,13 @@ function validateSuiteFile(filePath, content, basedir) {
|
|
|
3959
3727
|
return diagnostics;
|
|
3960
3728
|
}
|
|
3961
3729
|
function validateFilename(filePath) {
|
|
3962
|
-
const
|
|
3963
|
-
if (!VALID_FILENAME_RE.test(
|
|
3730
|
+
const basename2 = path6.basename(filePath);
|
|
3731
|
+
if (!VALID_FILENAME_RE.test(basename2)) {
|
|
3964
3732
|
return [{
|
|
3965
3733
|
file: filePath,
|
|
3966
3734
|
line: 1,
|
|
3967
3735
|
column: 1,
|
|
3968
|
-
message: `Invalid filename "${
|
|
3736
|
+
message: `Invalid filename "${basename2}" \u2014 expected pattern: [a-zA-Z0-9._-]+.(suite.)?(yaml|yml)`,
|
|
3969
3737
|
severity: "warning"
|
|
3970
3738
|
}];
|
|
3971
3739
|
}
|
|
@@ -3973,7 +3741,7 @@ function validateFilename(filePath) {
|
|
|
3973
3741
|
}
|
|
3974
3742
|
async function validateHooksFile(hooksFilePath, options = {}) {
|
|
3975
3743
|
const diagnostics = [];
|
|
3976
|
-
if (!
|
|
3744
|
+
if (!existsSync6(hooksFilePath)) {
|
|
3977
3745
|
if (options.required) {
|
|
3978
3746
|
return [buildDiagnostic(hooksFilePath, `Configured hooks file not found: ${hooksFilePath}`)];
|
|
3979
3747
|
}
|
|
@@ -3993,7 +3761,7 @@ async function validateHooksFile(hooksFilePath, options = {}) {
|
|
|
3993
3761
|
return diagnostics;
|
|
3994
3762
|
}
|
|
3995
3763
|
for (const hook of hooks) {
|
|
3996
|
-
if (!
|
|
3764
|
+
if (!existsSync6(hook.file)) {
|
|
3997
3765
|
diagnostics.push({
|
|
3998
3766
|
file: hooksFilePath,
|
|
3999
3767
|
line: 1,
|
|
@@ -4003,7 +3771,7 @@ async function validateHooksFile(hooksFilePath, options = {}) {
|
|
|
4003
3771
|
});
|
|
4004
3772
|
}
|
|
4005
3773
|
for (const dep of hook.deps) {
|
|
4006
|
-
if (!
|
|
3774
|
+
if (!existsSync6(dep)) {
|
|
4007
3775
|
diagnostics.push({
|
|
4008
3776
|
file: hooksFilePath,
|
|
4009
3777
|
line: 1,
|
|
@@ -4013,7 +3781,7 @@ async function validateHooksFile(hooksFilePath, options = {}) {
|
|
|
4013
3781
|
});
|
|
4014
3782
|
}
|
|
4015
3783
|
}
|
|
4016
|
-
if (hook.packageFile && !
|
|
3784
|
+
if (hook.packageFile && !existsSync6(hook.packageFile)) {
|
|
4017
3785
|
diagnostics.push({
|
|
4018
3786
|
file: hooksFilePath,
|
|
4019
3787
|
line: 1,
|
|
@@ -4127,7 +3895,7 @@ function validateRequiredWorkspaceFiles(workspace) {
|
|
|
4127
3895
|
["workspace.envFile", workspace.envFile],
|
|
4128
3896
|
["workspace.secretsFile", workspace.secretsFile]
|
|
4129
3897
|
]) {
|
|
4130
|
-
if (!
|
|
3898
|
+
if (!existsSync6(file.absolutePath)) {
|
|
4131
3899
|
diagnostics.push(buildDiagnostic(file.absolutePath, `Configured workspace file not found: ${key} -> ${file.absolutePath}`));
|
|
4132
3900
|
}
|
|
4133
3901
|
}
|
|
@@ -4188,7 +3956,7 @@ async function validateProject(options = {}) {
|
|
|
4188
3956
|
if (d.severity === "error") result.errorCount++;
|
|
4189
3957
|
else result.warningCount++;
|
|
4190
3958
|
}
|
|
4191
|
-
if (hooksFilePath && hooksDiags.length === 0 &&
|
|
3959
|
+
if (hooksFilePath && hooksDiags.length === 0 && existsSync6(hooksFilePath)) {
|
|
4192
3960
|
const { hooks } = await parseHooksFile(hooksFilePath);
|
|
4193
3961
|
const hookIds = new Set(hooks.map((h) => h.id));
|
|
4194
3962
|
for (const filePath of filePaths) {
|
|
@@ -4293,7 +4061,7 @@ function warnIfOutOfBounds(coords, viewport, actionType) {
|
|
|
4293
4061
|
}
|
|
4294
4062
|
|
|
4295
4063
|
// src/hooks/index.ts
|
|
4296
|
-
import { RUNTIME_IMAGE_MAP } from "@vostride/agent-qa-runner";
|
|
4064
|
+
import { RUNTIME_IMAGE_MAP, hookRunnerImage } from "@vostride/agent-qa-runner";
|
|
4297
4065
|
|
|
4298
4066
|
// src/ids/hook-id.ts
|
|
4299
4067
|
import { generateHookId as generateSharedHookId } from "@vostride/agent-qa-ids";
|
|
@@ -4317,8 +4085,6 @@ var ATTR_RUNNER = "agent-qa.runner";
|
|
|
4317
4085
|
var ATTR_GATE_RUN = "agent-qa.gate-run";
|
|
4318
4086
|
var RUN_ATTRIBUTE_KEY_MAX_LENGTH = 256;
|
|
4319
4087
|
var RUN_ATTRIBUTE_VALUE_MAX_LENGTH = 4096;
|
|
4320
|
-
var RUN_ATTRIBUTE_TRIGGERS = ["cli", "dashboard", "api", "mcp"];
|
|
4321
|
-
var RUN_ATTRIBUTE_RUNNERS = ["local", "browserstack"];
|
|
4322
4088
|
function isPlainRecord(value) {
|
|
4323
4089
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4324
4090
|
}
|
|
@@ -4333,43 +4099,6 @@ function assertValidUserAttributeKey(key, sourceLabel) {
|
|
|
4333
4099
|
throw new Error(`${sourceLabel}: Attribute key "${key}" exceeds ${RUN_ATTRIBUTE_KEY_MAX_LENGTH} characters`);
|
|
4334
4100
|
}
|
|
4335
4101
|
}
|
|
4336
|
-
function assertValidAttributeKeyShape(key, sourceLabel) {
|
|
4337
|
-
if (key.length === 0) {
|
|
4338
|
-
throw new Error(`${sourceLabel}: Attribute key must be non-empty`);
|
|
4339
|
-
}
|
|
4340
|
-
if (key.length > RUN_ATTRIBUTE_KEY_MAX_LENGTH) {
|
|
4341
|
-
throw new Error(`${sourceLabel}: Attribute key "${key}" exceeds ${RUN_ATTRIBUTE_KEY_MAX_LENGTH} characters`);
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
function assertValidAttributeValue(key, value, sourceLabel) {
|
|
4345
|
-
if (typeof value !== "string") {
|
|
4346
|
-
throw new Error(`${sourceLabel}: Attribute value for "${key}" must be a string`);
|
|
4347
|
-
}
|
|
4348
|
-
if (value.length > RUN_ATTRIBUTE_VALUE_MAX_LENGTH) {
|
|
4349
|
-
throw new Error(`${sourceLabel}: Attribute value for "${key}" exceeds ${RUN_ATTRIBUTE_VALUE_MAX_LENGTH} characters`);
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
function assertValidInternalAttribute(key, value, sourceLabel) {
|
|
4353
|
-
if (key === ATTR_TRIGGER) {
|
|
4354
|
-
if (!RUN_ATTRIBUTE_TRIGGERS.includes(value)) {
|
|
4355
|
-
throw new Error(`${sourceLabel}: Attribute value for "${key}" must be one of: ${RUN_ATTRIBUTE_TRIGGERS.join(", ")}`);
|
|
4356
|
-
}
|
|
4357
|
-
return;
|
|
4358
|
-
}
|
|
4359
|
-
if (key === ATTR_RUNNER) {
|
|
4360
|
-
if (!RUN_ATTRIBUTE_RUNNERS.includes(value)) {
|
|
4361
|
-
throw new Error(`${sourceLabel}: Attribute value for "${key}" must be one of: ${RUN_ATTRIBUTE_RUNNERS.join(", ")}`);
|
|
4362
|
-
}
|
|
4363
|
-
return;
|
|
4364
|
-
}
|
|
4365
|
-
if (key === ATTR_GATE_RUN) {
|
|
4366
|
-
if (value !== "true") {
|
|
4367
|
-
throw new Error(`${sourceLabel}: Attribute value for "${key}" must be "true"`);
|
|
4368
|
-
}
|
|
4369
|
-
return;
|
|
4370
|
-
}
|
|
4371
|
-
throw new Error(`${sourceLabel}: Attribute key "${key}" uses the reserved prefix "${INTERNAL_ATTRIBUTE_PREFIX}"`);
|
|
4372
|
-
}
|
|
4373
4102
|
function validateUserRunAttributes(input, sourceLabel = "run attributes") {
|
|
4374
4103
|
if (input === void 0 || input === null) return {};
|
|
4375
4104
|
if (!isPlainRecord(input)) {
|
|
@@ -4388,22 +4117,6 @@ function validateUserRunAttributes(input, sourceLabel = "run attributes") {
|
|
|
4388
4117
|
}
|
|
4389
4118
|
return attributes;
|
|
4390
4119
|
}
|
|
4391
|
-
function validateTrustedRunAttributes(input, sourceLabel = "run attributes") {
|
|
4392
|
-
if (input === void 0 || input === null) return {};
|
|
4393
|
-
if (!isPlainRecord(input)) {
|
|
4394
|
-
throw new Error(`${sourceLabel}: attributes must be a plain object`);
|
|
4395
|
-
}
|
|
4396
|
-
const attributes = /* @__PURE__ */ Object.create(null);
|
|
4397
|
-
for (const [key, value] of Object.entries(input)) {
|
|
4398
|
-
assertValidAttributeKeyShape(key, sourceLabel);
|
|
4399
|
-
assertValidAttributeValue(key, value, sourceLabel);
|
|
4400
|
-
if (key.startsWith(INTERNAL_ATTRIBUTE_PREFIX)) {
|
|
4401
|
-
assertValidInternalAttribute(key, value, sourceLabel);
|
|
4402
|
-
}
|
|
4403
|
-
attributes[key] = value;
|
|
4404
|
-
}
|
|
4405
|
-
return attributes;
|
|
4406
|
-
}
|
|
4407
4120
|
function parseRunAttrFlags(flags) {
|
|
4408
4121
|
const rawAttributes = /* @__PURE__ */ Object.create(null);
|
|
4409
4122
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4463,8 +4176,8 @@ function formatRunAttributesBlock(attributes) {
|
|
|
4463
4176
|
}
|
|
4464
4177
|
|
|
4465
4178
|
// src/version.ts
|
|
4466
|
-
import { createRequire as
|
|
4467
|
-
var require2 =
|
|
4179
|
+
import { createRequire as createRequire2 } from "module";
|
|
4180
|
+
var require2 = createRequire2(typeof __filename === "string" ? __filename : import.meta.url);
|
|
4468
4181
|
function readPackageVersion() {
|
|
4469
4182
|
try {
|
|
4470
4183
|
const manifest = require2("../package.json");
|
|
@@ -4482,10 +4195,10 @@ function getAgentQaVersion() {
|
|
|
4482
4195
|
|
|
4483
4196
|
// src/version-check/index.ts
|
|
4484
4197
|
import { chmod, mkdir as mkdir3, readFile as readFile9, writeFile as writeFile3 } from "fs/promises";
|
|
4485
|
-
import { createRequire as
|
|
4198
|
+
import { createRequire as createRequire3 } from "module";
|
|
4486
4199
|
import { homedir as homedir2 } from "os";
|
|
4487
4200
|
import { dirname as dirname7, join as join3 } from "path";
|
|
4488
|
-
var require3 =
|
|
4201
|
+
var require3 = createRequire3(typeof __filename === "string" ? __filename : import.meta.url);
|
|
4489
4202
|
var AGENT_QA_NPM_REGISTRY_URL = "https://registry.npmjs.org/agent-qa";
|
|
4490
4203
|
var VERSION_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4491
4204
|
var VERSION_CHECK_TIMEOUT_MS = 5e3;
|
|
@@ -4670,7 +4383,6 @@ export {
|
|
|
4670
4383
|
AUTH_STATE_SCHEMA_VERSION,
|
|
4671
4384
|
AUTH_STATE_SLUG_PATTERN,
|
|
4672
4385
|
AccessibilityConfigSchema,
|
|
4673
|
-
ActionPlanSchema,
|
|
4674
4386
|
AdapterLogDataSchema,
|
|
4675
4387
|
AgentLogDataSchema,
|
|
4676
4388
|
AgentQaConfigSchema,
|
|
@@ -4727,7 +4439,6 @@ export {
|
|
|
4727
4439
|
PlannerConfigSchema,
|
|
4728
4440
|
PlannerLogDataSchema,
|
|
4729
4441
|
PluginsSchema,
|
|
4730
|
-
ProviderConfigSchema,
|
|
4731
4442
|
ProviderPluginDeclarationSchema,
|
|
4732
4443
|
RUNTIME_IMAGE_MAP,
|
|
4733
4444
|
RUN_ATTRIBUTE_KEY_MAX_LENGTH,
|
|
@@ -4745,7 +4456,6 @@ export {
|
|
|
4745
4456
|
TestMetaSchema,
|
|
4746
4457
|
TestStepSchema,
|
|
4747
4458
|
TimeoutConfigSchema,
|
|
4748
|
-
TransportSchema,
|
|
4749
4459
|
UseSchema,
|
|
4750
4460
|
VALID_FILENAME_RE,
|
|
4751
4461
|
VERSION_CHECK_CACHE_TTL_MS,
|
|
@@ -4753,7 +4463,6 @@ export {
|
|
|
4753
4463
|
WEBVIEW_SCROLLERS_SCRIPT,
|
|
4754
4464
|
WorkspaceSchema,
|
|
4755
4465
|
applyPatchSetToTestDoc,
|
|
4756
|
-
browserstackProvider,
|
|
4757
4466
|
buildAuthStateHookEnv,
|
|
4758
4467
|
buildInternalRunAttributes,
|
|
4759
4468
|
buildRefEntries,
|
|
@@ -4766,7 +4475,6 @@ export {
|
|
|
4766
4475
|
createAuthFetch,
|
|
4767
4476
|
deriveMobileScroll,
|
|
4768
4477
|
detectGlobOverlap,
|
|
4769
|
-
detectPlaywrightVersion,
|
|
4770
4478
|
discoverWorkspaceFiles,
|
|
4771
4479
|
estimateTokens,
|
|
4772
4480
|
formatAppiumInstallGuidance,
|
|
@@ -4785,21 +4493,21 @@ export {
|
|
|
4785
4493
|
getAuthPath,
|
|
4786
4494
|
getCredential,
|
|
4787
4495
|
getLLMAuthProviderPlugin,
|
|
4788
|
-
|
|
4496
|
+
guardJsonText,
|
|
4497
|
+
guardRunText,
|
|
4498
|
+
hookRunnerImage,
|
|
4789
4499
|
isCanonicalRunId,
|
|
4500
|
+
isGitHubActions,
|
|
4790
4501
|
isLoopbackHost,
|
|
4791
4502
|
isPathInsideDir,
|
|
4792
4503
|
isReservedAuthStateHookEnvKey,
|
|
4793
4504
|
isWorkspacePathMatch,
|
|
4794
4505
|
listAuthStateMetadata,
|
|
4795
4506
|
listLLMAuthProviderPlugins,
|
|
4796
|
-
listProviders,
|
|
4797
4507
|
loadLLMAuthPlugins,
|
|
4798
4508
|
loadProviderPlugins,
|
|
4799
4509
|
localAppiumBinaryNames,
|
|
4800
4510
|
locateRunsDbPath,
|
|
4801
|
-
mapMobileCapabilities,
|
|
4802
|
-
mapWebCapabilities,
|
|
4803
4511
|
mergeRunAttributes,
|
|
4804
4512
|
normalizeAuthStateUse,
|
|
4805
4513
|
normalizeRole,
|
|
@@ -4814,21 +4522,21 @@ export {
|
|
|
4814
4522
|
parseTestFileWithTokens,
|
|
4815
4523
|
parseWebViewScrollers,
|
|
4816
4524
|
patchTestForSuite,
|
|
4525
|
+
printRunText,
|
|
4817
4526
|
readAuth,
|
|
4818
4527
|
readAuthStateMetadata,
|
|
4819
|
-
|
|
4528
|
+
refuseRetiredLocalBindings,
|
|
4820
4529
|
registerLLMAuthProviderPlugin,
|
|
4821
4530
|
registerLLMAuthProviderPlugins,
|
|
4822
|
-
registerProvider,
|
|
4823
4531
|
removeAuth,
|
|
4824
4532
|
removeAuthStateFiles,
|
|
4825
4533
|
removeAuthStateTarget,
|
|
4826
4534
|
renderPatchedTestDoc,
|
|
4535
|
+
reserveStdoutForData,
|
|
4827
4536
|
resolveAppiumExecutable,
|
|
4828
4537
|
resolveAuthStateForRun,
|
|
4829
4538
|
resolveAuthStatePaths,
|
|
4830
4539
|
resolveAuthStateRoot,
|
|
4831
|
-
resolveFarmCredentials,
|
|
4832
4540
|
resolveFileReferences,
|
|
4833
4541
|
resolveKnowledgeRoot,
|
|
4834
4542
|
resolveLLMAuth,
|
|
@@ -4844,7 +4552,6 @@ export {
|
|
|
4844
4552
|
validateProject,
|
|
4845
4553
|
validateSuiteFile,
|
|
4846
4554
|
validateTestFile,
|
|
4847
|
-
validateTrustedRunAttributes,
|
|
4848
4555
|
validateUserRunAttributes,
|
|
4849
4556
|
warnIfOutOfBounds,
|
|
4850
4557
|
webViewSwipeSignal,
|