@jevitate/cli 0.21.1 → 0.23.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.
- package/dist/bin.js +558 -101
- package/dist/build-info.generated.js +3 -3
- package/dist/check-api.js +35 -0
- package/dist/check-api.js.map +1 -1
- package/dist/check-types.d.ts +3 -0
- package/dist/check-types.js.map +1 -1
- package/dist/log-correlation.js +10 -2
- package/dist/log-correlation.js.map +1 -1
- package/dist/outcome-targets.d.ts +27 -0
- package/dist/outcome-targets.js +41 -0
- package/dist/outcome-targets.js.map +1 -0
- package/package.json +9 -9
package/dist/bin.js
CHANGED
|
@@ -35260,9 +35260,9 @@ async function confineFixture(raw, root2) {
|
|
|
35260
35260
|
}
|
|
35261
35261
|
if (!isFile)
|
|
35262
35262
|
throw fixtureNotFound(candidate, root2);
|
|
35263
|
-
const
|
|
35264
|
-
if (
|
|
35265
|
-
throw fixtureSensitive(candidate, root2,
|
|
35263
|
+
const sensitive2 = fixtureSensitiveReason(real, realRoot) ?? (isInsideRoot(candidate, resolve3(root2)) ? fixtureSensitiveReason(candidate, resolve3(root2)) : null);
|
|
35264
|
+
if (sensitive2 !== null)
|
|
35265
|
+
throw fixtureSensitive(candidate, root2, sensitive2);
|
|
35266
35266
|
return real;
|
|
35267
35267
|
}
|
|
35268
35268
|
function fixtureEscapesRoot(raw, root2) {
|
|
@@ -37855,6 +37855,7 @@ var MESSAGE_FIELD = /\b(reply|message|ask|chat|inquiry|prompt|say|talk|conversat
|
|
|
37855
37855
|
var MESSAGE_INPUT_TYPES = /* @__PURE__ */ new Set(["", "text", "search"]);
|
|
37856
37856
|
var SUBMIT_NAME = /\b(send|submit|reply|ask|post)\b|[→➤➔↑]|^\s*(go|ok)\s*$/i;
|
|
37857
37857
|
var PAIRED_SUBMIT_DISTANCE = 6;
|
|
37858
|
+
var RECIPIENT_NAME = /^\s*(to|cc|bcc)\b|\b(recipients?|contacts?|addressee|send to)\b/i;
|
|
37858
37859
|
function composerEvidence(node2, args) {
|
|
37859
37860
|
const el = node2;
|
|
37860
37861
|
const norm6 = (s3) => s3 === null ? "" : s3.replace(/\s+/g, " ").trim();
|
|
@@ -37886,6 +37887,29 @@ function composerEvidence(node2, args) {
|
|
|
37886
37887
|
parents.set(item.parentElement, n3);
|
|
37887
37888
|
}
|
|
37888
37889
|
const submit2 = new RegExp(args.submit, "i");
|
|
37890
|
+
const recipient = new RegExp(args.recipient, "i");
|
|
37891
|
+
const labelOf2 = (a) => {
|
|
37892
|
+
const aria = norm6(a.getAttribute("aria-label"));
|
|
37893
|
+
if (aria !== "")
|
|
37894
|
+
return aria;
|
|
37895
|
+
const by = norm6(a.getAttribute("aria-labelledby"));
|
|
37896
|
+
if (by !== "") {
|
|
37897
|
+
const text2 = by.split(" ").map((id2) => norm6(document.getElementById(id2)?.textContent ?? null)).join(" ").trim();
|
|
37898
|
+
if (text2 !== "")
|
|
37899
|
+
return text2;
|
|
37900
|
+
}
|
|
37901
|
+
const id = a.getAttribute("id");
|
|
37902
|
+
if (id !== null && id !== "") {
|
|
37903
|
+
const forLabel = Array.from(document.querySelectorAll("label")).find((l2) => l2.htmlFor === id);
|
|
37904
|
+
if (forLabel !== void 0)
|
|
37905
|
+
return norm6(forLabel.textContent);
|
|
37906
|
+
}
|
|
37907
|
+
const wrap = a.closest("label");
|
|
37908
|
+
if (wrap !== null)
|
|
37909
|
+
return norm6(wrap.textContent);
|
|
37910
|
+
return norm6(a.getAttribute("placeholder")) || norm6(a.getAttribute("name"));
|
|
37911
|
+
};
|
|
37912
|
+
const addressed = (container) => Array.from(container.querySelectorAll('select, input:not([type="hidden"]):not([type="submit"]):not([type="button"]), [role="combobox"], [role="listbox"]')).some((f2) => f2 !== el && !f2.contains(el) && rendered(f2) && recipient.test(labelOf2(f2)));
|
|
37889
37913
|
const up4 = (a) => {
|
|
37890
37914
|
const out = [];
|
|
37891
37915
|
for (let cur = a; cur !== null; cur = parentOf(cur))
|
|
@@ -37903,7 +37927,7 @@ function composerEvidence(node2, args) {
|
|
|
37903
37927
|
for (let i = 0; i < fieldPath.length && i <= args.distance; i++) {
|
|
37904
37928
|
const j = bp.indexOf(fieldPath[i]);
|
|
37905
37929
|
if (j >= 0) {
|
|
37906
|
-
if (i + j <= args.distance)
|
|
37930
|
+
if (i + j <= args.distance && !addressed(fieldPath[i]))
|
|
37907
37931
|
return true;
|
|
37908
37932
|
break;
|
|
37909
37933
|
}
|
|
@@ -38535,7 +38559,7 @@ async function readSnapshot(page, opts) {
|
|
|
38535
38559
|
const value = raw.valueBearing && !isSecretField(raw.inputType, raw.autocomplete, [raw.name]) ? await handle.evaluate(readControlValue) : null;
|
|
38536
38560
|
const facts = { ...raw, value };
|
|
38537
38561
|
const textEntry = raw.tag === "textarea" || raw.tag === "input" && MESSAGE_INPUT_TYPES.has(raw.inputType ?? "") || raw.role === "textbox";
|
|
38538
|
-
const conversational = textEntry && MESSAGE_FIELD.test(raw.name) ? await handle.evaluate(composerEvidence, { submit: SUBMIT_NAME.source, distance: PAIRED_SUBMIT_DISTANCE }).catch(() => false) : null;
|
|
38562
|
+
const conversational = textEntry && MESSAGE_FIELD.test(raw.name) ? await handle.evaluate(composerEvidence, { submit: SUBMIT_NAME.source, distance: PAIRED_SUBMIT_DISTANCE, recipient: RECIPIENT_NAME.source }).catch(() => false) : null;
|
|
38539
38563
|
const computed = await computeDescriptor(page, handle, { primaryOnly: true });
|
|
38540
38564
|
const control = {
|
|
38541
38565
|
index: controls.length,
|
|
@@ -39568,6 +39592,59 @@ async function clockBounded(work, ms, onTimeout) {
|
|
|
39568
39592
|
}
|
|
39569
39593
|
}
|
|
39570
39594
|
|
|
39595
|
+
// ../explore/dist/request-body-values.js
|
|
39596
|
+
var MIN_VALUE_CHARS = 3;
|
|
39597
|
+
var MAX_VALUES = 50;
|
|
39598
|
+
var MAX_BODY_CHARS = 64e3;
|
|
39599
|
+
var SENSITIVE_WORDS = /* @__PURE__ */ new Set(["pass", "password", "passwd", "pwd", "secret", "token", "auth", "authorization", "apikey", "key", "otp", "pin", "cvv", "cvc", "card", "ssn", "session", "cookie", "signature", "credential", "credentials"]);
|
|
39600
|
+
function sensitive(key2) {
|
|
39601
|
+
return key2.split(/[^A-Za-z0-9]+|(?<=[a-z0-9])(?=[A-Z])/).some((w) => SENSITIVE_WORDS.has(w.toLowerCase()));
|
|
39602
|
+
}
|
|
39603
|
+
function keep(out, key2, value) {
|
|
39604
|
+
if (out.length >= MAX_VALUES)
|
|
39605
|
+
return;
|
|
39606
|
+
if (key2 !== null && sensitive(key2))
|
|
39607
|
+
return;
|
|
39608
|
+
const v = value.replace(/\s+/g, " ").trim();
|
|
39609
|
+
if (v.length >= MIN_VALUE_CHARS)
|
|
39610
|
+
out.push(v);
|
|
39611
|
+
}
|
|
39612
|
+
function walk(out, key2, node2) {
|
|
39613
|
+
if (out.length >= MAX_VALUES)
|
|
39614
|
+
return;
|
|
39615
|
+
if (typeof node2 === "string")
|
|
39616
|
+
keep(out, key2, node2);
|
|
39617
|
+
else if (Array.isArray(node2))
|
|
39618
|
+
for (const item of node2)
|
|
39619
|
+
walk(out, key2, item);
|
|
39620
|
+
else if (node2 !== null && typeof node2 === "object") {
|
|
39621
|
+
for (const [k, v] of Object.entries(node2)) {
|
|
39622
|
+
if (sensitive(k))
|
|
39623
|
+
continue;
|
|
39624
|
+
walk(out, k, v);
|
|
39625
|
+
}
|
|
39626
|
+
}
|
|
39627
|
+
}
|
|
39628
|
+
function bodyValues(body, contentType) {
|
|
39629
|
+
if (body === null || body === "" || body.length > MAX_BODY_CHARS)
|
|
39630
|
+
return [];
|
|
39631
|
+
const type = (contentType ?? "").toLowerCase();
|
|
39632
|
+
const out = [];
|
|
39633
|
+
if (type.includes("application/x-www-form-urlencoded")) {
|
|
39634
|
+
for (const [k, v] of new URLSearchParams(body))
|
|
39635
|
+
keep(out, k, v);
|
|
39636
|
+
return out;
|
|
39637
|
+
}
|
|
39638
|
+
if (type === "" || type.includes("json")) {
|
|
39639
|
+
try {
|
|
39640
|
+
walk(out, null, JSON.parse(body));
|
|
39641
|
+
} catch {
|
|
39642
|
+
return [];
|
|
39643
|
+
}
|
|
39644
|
+
}
|
|
39645
|
+
return out;
|
|
39646
|
+
}
|
|
39647
|
+
|
|
39571
39648
|
// ../explore/dist/page-monitor.js
|
|
39572
39649
|
var RPC_TRAILER_READ_MS = 2e3;
|
|
39573
39650
|
var INTERACTIVE_SELECTOR2 = "a[href],button,input:not([type=hidden]),select,textarea,[role=button],[role=link],[role=textbox],[role=checkbox],[role=combobox],[contenteditable=true]";
|
|
@@ -39773,6 +39850,8 @@ function pathOf3(url2) {
|
|
|
39773
39850
|
return url2.split(/[?#]/)[0] ?? url2;
|
|
39774
39851
|
}
|
|
39775
39852
|
}
|
|
39853
|
+
var BODY_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
|
|
39854
|
+
var MAX_WRITE_BODIES = 200;
|
|
39776
39855
|
function requestContentTypeOf(r) {
|
|
39777
39856
|
try {
|
|
39778
39857
|
const v = r.headers()["content-type"];
|
|
@@ -39785,6 +39864,8 @@ var RECENT_REQUESTS_MS = 6e4;
|
|
|
39785
39864
|
var PageMonitor = class {
|
|
39786
39865
|
#page;
|
|
39787
39866
|
#inflight = /* @__PURE__ */ new Map();
|
|
39867
|
+
/** #649: the string values recent writes sent in their bodies (memory only; never persisted). */
|
|
39868
|
+
#writeBodies = [];
|
|
39788
39869
|
#completed = [];
|
|
39789
39870
|
#wakers = /* @__PURE__ */ new Set();
|
|
39790
39871
|
#statuses = /* @__PURE__ */ new WeakMap();
|
|
@@ -39822,6 +39903,18 @@ var PageMonitor = class {
|
|
|
39822
39903
|
startedAt,
|
|
39823
39904
|
...requestContentType2 === void 0 ? {} : { requestContentType: requestContentType2 }
|
|
39824
39905
|
});
|
|
39906
|
+
if (BODY_METHODS.has(r.method().toUpperCase())) {
|
|
39907
|
+
let values2 = [];
|
|
39908
|
+
try {
|
|
39909
|
+
values2 = bodyValues(r.postData(), requestContentType2);
|
|
39910
|
+
} catch {
|
|
39911
|
+
}
|
|
39912
|
+
if (values2.length > 0) {
|
|
39913
|
+
this.#writeBodies.push({ startedAt, url: r.url(), values: values2 });
|
|
39914
|
+
if (this.#writeBodies.length > MAX_WRITE_BODIES)
|
|
39915
|
+
this.#writeBodies.shift();
|
|
39916
|
+
}
|
|
39917
|
+
}
|
|
39825
39918
|
for (const c of this.#captures) {
|
|
39826
39919
|
c.began(r, {
|
|
39827
39920
|
method: r.method().toUpperCase(),
|
|
@@ -40073,6 +40166,10 @@ var PageMonitor = class {
|
|
|
40073
40166
|
clock.clearTimeout(timer);
|
|
40074
40167
|
}
|
|
40075
40168
|
}
|
|
40169
|
+
/** #649: the string values the writes started since `sinceMs` sent, from URLs `keep` accepts. */
|
|
40170
|
+
writeBodyValuesSince(sinceMs, keep2) {
|
|
40171
|
+
return this.#writeBodies.filter((w) => w.startedAt >= sinceMs && keep2(w.url)).flatMap((w) => w.values);
|
|
40172
|
+
}
|
|
40076
40173
|
/** Requests that ended since `sinceMs` (wall clock). */
|
|
40077
40174
|
completedSince(sinceMs) {
|
|
40078
40175
|
return this.#completed.filter((r) => r.endedAt >= sinceMs);
|
|
@@ -41105,8 +41202,8 @@ function boundCandidates(candidates, opts) {
|
|
|
41105
41202
|
return s3;
|
|
41106
41203
|
};
|
|
41107
41204
|
const ranked = candidates.map((c, i) => ({ c, i, s: score2(c, i) })).sort((a, b2) => b2.s - a.s || a.i - b2.i).slice(0, Math.max(0, opts.limit));
|
|
41108
|
-
const
|
|
41109
|
-
return { kept: candidates.filter((_, i) =>
|
|
41205
|
+
const keep2 = new Set(ranked.map((r) => r.i));
|
|
41206
|
+
return { kept: candidates.filter((_, i) => keep2.has(i)), omitted: candidates.length - keep2.size };
|
|
41110
41207
|
}
|
|
41111
41208
|
|
|
41112
41209
|
// ../explore/dist/stuck-actions.js
|
|
@@ -41391,7 +41488,8 @@ var SideEffectGuard = class {
|
|
|
41391
41488
|
const own2 = requests.filter((r) => this.#ours(r));
|
|
41392
41489
|
const settled = !inflightAny && own2.every((r) => r.status !== null && r.status >= 200 && r.status < 300);
|
|
41393
41490
|
const completedOk = own2.length > 0 && settled;
|
|
41394
|
-
|
|
41491
|
+
const sentValues = this.#monitor.writeBodyValuesSince(o.at, (url2) => this.#ours({ url: url2 }));
|
|
41492
|
+
this.#lastClick = { requestSent: requests.length > 0 || inflightAny, writes: [...done, ...pending], completedOk, settled, sentValues };
|
|
41395
41493
|
if (done.length + pending.length === 0)
|
|
41396
41494
|
return;
|
|
41397
41495
|
this.#fired.set(o.key, {
|
|
@@ -41897,6 +41995,7 @@ var UnsubmittedTypeTracker = class {
|
|
|
41897
41995
|
var GOAL_MET_THRESHOLD = 0.75;
|
|
41898
41996
|
var GOAL_CHECK_TRIGGER = 0.5;
|
|
41899
41997
|
var WRITES_DONE_NOTE = "the job's writes succeeded; the page shows no change (repeat data?)";
|
|
41998
|
+
var READ_BACK_DONE_NOTE = "the job's writes succeeded and a page loaded from the server shows what they sent";
|
|
41900
41999
|
function groundDone(e) {
|
|
41901
42000
|
if (e.unsubmitted.length > 0) {
|
|
41902
42001
|
return { accept: false, reason: `typed text in ${e.unsubmitted.map((l2) => `"${l2}"`).join(", ")} was never submitted` };
|
|
@@ -41922,6 +42021,9 @@ function groundDone(e) {
|
|
|
41922
42021
|
if (w !== void 0 && writesAllSucceeded(w) && w.goalIsWrites !== null && w.goalIsWrites >= GOAL_MET_THRESHOLD) {
|
|
41923
42022
|
return { accept: true, outcome: { status: "completed", verifiedBy: "succeeded-writes", note: WRITES_DONE_NOTE } };
|
|
41924
42023
|
}
|
|
42024
|
+
if (w !== void 0 && writesAllSucceeded(w) && e.readBack === true && p >= GOAL_CHECK_TRIGGER) {
|
|
42025
|
+
return { accept: true, outcome: { status: "completed", verifiedBy: "succeeded-writes", note: READ_BACK_DONE_NOTE } };
|
|
42026
|
+
}
|
|
41925
42027
|
const notMet = `the goal is not observably achieved on this page (p=${p.toFixed(2)})`;
|
|
41926
42028
|
if (w !== void 0 && writesAllSucceeded(w) && w.goalIsWrites !== null) {
|
|
41927
42029
|
return { accept: false, reason: `${notMet}; the job's writes succeeded, but they are not judged to be the whole goal (p=${w.goalIsWrites.toFixed(2)})` };
|
|
@@ -45461,6 +45563,19 @@ function pageStatusOf(ctx) {
|
|
|
45461
45563
|
const parts2 = [...isEmptyStatus(ctx.status) ? [] : [describeStatus(ctx.status)], ...[formMessageStatus(ctx)].filter((x) => x !== null)];
|
|
45462
45564
|
return parts2.length === 0 ? {} : { pageStatus: parts2.join("; ") };
|
|
45463
45565
|
}
|
|
45566
|
+
var DOCUMENT_LOADS = /* @__PURE__ */ new WeakMap();
|
|
45567
|
+
async function documentOf(page) {
|
|
45568
|
+
let loads = DOCUMENT_LOADS.get(page);
|
|
45569
|
+
if (loads === void 0) {
|
|
45570
|
+
const counter = { count: 0 };
|
|
45571
|
+
page.on("domcontentloaded", () => {
|
|
45572
|
+
counter.count += 1;
|
|
45573
|
+
});
|
|
45574
|
+
DOCUMENT_LOADS.set(page, counter);
|
|
45575
|
+
loads = counter;
|
|
45576
|
+
}
|
|
45577
|
+
return loads.count;
|
|
45578
|
+
}
|
|
45464
45579
|
|
|
45465
45580
|
// ../explore/dist/goal-loop/context/conversation.js
|
|
45466
45581
|
function initConversation(ctx, cfg) {
|
|
@@ -45485,6 +45600,7 @@ function initConversation(ctx, cfg) {
|
|
|
45485
45600
|
}
|
|
45486
45601
|
|
|
45487
45602
|
// ../explore/dist/answer.js
|
|
45603
|
+
var UNCONFIRMED_WRITE = "the quote shows what the run itself sent";
|
|
45488
45604
|
var ANSWER_PAGES_CHARS = 8e3;
|
|
45489
45605
|
var MAX_OBSERVED_PAGES = 30;
|
|
45490
45606
|
var OBSERVED_PAGE_CHARS = 2e4;
|
|
@@ -45526,9 +45642,94 @@ var ObservedPages = class {
|
|
|
45526
45642
|
if (v !== "")
|
|
45527
45643
|
this.#ownInputs.add(v);
|
|
45528
45644
|
}
|
|
45529
|
-
/**
|
|
45530
|
-
|
|
45645
|
+
/**
|
|
45646
|
+
* #647: values a 2xx write sent that no page loaded from the server has shown since. A 2xx says the
|
|
45647
|
+
* request was accepted, not that the app kept it: until a later document shows the value, a page
|
|
45648
|
+
* showing it may only be the client's own rendering of what the run sent.
|
|
45649
|
+
*/
|
|
45650
|
+
#unread = /* @__PURE__ */ new Set();
|
|
45651
|
+
/** #647: the document the last such write was sent from. */
|
|
45652
|
+
#writeDocument = null;
|
|
45653
|
+
/**
|
|
45654
|
+
* #239: a submit the run clicked fired writes that all succeeded (2xx): what it typed was sent. #647:
|
|
45655
|
+
* it is read back only when a page from a later document load (`document`) shows it.
|
|
45656
|
+
*/
|
|
45657
|
+
confirmOwnInputs(document2) {
|
|
45658
|
+
for (const v of this.#ownInputs)
|
|
45659
|
+
this.#unread.add(v);
|
|
45531
45660
|
this.#ownInputs.clear();
|
|
45661
|
+
if (document2 !== void 0)
|
|
45662
|
+
this.#writeDocument = document2;
|
|
45663
|
+
}
|
|
45664
|
+
/**
|
|
45665
|
+
* #649: a sent value the run did not type (a write's request body: a prefilled import's rows) that
|
|
45666
|
+
* a page on the write's own route already showed before the write — its source, not a copy the app
|
|
45667
|
+
* kept. Only a page on another route reads it back: reloading the source shows it either way.
|
|
45668
|
+
*/
|
|
45669
|
+
#sourceRoute = /* @__PURE__ */ new Map();
|
|
45670
|
+
/** #649: a write's body values the run did not type, decided on the next page seen (see `add`). */
|
|
45671
|
+
#pendingSent = null;
|
|
45672
|
+
/**
|
|
45673
|
+
* #649: a successful write the run fired sent `values` in its body, from a page on `url`. One the run
|
|
45674
|
+
* did not type awaits read-back like a typed one only when the next page shows it as NEW text — text
|
|
45675
|
+
* no earlier page on that route showed (an import's rows listed from its CSV; never a form's
|
|
45676
|
+
* untouched prefilled fields, still only in their fields). Decided when that page is added.
|
|
45677
|
+
*/
|
|
45678
|
+
noteSentValues(values2, url2) {
|
|
45679
|
+
const own2 = /* @__PURE__ */ new Set([...this.#ownInputs, ...this.#unread]);
|
|
45680
|
+
const fresh = values2.map((value) => fold(redactContext(value, this.secrets).slice(0, OBSERVED_FIELD_CHARS))).filter((v) => v !== "" && !own2.has(v));
|
|
45681
|
+
this.#pendingSent = fresh.length === 0 ? null : { route: routeOf(url2), values: fresh };
|
|
45682
|
+
}
|
|
45683
|
+
/** #649: the pending body values `text` (the page after the write) newly shows await read-back. */
|
|
45684
|
+
#settleSent(text2) {
|
|
45685
|
+
const pending = this.#pendingSent;
|
|
45686
|
+
if (pending === null)
|
|
45687
|
+
return;
|
|
45688
|
+
this.#pendingSent = null;
|
|
45689
|
+
const now2 = fold(text2);
|
|
45690
|
+
const before = this.#pages.filter((p) => routeOf(p.url) === pending.route);
|
|
45691
|
+
for (const v of pending.values) {
|
|
45692
|
+
if (!now2.includes(v) || before.some((p) => fold(p.text).includes(v)))
|
|
45693
|
+
continue;
|
|
45694
|
+
this.#unread.add(v);
|
|
45695
|
+
if (before.some((p) => (p.fields ?? []).some((f2) => fold(f2.value).includes(v))))
|
|
45696
|
+
this.#sourceRoute.set(v, pending.route);
|
|
45697
|
+
}
|
|
45698
|
+
}
|
|
45699
|
+
/** #647: the run's sent values (folded) that no page loaded from the server since has shown. */
|
|
45700
|
+
unreadWrites() {
|
|
45701
|
+
return this.#unread;
|
|
45702
|
+
}
|
|
45703
|
+
/** #647: the unread sent values (folded) that `shown` (a page's text, or field values) displays. */
|
|
45704
|
+
unreadShownIn(shown2) {
|
|
45705
|
+
const text2 = fold(shown2);
|
|
45706
|
+
return [...this.#unread].filter((v) => text2.includes(v));
|
|
45707
|
+
}
|
|
45708
|
+
/**
|
|
45709
|
+
* #647: a page from a later document load than the write shows `shown` (its visible text, or field
|
|
45710
|
+
* values): the sent values it contains are read back from the app. Returns those values.
|
|
45711
|
+
*/
|
|
45712
|
+
readBack(document2, shown2, url2) {
|
|
45713
|
+
if (this.#unread.size === 0 || document2 === void 0 || document2 === this.#writeDocument)
|
|
45714
|
+
return [];
|
|
45715
|
+
const text2 = fold(shown2);
|
|
45716
|
+
const route = url2 === void 0 ? void 0 : routeOf(url2);
|
|
45717
|
+
const back = [...this.#unread].filter((v) => text2.includes(v) && (route === void 0 || this.#sourceRoute.get(v) !== route));
|
|
45718
|
+
for (const v of back) {
|
|
45719
|
+
this.#unread.delete(v);
|
|
45720
|
+
this.#sourceRoute.delete(v);
|
|
45721
|
+
}
|
|
45722
|
+
this.#readBackCount += back.length;
|
|
45723
|
+
return back;
|
|
45724
|
+
}
|
|
45725
|
+
/** #645: how many sent values a page loaded from the server has shown again. */
|
|
45726
|
+
#readBackCount = 0;
|
|
45727
|
+
/**
|
|
45728
|
+
* #645: the app was seen to keep what the run's writes sent — at least one value was read back and
|
|
45729
|
+
* none is still unread. Code evidence the page judgment lacks (see `groundDone`).
|
|
45730
|
+
*/
|
|
45731
|
+
readBackConfirmed() {
|
|
45732
|
+
return this.#readBackCount > 0 && this.#unread.size === 0;
|
|
45532
45733
|
}
|
|
45533
45734
|
/** #239: the run's own typed, not-yet-saved values (folded). */
|
|
45534
45735
|
ownInputs() {
|
|
@@ -45542,9 +45743,9 @@ var ObservedPages = class {
|
|
|
45542
45743
|
const clip5 = (h) => redactContext((h ?? "").replace(/\s+/g, " ").trim(), this.secrets).slice(0, HEADING_CHARS);
|
|
45543
45744
|
const heading = clip5(headings2.heading);
|
|
45544
45745
|
const title = clip5(headings2.title);
|
|
45545
|
-
const
|
|
45546
|
-
const names =
|
|
45547
|
-
const links =
|
|
45746
|
+
const keep2 = (xs) => (xs ?? []).slice(0, MAX_OBSERVED_CONTROLS).map((n3) => redactContext(n3.replace(/\s+/g, " ").trim(), this.secrets).slice(0, OBSERVED_FIELD_CHARS)).filter((n3) => n3 !== "");
|
|
45747
|
+
const names = keep2(headings2.controlNames);
|
|
45748
|
+
const links = keep2(headings2.contentLinks);
|
|
45548
45749
|
const capped = (headings2.controlNames ?? []).length > MAX_OBSERVED_CONTROLS || (headings2.contentLinks ?? []).length > MAX_OBSERVED_CONTROLS;
|
|
45549
45750
|
const incomplete = [headings2.inventoryIncomplete ?? "", capped ? `more than ${MAX_OBSERVED_CONTROLS} controls were kept` : ""].filter((x) => x !== "").join("; ");
|
|
45550
45751
|
const status = headings2.status;
|
|
@@ -45564,6 +45765,8 @@ var ObservedPages = class {
|
|
|
45564
45765
|
if (nav.length > 0)
|
|
45565
45766
|
this.#topNav = nav;
|
|
45566
45767
|
}
|
|
45768
|
+
this.#settleSent(page.text);
|
|
45769
|
+
this.readBack(headings2.document, [page.text, ...kept.map((f2) => f2.value)].join("\n"), url2);
|
|
45567
45770
|
if (page.text.trim() === "" && kept.length === 0)
|
|
45568
45771
|
return;
|
|
45569
45772
|
const same = (p) => JSON.stringify(p.fields ?? []) === JSON.stringify(page.fields ?? []) && p.heading === page.heading && p.title === page.title && p.status === page.status && JSON.stringify(p.controls ?? []) === JSON.stringify(page.controls ?? []) && JSON.stringify(p.contentLinks ?? []) === JSON.stringify(page.contentLinks ?? []) && p.inventoryIncomplete === page.inventoryIncomplete;
|
|
@@ -45579,6 +45782,13 @@ var ObservedPages = class {
|
|
|
45579
45782
|
return [...this.#pages].reverse();
|
|
45580
45783
|
}
|
|
45581
45784
|
};
|
|
45785
|
+
function routeOf(url2) {
|
|
45786
|
+
try {
|
|
45787
|
+
return new URL(url2).pathname;
|
|
45788
|
+
} catch {
|
|
45789
|
+
return url2.split(/[?#]/)[0] ?? url2;
|
|
45790
|
+
}
|
|
45791
|
+
}
|
|
45582
45792
|
function pathOf6(url2) {
|
|
45583
45793
|
try {
|
|
45584
45794
|
const u = new URL(url2);
|
|
@@ -45918,14 +46128,14 @@ function errorPageReason(page) {
|
|
|
45918
46128
|
return `the page it is on is an error page (title "${page.title}")`;
|
|
45919
46129
|
return null;
|
|
45920
46130
|
}
|
|
45921
|
-
function groundClaim(claim2, quote6, pages, given, goal, answer, own2, absent) {
|
|
46131
|
+
function groundClaim(claim2, quote6, pages, given, goal, answer, own2, unread, absent) {
|
|
45922
46132
|
const name = (absent ?? "").replace(/\s+/g, " ").trim();
|
|
45923
46133
|
if (name !== "")
|
|
45924
46134
|
return { evidence: groundAbsence(claim2, name, pages, goal), notAnswer: false };
|
|
45925
|
-
const r = groundClaimOn(claim2, quote6, pages, given, goal, answer, own2);
|
|
46135
|
+
const r = groundClaimOn(claim2, quote6, pages, given, goal, answer, own2, unread);
|
|
45926
46136
|
return "evidence" in r ? r : { evidence: r, notAnswer: false };
|
|
45927
46137
|
}
|
|
45928
|
-
function groundClaimOn(claim2, quote6, pages, given, goal, answer, own2) {
|
|
46138
|
+
function groundClaimOn(claim2, quote6, pages, given, goal, answer, own2, unread) {
|
|
45929
46139
|
const q = bareQuote(quote6);
|
|
45930
46140
|
const base = { claim: claim2, quote: quote6 };
|
|
45931
46141
|
if (q.replace(/\s/g, "").length < MIN_QUOTE_CHARS)
|
|
@@ -45943,6 +46153,11 @@ function groundClaimOn(claim2, quote6, pages, given, goal, answer, own2) {
|
|
|
45943
46153
|
const why = `the quote is the run's own typed input in "${found.control ?? "a form field"}", never saved \u2014 it shows what the run entered, not what the app recorded (submit it, then report what the app shows)`;
|
|
45944
46154
|
return { ...base, url: page.url, grounded: false, ...where, why };
|
|
45945
46155
|
}
|
|
46156
|
+
const sent = [...unread].find((v) => v.length >= MIN_QUOTE_CHARS && q.includes(v));
|
|
46157
|
+
if (sent !== void 0) {
|
|
46158
|
+
const why = `${UNCONFIRMED_WRITE} ("${sent}"), and no page loaded since the write has shown it \u2014 the app may not have kept it (reload, or open the page where the app lists it, and report what that shows)`;
|
|
46159
|
+
return { ...base, url: page.url, grounded: false, ...where, why };
|
|
46160
|
+
}
|
|
45946
46161
|
if (found.source === "page-text" && !CONTROL_GOAL.test(goal) && quoteIsOnlyControlNames(quote6, found.page)) {
|
|
45947
46162
|
const why = "the quote is only a control's label (a button, a field label or a navigation link), not page content that answers the question";
|
|
45948
46163
|
return { evidence: { ...base, url: page.url, grounded: false, ...where, why }, notAnswer: true };
|
|
@@ -45970,7 +46185,8 @@ function groundAnswer(proposed, pages, opts = {}) {
|
|
|
45970
46185
|
return { accept: false, reason: NO_ANSWER_REASON, answer: null };
|
|
45971
46186
|
const given = new Set(numbersIn(opts.goal ?? ""));
|
|
45972
46187
|
const own2 = opts.ownInputs ?? /* @__PURE__ */ new Set();
|
|
45973
|
-
const
|
|
46188
|
+
const unread = opts.unreadWrites ?? /* @__PURE__ */ new Set();
|
|
46189
|
+
const grounded_ = proposed.claims.map((c) => groundClaim(c.claim, c.quote, pages, given, opts.goal ?? "", text2, own2, unread, c.absent));
|
|
45974
46190
|
const evidence = grounded_.map((g) => g.evidence);
|
|
45975
46191
|
const answer = { text: text2, evidence };
|
|
45976
46192
|
if (evidence.length === 0)
|
|
@@ -45979,6 +46195,8 @@ function groundAnswer(proposed, pages, opts = {}) {
|
|
|
45979
46195
|
if (badAt >= 0) {
|
|
45980
46196
|
const bad = evidence[badAt];
|
|
45981
46197
|
const reason = `the answer is not grounded: "${bad.claim}" \u2014 ${bad.why ?? "ungrounded"}`;
|
|
46198
|
+
if (bad.why?.startsWith(UNCONFIRMED_WRITE) === true)
|
|
46199
|
+
return { accept: false, reason, answer, unconfirmedWrite: true };
|
|
45982
46200
|
return grounded_[badAt].notAnswer ? { accept: false, reason, answer, notAnswer: true } : { accept: false, reason, answer };
|
|
45983
46201
|
}
|
|
45984
46202
|
const grounded = new Set(evidence.flatMap((e) => numbersIn(e.quote)));
|
|
@@ -46157,7 +46375,11 @@ async function reportAnswer(gen, input2) {
|
|
|
46157
46375
|
answer: out.answer === null ? null : r(out.answer),
|
|
46158
46376
|
claims: out.claims.map((c) => ({ claim: r(c.claim), quote: r(c.quote), ...c.absent == null ? {} : { absent: r(c.absent) } }))
|
|
46159
46377
|
});
|
|
46160
|
-
const grounding = {
|
|
46378
|
+
const grounding = {
|
|
46379
|
+
goal: input2.goal,
|
|
46380
|
+
...input2.ownInputs === void 0 ? {} : { ownInputs: input2.ownInputs },
|
|
46381
|
+
...input2.unreadWrites === void 0 ? {} : { unreadWrites: input2.unreadWrites }
|
|
46382
|
+
};
|
|
46161
46383
|
const res = await gen.generate("goal.answer", ask);
|
|
46162
46384
|
const verdict = await vetoed(groundAnswer(scrub3(res.output), input2.pages, grounding), input2.judge, vet, vetoes);
|
|
46163
46385
|
if (verdict.accept)
|
|
@@ -46267,6 +46489,7 @@ function initAnswers(ctx, cfg) {
|
|
|
46267
46489
|
ctx.settledClick = null;
|
|
46268
46490
|
ctx.clickedRegion = null;
|
|
46269
46491
|
ctx.wroteOk = false;
|
|
46492
|
+
ctx.readBackAsked = false;
|
|
46270
46493
|
ctx.writeGoal = goalAsksToWrite(cfg.goal);
|
|
46271
46494
|
ctx.vetoes = new VetoedAnswers();
|
|
46272
46495
|
ctx.replies = new ObservedPages(ctx.secrets);
|
|
@@ -48076,7 +48299,8 @@ function newStep(ctx, input2) {
|
|
|
48076
48299
|
...goalMet === void 0 ? {} : { goalMetProbability: goalMet },
|
|
48077
48300
|
...signIn === null || goalMet === void 0 ? {} : { signIn: { completed: signIn.completed, goalIsSignIn } },
|
|
48078
48301
|
...saved === null || goalMet === void 0 ? {} : { save: { completed: saved.completed, goalIsSave } },
|
|
48079
|
-
...writes === void 0 || goalMet === void 0 ? {} : { writes: { ...writes, goalIsWrites } }
|
|
48302
|
+
...writes === void 0 || goalMet === void 0 ? {} : { writes: { ...writes, goalIsWrites } },
|
|
48303
|
+
...writes === void 0 ? {} : { readBack: ctx.observed.readBackConfirmed() }
|
|
48080
48304
|
});
|
|
48081
48305
|
const judgments = {};
|
|
48082
48306
|
if (goalMet !== void 0 && goalMet !== null)
|
|
@@ -48116,6 +48340,29 @@ function deferEnding(ctx, step, ending) {
|
|
|
48116
48340
|
return note;
|
|
48117
48341
|
}
|
|
48118
48342
|
|
|
48343
|
+
// ../explore/dist/goal-loop/read-back.js
|
|
48344
|
+
function shownOn(step) {
|
|
48345
|
+
return [step.visibleText, ...step.snap.controls.map((c) => c.value ?? "")].join("\n");
|
|
48346
|
+
}
|
|
48347
|
+
async function readBackProbe(ctx, step) {
|
|
48348
|
+
const unread = ctx.observed.unreadShownIn(shownOn(step));
|
|
48349
|
+
if (unread.length === 0 || ctx.sideEffects.inflight().length > 0)
|
|
48350
|
+
return [];
|
|
48351
|
+
const r = await act(ctx.cfg.actor, { op: "reload", control: null });
|
|
48352
|
+
if (!r.ok)
|
|
48353
|
+
return [];
|
|
48354
|
+
const text2 = await readPageText(ctx.page, ctx.secrets).catch(() => "");
|
|
48355
|
+
const values2 = await ctx.page.evaluate(() => Array.from(document.querySelectorAll("input, textarea, select")).filter((e) => e.type !== "password").map((e) => e.value ?? "")).catch(() => []);
|
|
48356
|
+
const back = ctx.observed.readBack(await documentOf(ctx.page), redactPageText([text2, ...values2].join("\n"), ctx.secrets), ctx.page.url());
|
|
48357
|
+
const listed2 = (vs) => vs.map((v) => `"${v}"`).join(", ");
|
|
48358
|
+
const missing = unread.filter((v) => !back.includes(v));
|
|
48359
|
+
ctx.history.push(missing.length === 0 ? `read back after a reload: the page loaded from the server shows what the run sent (${listed2(back)})` : `after a reload, the page loaded from the server no longer shows what the run sent (${listed2(missing)})`);
|
|
48360
|
+
return back;
|
|
48361
|
+
}
|
|
48362
|
+
function unreadShown(ctx, step) {
|
|
48363
|
+
return ctx.observed.unreadShownIn(shownOn(step));
|
|
48364
|
+
}
|
|
48365
|
+
|
|
48119
48366
|
// ../explore/dist/goal-loop/handle-report.js
|
|
48120
48367
|
async function handleReport(ctx, step) {
|
|
48121
48368
|
const { cfg } = ctx;
|
|
@@ -48137,6 +48384,8 @@ async function handleReport(ctx, step) {
|
|
|
48137
48384
|
ctx.noteReplyText(snap.url, await readPageText(ctx.page, ctx.secrets));
|
|
48138
48385
|
}
|
|
48139
48386
|
const replyPages = ctx.replyGoal ? ctx.replies.pages() : null;
|
|
48387
|
+
if (replyPages === null)
|
|
48388
|
+
await readBackProbe(ctx, step);
|
|
48140
48389
|
const verdict = replyPages !== null && replyPages.length === 0 ? {
|
|
48141
48390
|
accept: false,
|
|
48142
48391
|
reason: ctx.preSend === null ? "no reply observed: no message was sent yet \u2014 text on the page before the conversation is not a reply" : `no reply observed: no new message appeared after the send within the reply wait (${Math.round(ctx.replyWaitMs / 1e3)}s)`,
|
|
@@ -48150,7 +48399,7 @@ async function handleReport(ctx, step) {
|
|
|
48150
48399
|
judge: cfg.judge,
|
|
48151
48400
|
vetoes: ctx.vetoes,
|
|
48152
48401
|
// #238: "none exists" is an answer only on observed pages that cover the app enough.
|
|
48153
|
-
...replyPages === null ? { topNav: ctx.observed.topNavigation(), ownInputs: ctx.observed.ownInputs() } : {}
|
|
48402
|
+
...replyPages === null ? { topNav: ctx.observed.topNavigation(), ownInputs: ctx.observed.ownInputs(), unreadWrites: ctx.observed.unreadWrites() } : {}
|
|
48154
48403
|
}).then((v) => v.accept && ctx.writeGoal && !ctx.wroteOk && v.answer.absent !== true ? { accept: false, reason: UNSAVED_WRITE_REASON, answer: v.answer } : v).catch((e) => ({ accept: false, reason: `no answer could be generated: ${firstLine(e)}`, answer: null }));
|
|
48155
48404
|
if (verdict.accept) {
|
|
48156
48405
|
const deferred = deferEnding(ctx, step, "report");
|
|
@@ -48170,6 +48419,12 @@ async function handleReport(ctx, step) {
|
|
|
48170
48419
|
ctx.stop = "done";
|
|
48171
48420
|
return "stop";
|
|
48172
48421
|
}
|
|
48422
|
+
if (verdict.unconfirmedWrite === true && !ctx.readBackAsked) {
|
|
48423
|
+
ctx.readBackAsked = true;
|
|
48424
|
+
ctx.history.push(`report deferred: ${verdict.reason}`);
|
|
48425
|
+
record2(false, `report deferred: ${verdict.reason}`, { answer: verdict.answer, origin: "engine" });
|
|
48426
|
+
return "continue";
|
|
48427
|
+
}
|
|
48173
48428
|
ctx.reportRejections += 1;
|
|
48174
48429
|
for (const e of verdict.answer?.evidence ?? [])
|
|
48175
48430
|
if (e.grounded)
|
|
@@ -48189,6 +48444,15 @@ async function handleReport(ctx, step) {
|
|
|
48189
48444
|
}
|
|
48190
48445
|
|
|
48191
48446
|
// ../explore/dist/goal-loop/handle-done.js
|
|
48447
|
+
async function unreadWriteHold(ctx, step, outcome) {
|
|
48448
|
+
if (outcome.status === "completed" && outcome.verifiedBy === "success-condition")
|
|
48449
|
+
return null;
|
|
48450
|
+
await readBackProbe(ctx, step);
|
|
48451
|
+
const unread = unreadShown(ctx, step);
|
|
48452
|
+
if (unread.length === 0)
|
|
48453
|
+
return null;
|
|
48454
|
+
return `what the run sent (${unread.map((v) => `"${v}"`).join(", ")}) has not shown on a page loaded since the write \u2014 the app may not have kept it (open the page where the app lists it)`;
|
|
48455
|
+
}
|
|
48192
48456
|
async function handleDone(ctx, step) {
|
|
48193
48457
|
const { cfg } = ctx;
|
|
48194
48458
|
const { snap, decision, record: record2, signIn, pendingNote, acceptedBy, groundGoal } = step;
|
|
@@ -48200,7 +48464,7 @@ async function handleDone(ctx, step) {
|
|
|
48200
48464
|
!(cfg.successCheck === void 0 && ctx.minEffort !== null && ctx.depth.report(ctx.tracker.actions, ctx.tracker.decisions, ctx.minEffort).minimum?.met === false)) {
|
|
48201
48465
|
ctx.goalChecked.add(snap.signature);
|
|
48202
48466
|
const { verdict, judgments } = await groundGoal();
|
|
48203
|
-
if (verdict.accept) {
|
|
48467
|
+
if (verdict.accept && await unreadWriteHold(ctx, step, verdict.outcome) === null) {
|
|
48204
48468
|
record2(true, `goal already met \u2014 stopped instead of "${decision.op}": ${acceptedBy(verdict.outcome)}`, {
|
|
48205
48469
|
op: "done",
|
|
48206
48470
|
control: null,
|
|
@@ -48236,7 +48500,19 @@ async function handleDone(ctx, step) {
|
|
|
48236
48500
|
}
|
|
48237
48501
|
}
|
|
48238
48502
|
if (decision.op === "done") {
|
|
48239
|
-
const
|
|
48503
|
+
const grounded = await groundGoal(false, true);
|
|
48504
|
+
const { judgments } = grounded;
|
|
48505
|
+
let verdict = grounded.verdict;
|
|
48506
|
+
const hold = verdict.accept ? await unreadWriteHold(ctx, step, verdict.outcome) : null;
|
|
48507
|
+
if (hold !== null) {
|
|
48508
|
+
if (!ctx.readBackAsked) {
|
|
48509
|
+
ctx.readBackAsked = true;
|
|
48510
|
+
ctx.history.push(`done deferred: ${hold}`);
|
|
48511
|
+
record2(false, `done deferred: ${hold}`, { origin: "engine" });
|
|
48512
|
+
return "continue";
|
|
48513
|
+
}
|
|
48514
|
+
verdict = { accept: false, reason: hold };
|
|
48515
|
+
}
|
|
48240
48516
|
if (verdict.accept) {
|
|
48241
48517
|
record2(true, `done accepted${pendingNote(verdict.outcome) === null ? "" : " provisionally"}: ${acceptedBy(verdict.outcome)}`, {
|
|
48242
48518
|
...judgments === void 0 ? {} : { judgments }
|
|
@@ -48943,10 +49219,20 @@ async function handleSelectOption(ctx, step, op) {
|
|
|
48943
49219
|
return "continue";
|
|
48944
49220
|
}
|
|
48945
49221
|
const named = text2 === null ? null : matchOption(text2, options);
|
|
49222
|
+
const field3 = control.name || control.summary;
|
|
49223
|
+
const noop2 = `${quote(named ?? "", 60)} is already selected in ${field3} \u2014 nothing to change`;
|
|
49224
|
+
const setByRun = () => ctx.transcript.entries().some((e) => e.op === "select" && e.actOk && e.value === named && (e.target ?? "").includes(`"${control.name}"`));
|
|
49225
|
+
if (named !== null && named === current2 && !isPlaceholderOption(named) && !ctx.history.includes(noop2) && !setByRun()) {
|
|
49226
|
+
ctx.fillHelper.commit();
|
|
49227
|
+
ctx.history.push(noop2);
|
|
49228
|
+
record2(true, noop2, { value: named });
|
|
49229
|
+
ctx.lastActedOp = op;
|
|
49230
|
+
return "continue";
|
|
49231
|
+
}
|
|
48946
49232
|
if (named !== null && !choices.includes(named)) {
|
|
48947
49233
|
ctx.fillHelper.commit();
|
|
48948
49234
|
const why = named === current2 ? `${quote(named, 60)} is already selected` : `${quote(named, 60)} is a placeholder, not a choice`;
|
|
48949
|
-
const reason = `select refused: ${why} in ${
|
|
49235
|
+
const reason = `select refused: ${why} in ${field3} \u2014 options not tried: ${untried()}`;
|
|
48950
49236
|
ctx.history.push(reason);
|
|
48951
49237
|
record2(false, reason, { origin: "engine", value: named });
|
|
48952
49238
|
ctx.lastActedOp = op;
|
|
@@ -49245,7 +49531,8 @@ async function perceiveStep(ctx) {
|
|
|
49245
49531
|
if (lc !== null && lc !== ctx.settledClick) {
|
|
49246
49532
|
ctx.settledClick = lc;
|
|
49247
49533
|
if (lc.writes.length > 0 && lc.writes.every((w) => w.status !== null && w.status >= 200 && w.status < 300)) {
|
|
49248
|
-
ctx.observed.
|
|
49534
|
+
ctx.observed.noteSentValues(lc.sentValues, ctx.page.url());
|
|
49535
|
+
ctx.observed.confirmOwnInputs(await documentOf(ctx.page));
|
|
49249
49536
|
ctx.wroteOk = true;
|
|
49250
49537
|
}
|
|
49251
49538
|
}
|
|
@@ -49696,6 +49983,7 @@ var APPEARED_CHARS = 400;
|
|
|
49696
49983
|
function clipLine(s3) {
|
|
49697
49984
|
return s3.length <= APPEARED_CHARS ? s3 : `${s3.slice(0, APPEARED_CHARS - 1)}\u2026`;
|
|
49698
49985
|
}
|
|
49986
|
+
var PROGRESS_LINE = /\b\d+\s*(?:of|\/)\s*\d+\b|\b(?:step|question|page|part|stage|section|confirmation)\s+\d+\b/i;
|
|
49699
49987
|
function appearedLines(before, after) {
|
|
49700
49988
|
const lines = (t) => t.split("\n").map((l2) => l2.replace(/\s+/g, " ").trim()).filter((l2) => l2 !== "");
|
|
49701
49989
|
const shape3 = (l2) => l2.replace(/\d+/g, "#");
|
|
@@ -49710,7 +49998,10 @@ function appearedLines(before, after) {
|
|
|
49710
49998
|
else
|
|
49711
49999
|
out.push(l2);
|
|
49712
50000
|
}
|
|
49713
|
-
|
|
50001
|
+
if (out.length > 0)
|
|
50002
|
+
return out;
|
|
50003
|
+
const was = new Set(lines(before));
|
|
50004
|
+
return lines(after).filter((l2) => PROGRESS_LINE.test(l2) && !was.has(l2));
|
|
49714
50005
|
}
|
|
49715
50006
|
|
|
49716
50007
|
// ../explore/dist/goal-loop/page-view.js
|
|
@@ -49791,8 +50082,12 @@ async function viewPage(ctx, step) {
|
|
|
49791
50082
|
ctx.chrome.observe(new URL(snap.url).pathname, snap.controls);
|
|
49792
50083
|
} catch {
|
|
49793
50084
|
}
|
|
50085
|
+
const document2 = await documentOf(ctx.page);
|
|
50086
|
+
const unreadBefore = [...ctx.observed.unreadWrites()];
|
|
49794
50087
|
ctx.observed.add(snap.url, visibleText, [...controlFields(snap.controls), ...richFields], {
|
|
49795
50088
|
...await readPageHeadings(ctx.page, ctx.secrets),
|
|
50089
|
+
// #647: which document load this is — a later one that shows a sent value reads it back.
|
|
50090
|
+
...document2 === void 0 ? {} : { document: document2 },
|
|
49796
50091
|
// #223: the action / label names (a quote made only of them is a label, not an answer) and
|
|
49797
50092
|
// the document's status (an answer on a 404 page is no answer). A link that is page content
|
|
49798
50093
|
// (in the main content, a list, a table, a card) is NOT one: its text may be the answer.
|
|
@@ -49805,6 +50100,13 @@ async function viewPage(ctx, step) {
|
|
|
49805
50100
|
navLinks: snap.controls.filter((c) => c.role === "link" && (c.landmark === "navigation" || c.landmark === "banner")).flatMap((c) => typeof c.href === "string" && c.href !== "" ? [c.href] : []),
|
|
49806
50101
|
...ctx.documentStatus.has(ctx.docKey(ctx.page.url())) ? { status: ctx.documentStatus.get(ctx.docKey(ctx.page.url())) } : {}
|
|
49807
50102
|
});
|
|
50103
|
+
{
|
|
50104
|
+
const now2 = ctx.observed.unreadWrites();
|
|
50105
|
+
const back = unreadBefore.filter((v) => !now2.has(v));
|
|
50106
|
+
if (back.length > 0) {
|
|
50107
|
+
ctx.history.push(`read back: this page, loaded from the server, shows what the run sent (${back.map((v) => `"${v}"`).join(", ")}) \u2014 the app kept it`);
|
|
50108
|
+
}
|
|
50109
|
+
}
|
|
49808
50110
|
ctx.noteReplyText(snap.url, visibleText);
|
|
49809
50111
|
ctx.depth.noteState(snap.signature, snap.url);
|
|
49810
50112
|
{
|
|
@@ -53099,13 +53401,13 @@ function lintUploadFixtures(journey, baseDir) {
|
|
|
53099
53401
|
});
|
|
53100
53402
|
continue;
|
|
53101
53403
|
}
|
|
53102
|
-
const
|
|
53103
|
-
if (
|
|
53404
|
+
const sensitive2 = fixtureSensitiveReason(real, realRootOf(baseDir)) ?? fixtureSensitiveReason(candidate, resolve5(baseDir));
|
|
53405
|
+
if (sensitive2 !== null) {
|
|
53104
53406
|
findings.push({
|
|
53105
53407
|
rule: "upload-sensitive-file",
|
|
53106
53408
|
level: "error",
|
|
53107
53409
|
step: i + 1,
|
|
53108
|
-
message: `step ${i + 1}: upload fixture ${candidate} looks sensitive (${
|
|
53410
|
+
message: `step ${i + 1}: upload fixture ${candidate} looks sensitive (${sensitive2})`,
|
|
53109
53411
|
fix: "use a dedicated test fixture file"
|
|
53110
53412
|
});
|
|
53111
53413
|
continue;
|
|
@@ -53230,7 +53532,7 @@ function lintJourney(journey, opts = {}) {
|
|
|
53230
53532
|
const refsBound = (a) => assertionRefsBound(journey, a);
|
|
53231
53533
|
const effects = sites.filter((site) => isEffectSite(site, site.where === "step" ? stepOf(site.step) : void 0, refsBound));
|
|
53232
53534
|
const endEffects = effects.filter((site) => site.where === "end-state");
|
|
53233
|
-
const effectAt = (n3) => effects.some((site) => (site.where === "step" || site.where === "step-request") && site.step === n3);
|
|
53535
|
+
const effectAt = (n3) => recorded[n3 - 1]?.step.kind === "waitFor" || effects.some((site) => (site.where === "step" || site.where === "step-request") && site.step === n3);
|
|
53234
53536
|
const writeRequests = recorded.map((r) => writeRequestsOf(r, classify2));
|
|
53235
53537
|
const isWrite2 = recorded.map((r, i) => isWriteStep(r, classify2, writeRequests[i]));
|
|
53236
53538
|
const lastWrite = isWrite2.lastIndexOf(true) + 1;
|
|
@@ -53433,9 +53735,9 @@ function splitBudget(total, stops) {
|
|
|
53433
53735
|
var CLAIM_KEYS = /* @__PURE__ */ new Set(["expect", "check", "resume", "waitFor"]);
|
|
53434
53736
|
function actionRefs(step) {
|
|
53435
53737
|
const out = /* @__PURE__ */ new Set();
|
|
53436
|
-
const
|
|
53738
|
+
const walk2 = (v) => {
|
|
53437
53739
|
if (Array.isArray(v)) {
|
|
53438
|
-
v.forEach(
|
|
53740
|
+
v.forEach(walk2);
|
|
53439
53741
|
return;
|
|
53440
53742
|
}
|
|
53441
53743
|
if (v === null || typeof v !== "object")
|
|
@@ -53450,9 +53752,9 @@ function actionRefs(step) {
|
|
|
53450
53752
|
out.add(n3);
|
|
53451
53753
|
for (const [k, x] of Object.entries(o))
|
|
53452
53754
|
if (!CLAIM_KEYS.has(k))
|
|
53453
|
-
|
|
53755
|
+
walk2(x);
|
|
53454
53756
|
};
|
|
53455
|
-
|
|
53757
|
+
walk2(step);
|
|
53456
53758
|
return [...out];
|
|
53457
53759
|
}
|
|
53458
53760
|
function sitePageAssertion(site) {
|
|
@@ -57249,7 +57551,7 @@ function seedPath(seedUrl) {
|
|
|
57249
57551
|
}
|
|
57250
57552
|
|
|
57251
57553
|
// ../explore/dist/adversarial/scope.js
|
|
57252
|
-
function
|
|
57554
|
+
function routeOf2(url2) {
|
|
57253
57555
|
const path2 = new URL(url2).pathname;
|
|
57254
57556
|
return path2.length > 1 && path2.endsWith("/") ? path2.slice(0, -1) : path2;
|
|
57255
57557
|
}
|
|
@@ -57269,7 +57571,7 @@ function startRouteGlobs(startUrl) {
|
|
|
57269
57571
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
57270
57572
|
throw new ScopeUnderivableError(`a ${parsed.protocol} URL has no route`);
|
|
57271
57573
|
}
|
|
57272
|
-
const route =
|
|
57574
|
+
const route = routeOf2(startUrl);
|
|
57273
57575
|
return route === "/" ? ["/", "/**"] : [route, `${route}/`, `${route}/**`];
|
|
57274
57576
|
}
|
|
57275
57577
|
function scopeGlobs(startUrl, extra = []) {
|
|
@@ -59401,7 +59703,7 @@ var CoverageTracker = class {
|
|
|
59401
59703
|
if (this.#counts(c))
|
|
59402
59704
|
this.#controls.add(key2);
|
|
59403
59705
|
}
|
|
59404
|
-
const route =
|
|
59706
|
+
const route = routeOf2(snapshot2.url);
|
|
59405
59707
|
const formControls = this.#region === void 0 ? snapshot2.controls : snapshot2.controls.filter((c) => c.inRegion === true);
|
|
59406
59708
|
for (const f2 of detectForms(formControls, this.#inScope))
|
|
59407
59709
|
this.#forms.add(`${route}|${f2.key}`);
|
|
@@ -59434,7 +59736,7 @@ var CoverageTracker = class {
|
|
|
59434
59736
|
submitted(url2, formKey) {
|
|
59435
59737
|
if (!this.#inScope(url2))
|
|
59436
59738
|
return;
|
|
59437
|
-
this.#submitted.add(`${
|
|
59739
|
+
this.#submitted.add(`${routeOf2(url2)}|${formKey}`);
|
|
59438
59740
|
}
|
|
59439
59741
|
/**
|
|
59440
59742
|
* A submit of `formKey` (on page `url`) never reached the server, so it never counts toward
|
|
@@ -59445,7 +59747,7 @@ var CoverageTracker = class {
|
|
|
59445
59747
|
blocked(url2, formKey, message2, why = "validation") {
|
|
59446
59748
|
if (!this.#inScope(url2))
|
|
59447
59749
|
return;
|
|
59448
|
-
const form = `${
|
|
59750
|
+
const form = `${routeOf2(url2)}|${formKey}`;
|
|
59449
59751
|
const key2 = `${form}|${why}`;
|
|
59450
59752
|
const cur = this.#blocked.get(key2);
|
|
59451
59753
|
this.#blocked.set(key2, { form, why, count: (cur?.count ?? 0) + 1, message: cur?.message ?? message2 });
|
|
@@ -60249,6 +60551,16 @@ function submitRequestSent(monitor, sinceMs) {
|
|
|
60249
60551
|
return true;
|
|
60250
60552
|
return false;
|
|
60251
60553
|
}
|
|
60554
|
+
var SUBMIT_REQUEST_POLL_MS = 50;
|
|
60555
|
+
async function submitRequestSentWithin(monitor, sinceMs, boundMs) {
|
|
60556
|
+
const deadline = clock.now() + boundMs;
|
|
60557
|
+
while (!submitRequestSent(monitor, sinceMs)) {
|
|
60558
|
+
if (clock.now() >= deadline)
|
|
60559
|
+
return false;
|
|
60560
|
+
await clock.sleep(SUBMIT_REQUEST_POLL_MS);
|
|
60561
|
+
}
|
|
60562
|
+
return true;
|
|
60563
|
+
}
|
|
60252
60564
|
async function nativeValidationMessage(page) {
|
|
60253
60565
|
return page.evaluate(() => {
|
|
60254
60566
|
const fields = Array.from(document.querySelectorAll("input,select,textarea"));
|
|
@@ -61468,6 +61780,7 @@ async function refuseMisuseStep(ctx, ep, turn, s3) {
|
|
|
61468
61780
|
}
|
|
61469
61781
|
|
|
61470
61782
|
// ../explore/dist/missions/adversarial-hunt/episode.js
|
|
61783
|
+
var SUBMIT_REQUEST_GRACE_MS = 1e3;
|
|
61471
61784
|
async function runEpisode(ctx, overlay, turn) {
|
|
61472
61785
|
const ep = {};
|
|
61473
61786
|
const { ran, planning, episode } = turn;
|
|
@@ -61550,10 +61863,12 @@ async function runEpisode(ctx, overlay, turn) {
|
|
|
61550
61863
|
if (result.ok)
|
|
61551
61864
|
ctx.cov.acted(ep.stepSnap.url, s3.control);
|
|
61552
61865
|
if (result.ok && s3.submitsForm !== void 0) {
|
|
61553
|
-
|
|
61866
|
+
const monitor = monitorFor(ctx.sessions.page);
|
|
61867
|
+
const invalid = submitRequestSent(monitor, at3) ? void 0 : await nativeValidationMessage(ctx.sessions.page);
|
|
61868
|
+
if (invalid === void 0 && await submitRequestSentWithin(monitor, at3, SUBMIT_REQUEST_GRACE_MS)) {
|
|
61554
61869
|
ctx.cov.submitted(ep.stepSnap.url, s3.submitsForm);
|
|
61555
61870
|
} else {
|
|
61556
|
-
ctx.cov.blocked(ep.stepSnap.url, s3.submitsForm,
|
|
61871
|
+
ctx.cov.blocked(ep.stepSnap.url, s3.submitsForm, invalid);
|
|
61557
61872
|
}
|
|
61558
61873
|
}
|
|
61559
61874
|
if (ran === "visit-route" && s3.control !== null)
|
|
@@ -62744,9 +63059,9 @@ function hostStarvedFailure(failure3, health) {
|
|
|
62744
63059
|
}
|
|
62745
63060
|
|
|
62746
63061
|
// src/build-info.generated.ts
|
|
62747
|
-
var GENERATED_COMMIT = "
|
|
62748
|
-
var GENERATED_TREE = "
|
|
62749
|
-
var GENERATED_BUILT_AT = "2026-10-
|
|
63062
|
+
var GENERATED_COMMIT = "ad72163";
|
|
63063
|
+
var GENERATED_TREE = "379caf5";
|
|
63064
|
+
var GENERATED_BUILT_AT = "2026-10-23T21:02:56.700Z";
|
|
62750
63065
|
|
|
62751
63066
|
// src/version.ts
|
|
62752
63067
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
@@ -62768,13 +63083,13 @@ function readCliVersion() {
|
|
|
62768
63083
|
|
|
62769
63084
|
// src/engine.ts
|
|
62770
63085
|
function bundledCommit() {
|
|
62771
|
-
return true ? "
|
|
63086
|
+
return true ? "ad72163" : void 0;
|
|
62772
63087
|
}
|
|
62773
63088
|
function bundledBuiltAt() {
|
|
62774
|
-
return true ? "2026-10-
|
|
63089
|
+
return true ? "2026-10-23T21:02:56.700Z" : void 0;
|
|
62775
63090
|
}
|
|
62776
63091
|
function bundledTree() {
|
|
62777
|
-
return true ? "
|
|
63092
|
+
return true ? "379caf5" : void 0;
|
|
62778
63093
|
}
|
|
62779
63094
|
function currentBuildTree() {
|
|
62780
63095
|
return bundledTree() ?? GENERATED_TREE;
|
|
@@ -85907,7 +86222,7 @@ var FINGERPRINT_MS = 1e4;
|
|
|
85907
86222
|
function oneLine2(s3) {
|
|
85908
86223
|
return s3.replace(/\s+/g, " ").trim();
|
|
85909
86224
|
}
|
|
85910
|
-
function
|
|
86225
|
+
function routeOf3(url2) {
|
|
85911
86226
|
try {
|
|
85912
86227
|
const u = new URL(url2);
|
|
85913
86228
|
return u.pathname + (u.search === "" ? "" : u.search);
|
|
@@ -85984,7 +86299,7 @@ var RunScreenshots = class {
|
|
|
85984
86299
|
this.#entries.push({
|
|
85985
86300
|
n: n3,
|
|
85986
86301
|
step: at3.step,
|
|
85987
|
-
route: this.#redact(redactUrl(
|
|
86302
|
+
route: this.#redact(redactUrl(routeOf3(url2))),
|
|
85988
86303
|
what: this.#redact(oneLine2(at3.what)).slice(0, 300),
|
|
85989
86304
|
path: path2
|
|
85990
86305
|
});
|
|
@@ -86308,7 +86623,7 @@ function invariantSetupTexts(spec) {
|
|
|
86308
86623
|
return Object.fromEntries(specStrings(spec, "", []).filter(([, s3]) => s3.includes("${setup.")).map(([p, s3]) => [`--invariants ${p}`, s3]));
|
|
86309
86624
|
}
|
|
86310
86625
|
function substituteSpecSetupRefs(spec, b2, baseUrl) {
|
|
86311
|
-
const
|
|
86626
|
+
const walk2 = (v, path2) => {
|
|
86312
86627
|
if (typeof v === "string") {
|
|
86313
86628
|
if (!v.includes("${setup.")) return v;
|
|
86314
86629
|
const where = `--invariants ${path2}`;
|
|
@@ -86323,11 +86638,11 @@ function substituteSpecSetupRefs(spec, b2, baseUrl) {
|
|
|
86323
86638
|
if (before !== after) throw new UnboundSetupRefError(`${where}: a \${setup.*} value may fill the path or query, never the origin`);
|
|
86324
86639
|
return out;
|
|
86325
86640
|
}
|
|
86326
|
-
if (Array.isArray(v)) return v.map((x, i) =>
|
|
86327
|
-
if (v !== null && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k,
|
|
86641
|
+
if (Array.isArray(v)) return v.map((x, i) => walk2(x, `${path2}[${i}]`));
|
|
86642
|
+
if (v !== null && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk2(x, path2 === "" ? k : `${path2}.${k}`)]));
|
|
86328
86643
|
return v;
|
|
86329
86644
|
};
|
|
86330
|
-
return
|
|
86645
|
+
return walk2(spec, "");
|
|
86331
86646
|
}
|
|
86332
86647
|
function fixtureSetupFailedResult(err, fx) {
|
|
86333
86648
|
const reason = err instanceof FixtureSetupError ? err.message : `fixture setup failed: ${err.message}`;
|
|
@@ -87627,6 +87942,7 @@ var ServerLogRuntime = class {
|
|
|
87627
87942
|
};
|
|
87628
87943
|
}
|
|
87629
87944
|
};
|
|
87945
|
+
var NON_ACTING_OPS = /* @__PURE__ */ new Set(["done", "blocked", null]);
|
|
87630
87946
|
function stepWindows(transcript, stepEpoch, missionStartEpochMs, drainMs) {
|
|
87631
87947
|
const windows = [];
|
|
87632
87948
|
let prevEnd = missionStartEpochMs;
|
|
@@ -87635,7 +87951,9 @@ function stepWindows(transcript, stepEpoch, missionStartEpochMs, drainMs) {
|
|
|
87635
87951
|
const settle2 = stepEpoch.get(entry.step) ?? prevEnd;
|
|
87636
87952
|
const isLast = i === transcript.length - 1;
|
|
87637
87953
|
const endMs = isLast ? settle2 + drainMs : settle2;
|
|
87638
|
-
|
|
87954
|
+
const acting = windows[windows.length - 1];
|
|
87955
|
+
if (NON_ACTING_OPS.has(entry.op) && acting !== void 0) acting.endMs = endMs;
|
|
87956
|
+
else windows.push({ step: entry.step, startMs: prevEnd, endMs });
|
|
87639
87957
|
prevEnd = settle2;
|
|
87640
87958
|
}
|
|
87641
87959
|
return windows;
|
|
@@ -88532,7 +88850,7 @@ function findingIdentity(f2) {
|
|
|
88532
88850
|
function str2(v, fallback = "") {
|
|
88533
88851
|
return typeof v === "string" ? v : fallback;
|
|
88534
88852
|
}
|
|
88535
|
-
function
|
|
88853
|
+
function routeOf4(item) {
|
|
88536
88854
|
if (typeof item.route === "string") return item.route;
|
|
88537
88855
|
if (typeof item.url === "string") return normalizeRoute(item.url);
|
|
88538
88856
|
return "/";
|
|
@@ -88544,7 +88862,7 @@ function extractRunFindings(data) {
|
|
|
88544
88862
|
if (!Array.isArray(list2)) return;
|
|
88545
88863
|
for (const item of list2) {
|
|
88546
88864
|
if (!isRecord(item) || typeof item.fingerprint !== "string") continue;
|
|
88547
|
-
out.push({ kind: kindOf(item), fingerprint: item.fingerprint, route:
|
|
88865
|
+
out.push({ kind: kindOf(item), fingerprint: item.fingerprint, route: routeOf4(item), title: str2(item.title, item.fingerprint) });
|
|
88548
88866
|
}
|
|
88549
88867
|
};
|
|
88550
88868
|
fingerprinted(data.defects, (d) => d.kind === "invariant" ? "invariant" : `defect/${str2(d.kind, "unknown")}`);
|
|
@@ -88553,14 +88871,14 @@ function extractRunFindings(data) {
|
|
|
88553
88871
|
if (isRecord(data.coverage) && Array.isArray(data.coverage.defects)) {
|
|
88554
88872
|
for (const d of data.coverage.defects) {
|
|
88555
88873
|
if (!isRecord(d) || typeof d.stateFingerprint !== "string") continue;
|
|
88556
|
-
out.push({ kind: "coverage-defect", fingerprint: d.stateFingerprint, route:
|
|
88874
|
+
out.push({ kind: "coverage-defect", fingerprint: d.stateFingerprint, route: routeOf4(d), title: str2(d.reason, "looks broken") });
|
|
88557
88875
|
}
|
|
88558
88876
|
}
|
|
88559
88877
|
if (isRecord(data.report) && Array.isArray(data.report.findings)) {
|
|
88560
88878
|
for (const f2 of data.report.findings) {
|
|
88561
88879
|
if (!isRecord(f2) || typeof f2.rubricItemId !== "string") continue;
|
|
88562
88880
|
const controls = Array.isArray(f2.controls) ? f2.controls.filter((c) => typeof c === "string") : [];
|
|
88563
|
-
out.push({ kind: "ux", route:
|
|
88881
|
+
out.push({ kind: "ux", route: routeOf4(f2), title: str2(f2.observation, f2.rubricItemId), rubricItemId: f2.rubricItemId, controls });
|
|
88564
88882
|
}
|
|
88565
88883
|
}
|
|
88566
88884
|
return out;
|
|
@@ -91645,18 +91963,18 @@ function rebaseRecording(recording, env) {
|
|
|
91645
91963
|
if (mustBeAllowed && !env.allowedOrigins.includes(u.origin)) refusals.push(`${where} is on ${u.origin}`);
|
|
91646
91964
|
return raw;
|
|
91647
91965
|
};
|
|
91648
|
-
const
|
|
91649
|
-
if (Array.isArray(v)) return v.map((x, i) =>
|
|
91966
|
+
const walk2 = (v, where) => {
|
|
91967
|
+
if (Array.isArray(v)) return v.map((x, i) => walk2(x, `${where}[${i}]`));
|
|
91650
91968
|
if (v === null || typeof v !== "object") return v;
|
|
91651
91969
|
const o = v;
|
|
91652
91970
|
const out = {};
|
|
91653
|
-
for (const [k, x] of Object.entries(o)) out[k] =
|
|
91971
|
+
for (const [k, x] of Object.entries(o)) out[k] = walk2(x, `${where}.${k}`);
|
|
91654
91972
|
if (o.kind === "navigate" && typeof o.url === "string") out.url = move(o.url, `${where} (navigate ${o.url})`, true);
|
|
91655
91973
|
if (o.kind === "urlIncludes" && typeof o.text === "string") out.text = move(o.text, `${where}.text`, false);
|
|
91656
91974
|
return out;
|
|
91657
91975
|
};
|
|
91658
91976
|
const pages = recording.pages.map((p, i) => ({
|
|
91659
|
-
...
|
|
91977
|
+
...walk2(p, `pages[${i}]`),
|
|
91660
91978
|
url: move(p.url, `pages[${i}] (${p.url})`, true)
|
|
91661
91979
|
}));
|
|
91662
91980
|
if (refusals.length > 0) {
|
|
@@ -94428,7 +94746,7 @@ function classifyTarget(d, testIdAttributes, via) {
|
|
|
94428
94746
|
function kebabCase(s3) {
|
|
94429
94747
|
return s3.normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/g, "");
|
|
94430
94748
|
}
|
|
94431
|
-
function
|
|
94749
|
+
function routeOf5(url2) {
|
|
94432
94750
|
if (url2 === void 0 || url2 === "") return void 0;
|
|
94433
94751
|
try {
|
|
94434
94752
|
return new URL(url2).pathname;
|
|
@@ -94526,7 +94844,7 @@ function dedupeSuggestions(lists) {
|
|
|
94526
94844
|
return [...byKey.values()].sort((a, b2) => b2.occurrences.length - a.occurrences.length || a.key.localeCompare(b2.key));
|
|
94527
94845
|
}
|
|
94528
94846
|
function flatWithRoute(rec) {
|
|
94529
|
-
return rec.pages.flatMap((p) => p.steps.map((recorded) => ({ recorded, route:
|
|
94847
|
+
return rec.pages.flatMap((p) => p.steps.map((recorded) => ({ recorded, route: routeOf5(p.url) })));
|
|
94530
94848
|
}
|
|
94531
94849
|
function healthLine(h) {
|
|
94532
94850
|
const total = h.stable + h.brittle;
|
|
@@ -96830,6 +97148,43 @@ function reportSlowness(replays) {
|
|
|
96830
97148
|
import { mkdir as mkdir22, writeFile as writeFile25 } from "node:fs/promises";
|
|
96831
97149
|
import { join as join52, resolve as resolve16 } from "node:path";
|
|
96832
97150
|
|
|
97151
|
+
// src/outcome-targets.ts
|
|
97152
|
+
function evaluateOutcomeTargets(outcomes, baseline) {
|
|
97153
|
+
return outcomes.map((o) => evaluateOne(o, baseline));
|
|
97154
|
+
}
|
|
97155
|
+
function evaluateOne(o, baseline) {
|
|
97156
|
+
const metric = o.metric;
|
|
97157
|
+
const target = o.target;
|
|
97158
|
+
if (metric === void 0 || target === void 0) {
|
|
97159
|
+
return { outcomeId: o.id, status: "not-decidable-by-replay", reason: "no metric or no target" };
|
|
97160
|
+
}
|
|
97161
|
+
const written2 = `${target.op} ${target.value} ${target.unit}`;
|
|
97162
|
+
if (metric.kind !== "duration") {
|
|
97163
|
+
return { outcomeId: o.id, status: "not-decidable-by-replay", target: written2, reason: `a ${metric.kind} metric needs real users, not a replay` };
|
|
97164
|
+
}
|
|
97165
|
+
const at3 = (name) => baseline.anchors.find((a) => a.name === name)?.atMs;
|
|
97166
|
+
const from = at3(metric.from);
|
|
97167
|
+
const to = at3(metric.to);
|
|
97168
|
+
const missing = from === void 0 ? metric.from : to === void 0 ? metric.to : null;
|
|
97169
|
+
if (missing !== null || from === void 0 || to === void 0) {
|
|
97170
|
+
return { outcomeId: o.id, status: "unmeasured", target: written2, reason: `anchor '${missing}' is not in the replay's baseline` };
|
|
97171
|
+
}
|
|
97172
|
+
const floorSeconds = Math.max(0, to - from) / 1e3;
|
|
97173
|
+
const floor = `the replay took ${floorSeconds}s from '${metric.from}' to '${metric.to}'`;
|
|
97174
|
+
if (metric.stat === "share_under") {
|
|
97175
|
+
const wantsShare = (target.op === ">" || target.op === ">=") && target.unit === "percent" && target.value > 0;
|
|
97176
|
+
if (!wantsShare || metric.threshold === void 0) {
|
|
97177
|
+
return { outcomeId: o.id, status: "not-decidable-by-replay", floorSeconds, target: written2, reason: "only a lower bound on the share under a threshold is decidable" };
|
|
97178
|
+
}
|
|
97179
|
+
return metric.threshold < floorSeconds ? { outcomeId: o.id, status: "miss", floorSeconds, target: written2, reason: `${floor}, over the ${metric.threshold}s threshold: no one finishes under it` } : { outcomeId: o.id, status: "not-decidable", floorSeconds, target: written2, reason: `${floor}, within the ${metric.threshold}s threshold` };
|
|
97180
|
+
}
|
|
97181
|
+
if (target.unit !== "s" || target.op !== "<" && target.op !== "<=") {
|
|
97182
|
+
return { outcomeId: o.id, status: "not-decidable-by-replay", floorSeconds, target: written2, reason: "only an upper bound in seconds is decidable" };
|
|
97183
|
+
}
|
|
97184
|
+
const breaks = target.op === "<" ? floorSeconds >= target.value : floorSeconds > target.value;
|
|
97185
|
+
return breaks ? { outcomeId: o.id, status: "miss", floorSeconds, target: written2, reason: `${floor}: the fastest path already breaks ${written2}` } : { outcomeId: o.id, status: "not-decidable", floorSeconds, target: written2, reason: `${floor}, within ${written2}` };
|
|
97186
|
+
}
|
|
97187
|
+
|
|
96833
97188
|
// src/ux-usability/setup.ts
|
|
96834
97189
|
import { join as join46, resolve as resolvePath11 } from "node:path";
|
|
96835
97190
|
|
|
@@ -97259,7 +97614,7 @@ function controlKey2(c) {
|
|
|
97259
97614
|
}
|
|
97260
97615
|
|
|
97261
97616
|
// ../ux/dist/route.js
|
|
97262
|
-
function
|
|
97617
|
+
function routeOf6(url2) {
|
|
97263
97618
|
try {
|
|
97264
97619
|
return urlTemplate(new URL(url2).pathname || "/");
|
|
97265
97620
|
} catch {
|
|
@@ -97408,7 +97763,7 @@ function a11yChecks(evidence, rubric) {
|
|
|
97408
97763
|
const q = rubric.get(id)?.questions[0];
|
|
97409
97764
|
return q?.severity ?? "minor";
|
|
97410
97765
|
};
|
|
97411
|
-
const route =
|
|
97766
|
+
const route = routeOf6(evidence.url);
|
|
97412
97767
|
const controlByRef = new Map(evidence.controls.map((c) => [`control:${c.index}`, c]));
|
|
97413
97768
|
const labelOf2 = (refId) => {
|
|
97414
97769
|
const c = controlByRef.get(refId);
|
|
@@ -97709,6 +98064,7 @@ var SIGNAL_RULES = {
|
|
|
97709
98064
|
"duplicate-create": { id: "signal-duplicate-create", principle: "Error prevention", citation: NNG, severity: "major" },
|
|
97710
98065
|
"failed-submit": { id: "signal-failed-submit", principle: "Help users recognize, diagnose, and recover from errors", citation: NNG, severity: "major" },
|
|
97711
98066
|
"url-mismatch": { id: "signal-url-mismatch", principle: "Consistency and standards", citation: NNG, severity: "minor" },
|
|
98067
|
+
"repeated-advance": { id: "signal-repeated-advance", principle: "Flexibility and efficiency of use", citation: NNG, severity: "minor" },
|
|
97712
98068
|
"horizontal-overflow": { id: "signal-horizontal-overflow", principle: "Flexibility and efficiency of use", citation: NNG, severity: "major" },
|
|
97713
98069
|
"vertical-clipping": { id: "signal-vertical-clipping", principle: "Visibility of system status", citation: NNG, severity: "major" }
|
|
97714
98070
|
};
|
|
@@ -97750,7 +98106,7 @@ function makeSignalFinding(input2) {
|
|
|
97750
98106
|
recommendation: input2.recommendation.trim(),
|
|
97751
98107
|
tier: "signal",
|
|
97752
98108
|
screenId: input2.screenId,
|
|
97753
|
-
route:
|
|
98109
|
+
route: routeOf6(input2.url),
|
|
97754
98110
|
controls: [...input2.controls ?? []],
|
|
97755
98111
|
quotes: [...input2.quotes ?? []],
|
|
97756
98112
|
occurrences: input2.occurrences ?? 1,
|
|
@@ -97791,6 +98147,14 @@ function quoteLine(text2, needle) {
|
|
|
97791
98147
|
const t = line2.replace(/\s+/g, " ").trim();
|
|
97792
98148
|
return t.length <= 160 ? t : `${t.slice(0, 157)}...`;
|
|
97793
98149
|
}
|
|
98150
|
+
function labelOf(s3) {
|
|
98151
|
+
const quoted = /"([^"]{2,160})"/.exec(s3.target ?? "")?.[1];
|
|
98152
|
+
const raw = quoted ?? s3.descriptor?.name ?? s3.descriptor?.text ?? null;
|
|
98153
|
+
if (raw === null || raw === void 0)
|
|
98154
|
+
return null;
|
|
98155
|
+
const t = raw.replace(/[→←↗›»…]+/g, " ").replace(/\s+/g, " ").trim();
|
|
98156
|
+
return t.length >= 2 ? t : null;
|
|
98157
|
+
}
|
|
97794
98158
|
|
|
97795
98159
|
// ../ux/dist/signals/basic.js
|
|
97796
98160
|
function detectHungRequests(capture2, opts = {}) {
|
|
@@ -97979,7 +98343,7 @@ function detectInternalIds(capture2) {
|
|
|
97979
98343
|
if (typed.includes(id))
|
|
97980
98344
|
continue;
|
|
97981
98345
|
const line2 = quoteLine(screen.visibleText, id);
|
|
97982
|
-
const key2 = `${
|
|
98346
|
+
const key2 = `${routeOf6(screen.url)}|${pattern.name}`;
|
|
97983
98347
|
const g = groups.get(key2) ?? { pattern, ids: /* @__PURE__ */ new Set(), lines: /* @__PURE__ */ new Set(), screens: [], intentional: true };
|
|
97984
98348
|
g.ids.add(id);
|
|
97985
98349
|
g.lines.add(line2);
|
|
@@ -98080,6 +98444,71 @@ function detectInertControls(capture2) {
|
|
|
98080
98444
|
return out;
|
|
98081
98445
|
}
|
|
98082
98446
|
|
|
98447
|
+
// ../ux/dist/signals/effort.js
|
|
98448
|
+
var REPEATED_ADVANCE_MIN = 3;
|
|
98449
|
+
var ADVANCE_NAME = /^(continue|next|next step|confirm|ok|okay|proceed|yes|go on)$/i;
|
|
98450
|
+
var INPUT_OPS = /* @__PURE__ */ new Set(["type", "select", "send", "upload", "fill", "edit_text", "check", "uncheck"]);
|
|
98451
|
+
var PASSIVE_OPS = /* @__PURE__ */ new Set(["wait", "scroll_down", "scroll_up"]);
|
|
98452
|
+
function advanced(capture2, s3) {
|
|
98453
|
+
const before = capture2.screens.filter((x) => x.step === s3.step).pop();
|
|
98454
|
+
const after = capture2.screens.find((x) => x.step > s3.step);
|
|
98455
|
+
if (before === void 0 || after === void 0 || routeOf6(after.url) !== routeOf6(before.url))
|
|
98456
|
+
return void 0;
|
|
98457
|
+
return after.visibleText !== before.visibleText ? after : void 0;
|
|
98458
|
+
}
|
|
98459
|
+
function detectRepeatedAdvances(capture2) {
|
|
98460
|
+
const streaks = [];
|
|
98461
|
+
let current2 = [];
|
|
98462
|
+
let key2 = null;
|
|
98463
|
+
const close = () => {
|
|
98464
|
+
if (current2.length >= REPEATED_ADVANCE_MIN)
|
|
98465
|
+
streaks.push(current2);
|
|
98466
|
+
current2 = [];
|
|
98467
|
+
key2 = null;
|
|
98468
|
+
};
|
|
98469
|
+
for (const s3 of capture2.steps) {
|
|
98470
|
+
if (s3.op !== null && PASSIVE_OPS.has(s3.op))
|
|
98471
|
+
continue;
|
|
98472
|
+
const label2 = s3.op === "click" && s3.actOk ? labelOf(s3) : null;
|
|
98473
|
+
const after = label2 !== null && ADVANCE_NAME.test(label2) ? advanced(capture2, s3) : void 0;
|
|
98474
|
+
if (label2 === null || after === void 0 || s3.op !== null && INPUT_OPS.has(s3.op)) {
|
|
98475
|
+
close();
|
|
98476
|
+
continue;
|
|
98477
|
+
}
|
|
98478
|
+
const k = `${routeOf6(s3.url)}|${label2.toLowerCase()}`;
|
|
98479
|
+
if (key2 !== null && k !== key2)
|
|
98480
|
+
close();
|
|
98481
|
+
key2 = k;
|
|
98482
|
+
current2.push({ step: s3, after });
|
|
98483
|
+
}
|
|
98484
|
+
close();
|
|
98485
|
+
return streaks.map((hits) => {
|
|
98486
|
+
const label2 = labelOf(hits[0].step) ?? "the advance control";
|
|
98487
|
+
const n3 = hits.length;
|
|
98488
|
+
const steps = hits.map((h) => h.step.step);
|
|
98489
|
+
const last = hits[n3 - 1];
|
|
98490
|
+
return makeSignalFinding({
|
|
98491
|
+
kind: "repeated-advance",
|
|
98492
|
+
confidence: n3 >= 5 ? 0.8 : n3 === 4 ? 0.7 : 0.6,
|
|
98493
|
+
url: last.after.url,
|
|
98494
|
+
screenId: last.after.signature,
|
|
98495
|
+
observation: `The flow needed "${label2}" ${n3} times in a row (steps ${steps.join(", ")}) with nothing entered between them, each only moving to the next screen.`,
|
|
98496
|
+
userImpact: "Every extra confirmation costs the user time and attention without adding a decision; repeated identical steps also read as the page not responding.",
|
|
98497
|
+
recommendation: `Fold the repeated "${label2}" screens into one step, or let the user confirm once.`,
|
|
98498
|
+
controls: [label2],
|
|
98499
|
+
occurrences: n3,
|
|
98500
|
+
screenIds: [...new Set(hits.map((h) => h.after.signature))],
|
|
98501
|
+
evidence: {
|
|
98502
|
+
kind: "repeated-advance",
|
|
98503
|
+
steps,
|
|
98504
|
+
requests: [],
|
|
98505
|
+
...last.after.screenshot === void 0 ? {} : { screenshot: last.after.screenshot },
|
|
98506
|
+
detail: `${n3} consecutive successful "${label2}" clicks on ${routeOf6(last.after.url)} with no input between them, each changing the page text`
|
|
98507
|
+
}
|
|
98508
|
+
});
|
|
98509
|
+
});
|
|
98510
|
+
}
|
|
98511
|
+
|
|
98083
98512
|
// ../ux/dist/signals/journey.js
|
|
98084
98513
|
var ERROR_TEXT = /\b(error|errors|failed|failure|went wrong|unavailable|couldn['’]?t|could not|can['’]?t|cannot|unable|try again|invalid|problem|denied|forbidden|expired|not (?:be )?(?:saved|sent|found|recorded))\b/i;
|
|
98085
98514
|
var GENERIC_ERROR = /\b(something went wrong|an? (?:unexpected |unknown )?error (?:has )?occurred|unexpected error|oops|try again later)\b/i;
|
|
@@ -98088,14 +98517,6 @@ var FIELD_OPS = /* @__PURE__ */ new Set(["type", "select"]);
|
|
|
98088
98517
|
function normLine(s3) {
|
|
98089
98518
|
return s3.replace(/\s+/g, " ").trim().toLowerCase();
|
|
98090
98519
|
}
|
|
98091
|
-
function labelOf(s3) {
|
|
98092
|
-
const quoted = /"([^"]{2,160})"/.exec(s3.target ?? "")?.[1];
|
|
98093
|
-
const raw = quoted ?? s3.descriptor?.name ?? s3.descriptor?.text ?? null;
|
|
98094
|
-
if (raw === null || raw === void 0)
|
|
98095
|
-
return null;
|
|
98096
|
-
const t = raw.replace(/[→←↗›»…]+/g, " ").replace(/\s+/g, " ").trim();
|
|
98097
|
-
return t.length >= 2 ? t : null;
|
|
98098
|
-
}
|
|
98099
98520
|
function screenBefore(capture2, step) {
|
|
98100
98521
|
return capture2.screens.filter((s3) => s3.step <= step).pop();
|
|
98101
98522
|
}
|
|
@@ -98134,7 +98555,7 @@ function detectStuckJobs(capture2, opts = {}) {
|
|
|
98134
98555
|
const label2 = labelOf(start);
|
|
98135
98556
|
if (label2 === null)
|
|
98136
98557
|
continue;
|
|
98137
|
-
const route =
|
|
98558
|
+
const route = routeOf6(start.url);
|
|
98138
98559
|
const key2 = `${route}|${normLine(label2)}`;
|
|
98139
98560
|
if (reported.has(key2))
|
|
98140
98561
|
continue;
|
|
@@ -98143,7 +98564,7 @@ function detectStuckJobs(capture2, opts = {}) {
|
|
|
98143
98564
|
continue;
|
|
98144
98565
|
const window2 = [];
|
|
98145
98566
|
for (const s3 of capture2.steps.filter((x) => x.step > start.step)) {
|
|
98146
|
-
if (
|
|
98567
|
+
if (routeOf6(s3.url) !== route)
|
|
98147
98568
|
break;
|
|
98148
98569
|
const other = s3.op !== null && START_OPS.has(s3.op) && controlKey3(s3) !== controlKey3(start);
|
|
98149
98570
|
if (other && capture2.requests.some((r) => r.step === s3.step && isWrite(r) && okStatus(r)))
|
|
@@ -98151,7 +98572,7 @@ function detectStuckJobs(capture2, opts = {}) {
|
|
|
98151
98572
|
window2.push(s3);
|
|
98152
98573
|
}
|
|
98153
98574
|
const lastStep = window2.length > 0 ? window2[window2.length - 1].step : start.step;
|
|
98154
|
-
const screens = capture2.screens.filter((s3) => s3.step > start.step && s3.step <= lastStep + 1 &&
|
|
98575
|
+
const screens = capture2.screens.filter((s3) => s3.step > start.step && s3.step <= lastStep + 1 && routeOf6(s3.url) === route);
|
|
98155
98576
|
if (screens.length === 0)
|
|
98156
98577
|
continue;
|
|
98157
98578
|
const reclicks = window2.filter((s3) => s3.op === start.op && s3.actOk && controlKey3(s3) === controlKey3(start));
|
|
@@ -98163,7 +98584,17 @@ function detectStuckJobs(capture2, opts = {}) {
|
|
|
98163
98584
|
}
|
|
98164
98585
|
const polls = [...polled.values()].sort((a, b2) => b2.length - a.length)[0] ?? [];
|
|
98165
98586
|
const inProgress = reclicks.length > 0 || waits.length > 0 || busy.length > 0 || polls.length >= 3;
|
|
98587
|
+
const before = screenBefore(capture2, start.step);
|
|
98588
|
+
const was = new Set(before === void 0 ? [] : linesOf(before.visibleText).map(normLine));
|
|
98589
|
+
const labelLine = normLine(label2);
|
|
98590
|
+
const own2 = new Set([start.value, start.message, ...capture2.typedValues ?? []].filter((v) => v !== void 0 && v.trim().length > 0).map(normLine));
|
|
98166
98591
|
const last = screens[screens.length - 1];
|
|
98592
|
+
const completed = before !== void 0 && !last.busy && !BUSY_TEXT2.test(last.visibleText) && screens.some((s3) => linesOf(s3.visibleText).some((line2) => {
|
|
98593
|
+
const n3 = normLine(line2);
|
|
98594
|
+
return n3.length >= 2 && n3 !== labelLine && !was.has(n3) && !own2.has(n3) && !BUSY_TEXT2.test(line2);
|
|
98595
|
+
}));
|
|
98596
|
+
if (completed)
|
|
98597
|
+
continue;
|
|
98167
98598
|
const offered = reclicks.length > 0 || normLine(last.visibleText).includes(normLine(label2));
|
|
98168
98599
|
const endedHere = !capture2.screens.some((s3) => s3.step > last.step);
|
|
98169
98600
|
const startedAt = Math.min(...writes.map((w) => w.startedAt));
|
|
@@ -98342,7 +98773,7 @@ function detectFailedSubmits(capture2) {
|
|
|
98342
98773
|
const after = screenAfter(capture2, s3.step);
|
|
98343
98774
|
if (after === void 0)
|
|
98344
98775
|
continue;
|
|
98345
|
-
if (before !== void 0 &&
|
|
98776
|
+
if (before !== void 0 && routeOf6(after.url) !== routeOf6(before.url))
|
|
98346
98777
|
continue;
|
|
98347
98778
|
const lines = linesOf(`${after.visibleText}
|
|
98348
98779
|
${s3.reply ?? ""}`);
|
|
@@ -98411,15 +98842,15 @@ function headingOf(s3) {
|
|
|
98411
98842
|
function detectUrlMismatches(capture2) {
|
|
98412
98843
|
const hits = /* @__PURE__ */ new Map();
|
|
98413
98844
|
capture2.screens.forEach((b2, i) => {
|
|
98414
|
-
const route =
|
|
98845
|
+
const route = routeOf6(b2.url);
|
|
98415
98846
|
const heading = headingOf(b2);
|
|
98416
98847
|
if (heading.length < 3)
|
|
98417
98848
|
return;
|
|
98418
98849
|
const { words: words3, known } = routeWords2(route);
|
|
98419
98850
|
if (words3.length === 0 || relates(heading, words3))
|
|
98420
98851
|
return;
|
|
98421
|
-
const other = capture2.screens.find((s3) =>
|
|
98422
|
-
const prior = capture2.screens.slice(0, i).reverse().find((s3) =>
|
|
98852
|
+
const other = capture2.screens.find((s3) => routeOf6(s3.url) !== route && normLine(headingOf(s3)) === normLine(heading) && relates(heading, routeWords2(routeOf6(s3.url)).words));
|
|
98853
|
+
const prior = capture2.screens.slice(0, i).reverse().find((s3) => routeOf6(s3.url) === route && normLine(headingOf(s3)) !== normLine(heading));
|
|
98423
98854
|
const transitioned = known && prior !== void 0 && relates(headingOf(prior), words3);
|
|
98424
98855
|
if (other === void 0 && !transitioned)
|
|
98425
98856
|
return;
|
|
@@ -98430,7 +98861,7 @@ function detectUrlMismatches(capture2) {
|
|
|
98430
98861
|
route,
|
|
98431
98862
|
known,
|
|
98432
98863
|
...transitioned ? { was: headingOf(prior) } : {},
|
|
98433
|
-
...other === void 0 ? {} : { elsewhere:
|
|
98864
|
+
...other === void 0 ? {} : { elsewhere: routeOf6(other.url) }
|
|
98434
98865
|
};
|
|
98435
98866
|
g.screens.push(b2);
|
|
98436
98867
|
hits.set(k, g);
|
|
@@ -98475,6 +98906,7 @@ function detectSignals(capture2, opts = {}) {
|
|
|
98475
98906
|
...withoutSubsumedDuplicateWrites(detectDuplicateWrites(capture2, opts), journey),
|
|
98476
98907
|
...detectInternalIds(capture2),
|
|
98477
98908
|
...detectInertControls(capture2),
|
|
98909
|
+
...detectRepeatedAdvances(capture2),
|
|
98478
98910
|
...journey
|
|
98479
98911
|
];
|
|
98480
98912
|
}
|
|
@@ -98506,6 +98938,7 @@ var SIGNAL_IMPACT = {
|
|
|
98506
98938
|
"internal-id": "confused",
|
|
98507
98939
|
"inert-control": "confused",
|
|
98508
98940
|
"url-mismatch": "confused",
|
|
98941
|
+
"repeated-advance": "slowed",
|
|
98509
98942
|
"horizontal-overflow": "confused",
|
|
98510
98943
|
"vertical-clipping": "confused"
|
|
98511
98944
|
};
|
|
@@ -98533,7 +98966,7 @@ function detectFriction(capture2, outcome) {
|
|
|
98533
98966
|
};
|
|
98534
98967
|
const routesAt = (nums) => {
|
|
98535
98968
|
const set2 = new Set(nums);
|
|
98536
|
-
return [.../* @__PURE__ */ new Set([...steps.filter((s3) => set2.has(s3.step)).map((s3) =>
|
|
98969
|
+
return [.../* @__PURE__ */ new Set([...steps.filter((s3) => set2.has(s3.step)).map((s3) => routeOf6(s3.url)), ...capture2.screens.filter((s3) => set2.has(s3.step)).map((s3) => routeOf6(s3.url))])];
|
|
98537
98970
|
};
|
|
98538
98971
|
const add = (kind, impact, nums, detail) => {
|
|
98539
98972
|
const sorted2 = [...new Set(nums)].filter((n3) => n3 > 0).sort((a, b2) => a - b2);
|
|
@@ -98624,7 +99057,7 @@ function detectFriction(capture2, outcome) {
|
|
|
98624
99057
|
const blocking = reqs.some((r) => WRITE_METHODS3.has(r.method.toUpperCase()) && (r.failed === true || (r.status ?? 0) >= 500));
|
|
98625
99058
|
add("error", blocking ? "blocked" : "slowed", [step], `${reqs.map((r) => `${r.endpoint} \u2192 ${r.failed === true ? "failed" : r.status}`).join(", ")} after step ${step}`);
|
|
98626
99059
|
}
|
|
98627
|
-
const seq = capture2.screens.map((s3) => ({ route:
|
|
99060
|
+
const seq = capture2.screens.map((s3) => ({ route: routeOf6(s3.url), step: s3.step }));
|
|
98628
99061
|
for (let x = 2; x < seq.length; x++) {
|
|
98629
99062
|
const here = seq[x];
|
|
98630
99063
|
if (seq[x - 1].route === here.route)
|
|
@@ -98651,7 +99084,7 @@ function detectFriction(capture2, outcome) {
|
|
|
98651
99084
|
continue;
|
|
98652
99085
|
if (between.some((r) => r.actOk && (r.op === "click" || r.op === "send" || r.op === "select")))
|
|
98653
99086
|
continue;
|
|
98654
|
-
add("abandoned", "confused", [s3.step, rest[leftAt].step], `a value typed at step ${s3.step} was left unsubmitted when the run moved to ${
|
|
99087
|
+
add("abandoned", "confused", [s3.step, rest[leftAt].step], `a value typed at step ${s3.step} was left unsubmitted when the run moved to ${routeOf6(rest[leftAt].url)}`);
|
|
98655
99088
|
}
|
|
98656
99089
|
if (outcome?.status === "incomplete" && steps.length > 0) {
|
|
98657
99090
|
const tail = steps.slice(-3).map((s3) => s3.step);
|
|
@@ -99229,8 +99662,8 @@ function boundTargetOptions(controls, point, steps, limit = MAX_CHOICE_OPTIONS -
|
|
|
99229
99662
|
const acted = steps.filter((s3) => point.steps.includes(s3.step));
|
|
99230
99663
|
const words3 = new Set(normalizeText(point.detail).split(" ").filter((w) => w.length >= 3));
|
|
99231
99664
|
const score2 = (c) => (acted.some((s3) => stepNames(s3, c)) ? 400 : 0) + (normalizeText(c.name).split(" ").some((w) => words3.has(w)) ? 60 : 0) + (c.enabled ? 10 : 0);
|
|
99232
|
-
const
|
|
99233
|
-
return controls.filter((c) =>
|
|
99665
|
+
const keep2 = new Set(controls.map((c, i) => ({ c, i, s: score2(c) })).sort((a, b2) => b2.s - a.s || a.i - b2.i).slice(0, limit).map((x) => x.c.index));
|
|
99666
|
+
return controls.filter((c) => keep2.has(c.index));
|
|
99234
99667
|
}
|
|
99235
99668
|
function frictionText(p) {
|
|
99236
99669
|
return `${p.kind} (steps ${p.steps.join(", ")}): ${p.detail}`.slice(0, 600);
|
|
@@ -99259,7 +99692,7 @@ async function analyzeClaims(request, deps) {
|
|
|
99259
99692
|
for (const raw of request.screens) {
|
|
99260
99693
|
try {
|
|
99261
99694
|
const red = redactEvidence(raw, secrets, deps.redactor);
|
|
99262
|
-
screens.push({ raw, red, route:
|
|
99695
|
+
screens.push({ raw, red, route: routeOf6(red.url) });
|
|
99263
99696
|
} catch (cause) {
|
|
99264
99697
|
return { kind: "failed", reason: `redaction failed: ${message(cause)}`, screenId: raw.screenId };
|
|
99265
99698
|
}
|
|
@@ -99749,7 +100182,7 @@ ${f2.route}
|
|
|
99749
100182
|
${element}`, "utf8").digest("hex").slice(0, 16);
|
|
99750
100183
|
}
|
|
99751
100184
|
function contractRouteTemplate(urlOrRoute) {
|
|
99752
|
-
return
|
|
100185
|
+
return routeOf6(urlOrRoute).split("/").map((seg) => /^:[A-Za-z][A-Za-z0-9_]{0,63}$/.test(seg) ? `{${seg.slice(1)}}` : seg).join("/");
|
|
99753
100186
|
}
|
|
99754
100187
|
|
|
99755
100188
|
// ../ux/dist/rubric/schema.js
|
|
@@ -101091,7 +101524,7 @@ function planGuardProbes(screens, secrets, safety) {
|
|
|
101091
101524
|
const refused = [];
|
|
101092
101525
|
for (const screen of screens) {
|
|
101093
101526
|
const red = redactEvidence(screen, secrets);
|
|
101094
|
-
const route =
|
|
101527
|
+
const route = routeOf6(red.url);
|
|
101095
101528
|
screen.controls.forEach((c, i) => {
|
|
101096
101529
|
if (controlRisk(c.name, c.role)?.risk !== "destructive") return;
|
|
101097
101530
|
const rc = red.controls[i];
|
|
@@ -101850,11 +102283,11 @@ var MAX_SCAN_DEPTH = 8;
|
|
|
101850
102283
|
function scanRuns(dirs) {
|
|
101851
102284
|
const seen = /* @__PURE__ */ new Set();
|
|
101852
102285
|
const runs = [];
|
|
101853
|
-
const
|
|
102286
|
+
const walk2 = (dir, depth) => {
|
|
101854
102287
|
for (const entry of readdirSync8(dir, { withFileTypes: true }).sort((a, b2) => a.name.localeCompare(b2.name))) {
|
|
101855
102288
|
const path2 = resolve13(dir, entry.name);
|
|
101856
102289
|
if (entry.isDirectory()) {
|
|
101857
|
-
if (depth < MAX_SCAN_DEPTH && !entry.name.startsWith(".") && entry.name !== "node_modules")
|
|
102290
|
+
if (depth < MAX_SCAN_DEPTH && !entry.name.startsWith(".") && entry.name !== "node_modules") walk2(path2, depth + 1);
|
|
101858
102291
|
continue;
|
|
101859
102292
|
}
|
|
101860
102293
|
if (!entry.isFile() || !isResultName(entry.name) || seen.has(path2)) continue;
|
|
@@ -101866,7 +102299,7 @@ function scanRuns(dirs) {
|
|
|
101866
102299
|
};
|
|
101867
102300
|
for (const dir of dirs) {
|
|
101868
102301
|
if (!existsSync23(dir) || !statSync9(dir).isDirectory()) continue;
|
|
101869
|
-
|
|
102302
|
+
walk2(dir, 0);
|
|
101870
102303
|
}
|
|
101871
102304
|
return runs.sort((a, b2) => (a.startedAt ?? "").localeCompare(b2.startedAt ?? "") || a.runId.localeCompare(b2.runId));
|
|
101872
102305
|
}
|
|
@@ -103328,6 +103761,29 @@ var REAL_RUNNERS = {
|
|
|
103328
103761
|
usability: runUsabilityMission,
|
|
103329
103762
|
verifyFix: runVerifyFix
|
|
103330
103763
|
};
|
|
103764
|
+
async function addOutcomeTargets(items, executed, opts) {
|
|
103765
|
+
const catalogDir = opts.projectDir === void 0 ? findProjectDir() : opts.projectDir;
|
|
103766
|
+
if (catalogDir === null) return;
|
|
103767
|
+
const catalogs = /* @__PURE__ */ new Map();
|
|
103768
|
+
const catalogFor = (journeysDir) => {
|
|
103769
|
+
const dir = resolve16(journeysDir);
|
|
103770
|
+
let c = catalogs.get(dir);
|
|
103771
|
+
if (c === void 0) {
|
|
103772
|
+
c = loadCatalog(catalogDir, dir).catch(() => null);
|
|
103773
|
+
catalogs.set(dir, c);
|
|
103774
|
+
}
|
|
103775
|
+
return c;
|
|
103776
|
+
};
|
|
103777
|
+
for (const [idx, { item }] of executed.entries()) {
|
|
103778
|
+
const report2 = items[idx];
|
|
103779
|
+
if (report2 === void 0 || item.kind !== "journey" || report2.baseline === void 0) continue;
|
|
103780
|
+
const catalog = await catalogFor(item.t.target.journeysDir ?? opts.journeysDir);
|
|
103781
|
+
const jobId = catalog?.journeys.find((j) => j.id === item.name)?.job;
|
|
103782
|
+
const outcomes = catalog === null || jobId === void 0 ? void 0 : findJob(catalog, jobId)?.job.desiredOutcomes;
|
|
103783
|
+
if (outcomes === void 0 || outcomes.length === 0) continue;
|
|
103784
|
+
items[idx] = { ...report2, outcomeTargets: evaluateOutcomeTargets(outcomes, report2.baseline) };
|
|
103785
|
+
}
|
|
103786
|
+
}
|
|
103331
103787
|
async function approvalDefects(opts, req) {
|
|
103332
103788
|
const dirs = [...new Set([opts.journeysDir, ...opts.suite.targets.flatMap((t) => t.journeysDir === void 0 ? [] : [t.journeysDir])].map((d) => resolve16(d)))];
|
|
103333
103789
|
const catalogs = [];
|
|
@@ -103553,6 +104009,7 @@ async function runCheck(opts) {
|
|
|
103553
104009
|
...ex.locatorHealth === void 0 ? {} : { locatorHealth: ex.locatorHealth }
|
|
103554
104010
|
};
|
|
103555
104011
|
});
|
|
104012
|
+
await addOutcomeTargets(itemReports, executed, opts);
|
|
103556
104013
|
const details = executed.flatMap(
|
|
103557
104014
|
({ item, ex }) => item.kind === "journey" && ex?.locatorDetail !== void 0 ? [{ name: item.name, detail: ex.locatorDetail }] : []
|
|
103558
104015
|
);
|
|
@@ -104321,8 +104778,8 @@ function bundleDemos(input2, journeys, warn) {
|
|
|
104321
104778
|
if (demos.length > LIMITS.demos) {
|
|
104322
104779
|
warn.push(`${demos.length} demos: only the first ${LIMITS.demos} are exported`);
|
|
104323
104780
|
const kept = new Set(demos.slice(0, LIMITS.demos).map((d) => `media/${d.journey}/`));
|
|
104324
|
-
const
|
|
104325
|
-
return { demos: demos.slice(0, LIMITS.demos), files: files.filter((f2) =>
|
|
104781
|
+
const keep2 = (p) => [...kept].some((k) => p.startsWith(k));
|
|
104782
|
+
return { demos: demos.slice(0, LIMITS.demos), files: files.filter((f2) => keep2(f2.path)), media: media.filter((m) => keep2(m.path)) };
|
|
104326
104783
|
}
|
|
104327
104784
|
return { demos, files, media };
|
|
104328
104785
|
}
|
|
@@ -105018,7 +105475,7 @@ function findingDropWarning(fingerprint2, kind, issues) {
|
|
|
105018
105475
|
function bundleFindings(src) {
|
|
105019
105476
|
const warnings = [];
|
|
105020
105477
|
const latest = /* @__PURE__ */ new Map();
|
|
105021
|
-
const
|
|
105478
|
+
const keep2 = (m) => {
|
|
105022
105479
|
if (m === null) return;
|
|
105023
105480
|
if ("drop" in m) {
|
|
105024
105481
|
warnings.push(`${m.drop} \u2014 left out of the bundle`);
|
|
@@ -105033,10 +105490,10 @@ function bundleFindings(src) {
|
|
|
105033
105490
|
if (prev === void 0 || prev.at <= m.finding.at) latest.set(m.finding.fingerprint, m.finding);
|
|
105034
105491
|
};
|
|
105035
105492
|
const byPath = new Map(src.runs.map((r) => [r.path, r]));
|
|
105036
|
-
for (const d of consolidate(src.runs))
|
|
105493
|
+
for (const d of consolidate(src.runs)) keep2(defectFinding(d, byPath));
|
|
105037
105494
|
for (const { report: report2, run } of src.uxReports) {
|
|
105038
105495
|
if (!isRecord(report2) || !Array.isArray(report2.findings)) continue;
|
|
105039
|
-
for (const f2 of report2.findings) if (isRecord(f2))
|
|
105496
|
+
for (const f2 of report2.findings) if (isRecord(f2)) keep2(uxFinding(f2, run));
|
|
105040
105497
|
}
|
|
105041
105498
|
let findings = [...latest.values()].sort((a, b2) => a.at.localeCompare(b2.at) || a.fingerprint.localeCompare(b2.fingerprint));
|
|
105042
105499
|
if (findings.length > MAX_FINDINGS) {
|
|
@@ -116451,8 +116908,8 @@ function confineSweepFixture(raw, root2) {
|
|
|
116451
116908
|
realRoot = realpathSync5(root2);
|
|
116452
116909
|
} catch {
|
|
116453
116910
|
}
|
|
116454
|
-
const
|
|
116455
|
-
if (
|
|
116911
|
+
const sensitive2 = fixtureSensitiveReason(real, realRoot) ?? fixtureSensitiveReason(absolute, resolve23(root2));
|
|
116912
|
+
if (sensitive2 !== null) throw fixtureSensitive(absolute, root2, sensitive2);
|
|
116456
116913
|
return absolute;
|
|
116457
116914
|
}
|
|
116458
116915
|
function tsvTags(cell2, where, problems) {
|