@swmansion/argent 0.20.1-next.7 → 0.20.1-next.9
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/tool-server.cjs +271 -108
- package/package.json +1 -1
- package/skills/argent-create-flow/references/flow-yaml.md +3 -1
- package/skills/argent-create-flow/references/live-authoring.md +2 -0
- package/skills/argent-create-flow/references/reliability-and-recovery.md +3 -0
- package/skills/argent-qa-flows/SKILL.md +1 -1
package/dist/tool-server.cjs
CHANGED
|
@@ -16776,10 +16776,10 @@ var require_browser = __commonJS({
|
|
|
16776
16776
|
exports2.useColors = useColors;
|
|
16777
16777
|
exports2.storage = localstorage();
|
|
16778
16778
|
exports2.destroy = /* @__PURE__ */ (() => {
|
|
16779
|
-
let
|
|
16779
|
+
let warned2 = false;
|
|
16780
16780
|
return () => {
|
|
16781
|
-
if (!
|
|
16782
|
-
|
|
16781
|
+
if (!warned2) {
|
|
16782
|
+
warned2 = true;
|
|
16783
16783
|
console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
|
|
16784
16784
|
}
|
|
16785
16785
|
};
|
|
@@ -109158,12 +109158,16 @@ function settleWithin(p, ms, signal) {
|
|
|
109158
109158
|
};
|
|
109159
109159
|
p.then(
|
|
109160
109160
|
(value) => finish({ type: "value", value }),
|
|
109161
|
-
(err) =>
|
|
109161
|
+
(err) => {
|
|
109162
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
109163
|
+
finish({ type: "error", error: cause.message, cause });
|
|
109164
|
+
}
|
|
109162
109165
|
);
|
|
109163
109166
|
if (signal?.aborted) return finish({ type: "aborted" });
|
|
109164
109167
|
const onAbort = () => finish({ type: "aborted" });
|
|
109165
109168
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
109166
109169
|
teardown.push(() => signal?.removeEventListener("abort", onAbort));
|
|
109170
|
+
if (ms === void 0) return;
|
|
109167
109171
|
const timer = setTimeout(() => finish({ type: "timeout" }), Math.max(0, ms));
|
|
109168
109172
|
teardown.push(() => clearTimeout(timer));
|
|
109169
109173
|
});
|
|
@@ -133794,89 +133798,130 @@ var ARGENT_PROFILER_PREFIX = "__argent_";
|
|
|
133794
133798
|
function isArgentProfilerFunction(name) {
|
|
133795
133799
|
return name.startsWith(ARGENT_PROFILER_PREFIX);
|
|
133796
133800
|
}
|
|
133797
|
-
function buildCpuSampleIndex(cpuProfile
|
|
133801
|
+
function buildCpuSampleIndex(cpuProfile) {
|
|
133798
133802
|
const { nodes, samples, timeDeltas, startTime, endTime } = cpuProfile;
|
|
133799
133803
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
133800
133804
|
for (const node of nodes) {
|
|
133801
133805
|
nodeMap.set(node.id, node);
|
|
133802
133806
|
}
|
|
133803
|
-
const cpuStartMs = startTime / 1e3;
|
|
133804
|
-
let clockOffsetMs = 0;
|
|
133805
|
-
if (firstCommitTimestampMs !== null && firstCommitTimestampMs > 0) {
|
|
133806
|
-
const diff = firstCommitTimestampMs - cpuStartMs;
|
|
133807
|
-
if (Math.abs(diff) > 1e3) {
|
|
133808
|
-
clockOffsetMs = diff;
|
|
133809
|
-
}
|
|
133810
|
-
}
|
|
133811
133807
|
const timestampsMs = new Float64Array(samples.length);
|
|
133812
|
-
|
|
133808
|
+
const intervalStartsMs = new Float64Array(samples.length);
|
|
133809
|
+
let accumulatedUs = 0;
|
|
133813
133810
|
for (let i = 0; i < samples.length; i++) {
|
|
133814
|
-
|
|
133815
|
-
|
|
133811
|
+
const raw = timeDeltas[i];
|
|
133812
|
+
const delta = Number.isFinite(raw) && (raw ?? 0) >= 0 ? raw : 0;
|
|
133813
|
+
intervalStartsMs[i] = accumulatedUs / 1e3;
|
|
133814
|
+
accumulatedUs += delta;
|
|
133815
|
+
timestampsMs[i] = accumulatedUs / 1e3;
|
|
133816
133816
|
}
|
|
133817
133817
|
return {
|
|
133818
|
+
childToParent: buildChildToParent(nodeMap),
|
|
133818
133819
|
timestampsMs,
|
|
133820
|
+
intervalStartsMs,
|
|
133819
133821
|
sampleNodeIds: samples,
|
|
133820
133822
|
nodeMap,
|
|
133821
133823
|
durationMs: (endTime - startTime) / 1e3
|
|
133822
133824
|
};
|
|
133823
133825
|
}
|
|
133826
|
+
var IDLE_FRAME_NAMES = /* @__PURE__ */ new Set(["(idle)", "(program)", "(root)", "[idle]", "[root]"]);
|
|
133827
|
+
function isIdleFrame(name) {
|
|
133828
|
+
return !name || IDLE_FRAME_NAMES.has(name);
|
|
133829
|
+
}
|
|
133824
133830
|
function queryCpuWindow(index, startMs, endMs, topN2 = 5) {
|
|
133825
|
-
const { timestampsMs, sampleNodeIds, nodeMap } = index;
|
|
133831
|
+
const { timestampsMs, intervalStartsMs, sampleNodeIds, nodeMap } = index;
|
|
133832
|
+
const n = timestampsMs.length;
|
|
133833
|
+
const sampleRangeMs = {
|
|
133834
|
+
start: n > 0 ? intervalStartsMs[0] : 0,
|
|
133835
|
+
end: n > 0 ? timestampsMs[n - 1] : 0
|
|
133836
|
+
};
|
|
133837
|
+
const empty2 = {
|
|
133838
|
+
hotspots: [],
|
|
133839
|
+
samplesInWindow: 0,
|
|
133840
|
+
coveredMs: 0,
|
|
133841
|
+
idleMs: 0,
|
|
133842
|
+
sampleRangeMs,
|
|
133843
|
+
medianIntervalMs: 0,
|
|
133844
|
+
maxIntervalMs: 0
|
|
133845
|
+
};
|
|
133846
|
+
if (n === 0) return empty2;
|
|
133826
133847
|
let lo = 0;
|
|
133827
|
-
let hi =
|
|
133848
|
+
let hi = n;
|
|
133828
133849
|
while (lo < hi) {
|
|
133829
133850
|
const mid = lo + hi >>> 1;
|
|
133830
133851
|
if (timestampsMs[mid] < startMs) lo = mid + 1;
|
|
133831
133852
|
else hi = mid;
|
|
133832
133853
|
}
|
|
133833
|
-
const
|
|
133834
|
-
|
|
133835
|
-
|
|
133836
|
-
|
|
133854
|
+
const selfMsByNode = /* @__PURE__ */ new Map();
|
|
133855
|
+
const intervals = [];
|
|
133856
|
+
let samplesInWindow = 0;
|
|
133857
|
+
let coveredMs = 0;
|
|
133858
|
+
let idleMs = 0;
|
|
133859
|
+
let maxIntervalMs = 0;
|
|
133860
|
+
for (let i = lo; i < n; i++) {
|
|
133861
|
+
const from2 = intervalStartsMs[i];
|
|
133862
|
+
if (from2 > endMs) break;
|
|
133863
|
+
const overlap = Math.min(endMs, timestampsMs[i]) - Math.max(startMs, from2);
|
|
133864
|
+
if (overlap <= 0) continue;
|
|
133865
|
+
samplesInWindow++;
|
|
133866
|
+
coveredMs += overlap;
|
|
133867
|
+
intervals.push(timestampsMs[i] - from2);
|
|
133868
|
+
if (timestampsMs[i] - from2 > maxIntervalMs) maxIntervalMs = timestampsMs[i] - from2;
|
|
133837
133869
|
const nodeId = sampleNodeIds[i];
|
|
133838
|
-
|
|
133839
|
-
|
|
133840
|
-
|
|
133841
|
-
|
|
133842
|
-
const windowDurationMs = endMs - startMs;
|
|
133843
|
-
const avgIntervalMs = totalSamples > 1 ? windowDurationMs / totalSamples : 1;
|
|
133844
|
-
const childToParent = /* @__PURE__ */ new Map();
|
|
133845
|
-
for (const node of nodeMap.values()) {
|
|
133846
|
-
for (const childId of node.children ?? []) {
|
|
133847
|
-
childToParent.set(childId, node.id);
|
|
133870
|
+
const node = nodeMap.get(nodeId);
|
|
133871
|
+
if (isIdleFrame(node?.callFrame.functionName)) {
|
|
133872
|
+
idleMs += overlap;
|
|
133873
|
+
continue;
|
|
133848
133874
|
}
|
|
133875
|
+
selfMsByNode.set(nodeId, (selfMsByNode.get(nodeId) ?? 0) + overlap);
|
|
133849
133876
|
}
|
|
133850
|
-
|
|
133851
|
-
|
|
133852
|
-
|
|
133877
|
+
if (samplesInWindow === 0) return { ...empty2, sampleRangeMs };
|
|
133878
|
+
intervals.sort((a, b) => a - b);
|
|
133879
|
+
const medianIntervalMs = intervals[Math.floor(intervals.length / 2)] ?? 0;
|
|
133880
|
+
const childToParent = index.childToParent ?? buildChildToParent(nodeMap);
|
|
133881
|
+
const totalMsByNode = /* @__PURE__ */ new Map();
|
|
133882
|
+
for (const [nodeId, ms] of selfMsByNode) {
|
|
133883
|
+
totalMsByNode.set(nodeId, (totalMsByNode.get(nodeId) ?? 0) + ms);
|
|
133853
133884
|
let current = nodeId;
|
|
133885
|
+
const seen = /* @__PURE__ */ new Set([current]);
|
|
133854
133886
|
while (childToParent.has(current)) {
|
|
133855
133887
|
const parent = childToParent.get(current);
|
|
133856
|
-
|
|
133888
|
+
if (seen.has(parent)) break;
|
|
133889
|
+
seen.add(parent);
|
|
133890
|
+
totalMsByNode.set(parent, (totalMsByNode.get(parent) ?? 0) + ms);
|
|
133857
133891
|
current = parent;
|
|
133858
133892
|
}
|
|
133859
133893
|
}
|
|
133860
133894
|
const entries = [];
|
|
133861
|
-
for (const [nodeId,
|
|
133895
|
+
for (const [nodeId, ms] of selfMsByNode) {
|
|
133862
133896
|
const node = nodeMap.get(nodeId);
|
|
133863
133897
|
if (!node) continue;
|
|
133864
133898
|
const name = node.callFrame.functionName;
|
|
133865
|
-
if (!name || name === "(idle)" || name === "(program)" || name === "(root)" || name === "[idle]" || name === "[root]")
|
|
133866
|
-
continue;
|
|
133867
133899
|
if (isArgentProfilerFunction(name)) continue;
|
|
133868
|
-
const selfMs = Math.round(hits * avgIntervalMs * 100) / 100;
|
|
133869
|
-
const totalMs = Math.round((totalHits.get(nodeId) ?? hits) * avgIntervalMs * 100) / 100;
|
|
133870
133900
|
entries.push({
|
|
133871
133901
|
name,
|
|
133872
|
-
selfMs,
|
|
133873
|
-
totalMs,
|
|
133902
|
+
selfMs: Math.round(ms * 100) / 100,
|
|
133903
|
+
totalMs: Math.round((totalMsByNode.get(nodeId) ?? ms) * 100) / 100,
|
|
133874
133904
|
url: node.callFrame.url || void 0,
|
|
133875
133905
|
lineNumber: node.callFrame.lineNumber >= 0 ? node.callFrame.lineNumber : void 0
|
|
133876
133906
|
});
|
|
133877
133907
|
}
|
|
133878
133908
|
entries.sort((a, b) => b.selfMs - a.selfMs);
|
|
133879
|
-
return
|
|
133909
|
+
return {
|
|
133910
|
+
hotspots: entries.slice(0, topN2),
|
|
133911
|
+
samplesInWindow,
|
|
133912
|
+
coveredMs,
|
|
133913
|
+
idleMs,
|
|
133914
|
+
sampleRangeMs,
|
|
133915
|
+
medianIntervalMs,
|
|
133916
|
+
maxIntervalMs
|
|
133917
|
+
};
|
|
133918
|
+
}
|
|
133919
|
+
function buildChildToParent(nodeMap) {
|
|
133920
|
+
const childToParent = /* @__PURE__ */ new Map();
|
|
133921
|
+
for (const node of nodeMap.values()) {
|
|
133922
|
+
for (const childId of node.children ?? []) childToParent.set(childId, node.id);
|
|
133923
|
+
}
|
|
133924
|
+
return childToParent;
|
|
133880
133925
|
}
|
|
133881
133926
|
function correlateCpuWithCommits(summaries, index, topNPerCommit = 5) {
|
|
133882
133927
|
if (!index) return summaries;
|
|
@@ -133884,28 +133929,35 @@ function correlateCpuWithCommits(summaries, index, topNPerCommit = 5) {
|
|
|
133884
133929
|
if (summary.isMargin) return summary;
|
|
133885
133930
|
const startMs = summary.timestampMs;
|
|
133886
133931
|
const endMs = summary.timestampMs + summary.totalRenderMs;
|
|
133887
|
-
const hotspots = queryCpuWindow(index, startMs, endMs, topNPerCommit);
|
|
133932
|
+
const { hotspots } = queryCpuWindow(index, startMs, endMs, topNPerCommit);
|
|
133888
133933
|
if (hotspots.length === 0) return summary;
|
|
133889
133934
|
return { ...summary, cpuHotspots: hotspots };
|
|
133890
133935
|
});
|
|
133891
133936
|
}
|
|
133892
133937
|
function serializeCpuSampleIndex(index) {
|
|
133893
133938
|
return {
|
|
133939
|
+
version: 2,
|
|
133894
133940
|
timestampsMs: Array.from(index.timestampsMs),
|
|
133941
|
+
intervalStartsMs: Array.from(index.intervalStartsMs),
|
|
133895
133942
|
sampleNodeIds: index.sampleNodeIds,
|
|
133896
133943
|
nodes: [...index.nodeMap.values()],
|
|
133897
133944
|
durationMs: index.durationMs
|
|
133898
133945
|
};
|
|
133899
133946
|
}
|
|
133900
133947
|
function deserializeCpuSampleIndex(raw) {
|
|
133948
|
+
if (raw?.version !== 2 || !Array.isArray(raw.timestampsMs) || !Array.isArray(raw.nodes)) {
|
|
133949
|
+
throw new Error("unsupported CPU sample index format");
|
|
133950
|
+
}
|
|
133901
133951
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
133902
133952
|
for (const node of raw.nodes) {
|
|
133903
133953
|
nodeMap.set(node.id, node);
|
|
133904
133954
|
}
|
|
133905
133955
|
return {
|
|
133906
133956
|
timestampsMs: new Float64Array(raw.timestampsMs),
|
|
133957
|
+
intervalStartsMs: new Float64Array(raw.intervalStartsMs ?? []),
|
|
133907
133958
|
sampleNodeIds: raw.sampleNodeIds,
|
|
133908
133959
|
nodeMap,
|
|
133960
|
+
childToParent: buildChildToParent(nodeMap),
|
|
133909
133961
|
durationMs: raw.durationMs
|
|
133910
133962
|
};
|
|
133911
133963
|
}
|
|
@@ -134208,8 +134260,7 @@ async function runPipeline(input, options) {
|
|
|
134208
134260
|
hotCommitIndices,
|
|
134209
134261
|
input.sessionMeta.unattributedByCommit
|
|
134210
134262
|
);
|
|
134211
|
-
const
|
|
134212
|
-
const cpuSampleIndex = input.flamegraph ? buildCpuSampleIndex(input.flamegraph, firstCommitTs) : null;
|
|
134263
|
+
const cpuSampleIndex = input.flamegraph ? buildCpuSampleIndex(input.flamegraph) : null;
|
|
134213
134264
|
const hotCommitSummaries = correlateCpuWithCommits(rawHotCommitSummaries, cpuSampleIndex);
|
|
134214
134265
|
const preprocessedCommitTree = { ...input.commitTree, commits: preprocessed };
|
|
134215
134266
|
const reduceOutput = reduce(
|
|
@@ -140615,8 +140666,12 @@ init_zod();
|
|
|
140615
140666
|
init_src();
|
|
140616
140667
|
var import_fs18 = require("fs");
|
|
140617
140668
|
var timeWindowSchema = external_exports.object({
|
|
140618
|
-
start: external_exports.coerce.number().describe(
|
|
140619
|
-
|
|
140669
|
+
start: external_exports.coerce.number().describe(
|
|
140670
|
+
"Start of window in ms since profiling started \u2014 the same clock profiler-commit-query prints"
|
|
140671
|
+
),
|
|
140672
|
+
end: external_exports.coerce.number().describe(
|
|
140673
|
+
"End of window in ms since profiling started \u2014 the same clock profiler-commit-query prints"
|
|
140674
|
+
)
|
|
140620
140675
|
});
|
|
140621
140676
|
var zodSchema53 = external_exports.object({
|
|
140622
140677
|
port: external_exports.coerce.number().default(8081).describe("Metro server port"),
|
|
@@ -140626,7 +140681,9 @@ var zodSchema53 = external_exports.object({
|
|
|
140626
140681
|
mode: external_exports.enum(["top_functions", "time_window", "call_tree", "component_cpu"]).describe(
|
|
140627
140682
|
"Query mode: top_functions (global hotspots), time_window (CPU in a time range), call_tree (callers/callees of a function), component_cpu (CPU during a component's commits)"
|
|
140628
140683
|
),
|
|
140629
|
-
time_window_ms: timeWindowSchema.optional().describe(
|
|
140684
|
+
time_window_ms: timeWindowSchema.optional().describe(
|
|
140685
|
+
"Time window filter for time_window mode (ms since profiling started \u2014 the same clock profiler-commit-query prints)"
|
|
140686
|
+
),
|
|
140630
140687
|
component_name: external_exports.string().optional().describe("Component name for component_cpu mode"),
|
|
140631
140688
|
function_name: external_exports.string().optional().describe("Function name for call_tree mode"),
|
|
140632
140689
|
top_n: external_exports.coerce.number().int().positive().default(15).describe("Number of results to return (default 15)"),
|
|
@@ -140659,31 +140716,60 @@ async function getIndex(sessionPaths) {
|
|
|
140659
140716
|
}
|
|
140660
140717
|
const cpuProfile = await readCpuProfile(sessionPaths.cpuProfilePath);
|
|
140661
140718
|
let commitTree = null;
|
|
140662
|
-
let firstCommitTs = null;
|
|
140663
140719
|
if (sessionPaths.commitsPath) {
|
|
140664
140720
|
const onDisk = await readCommitTree(sessionPaths.commitsPath);
|
|
140665
140721
|
commitTree = { commits: onDisk.commits };
|
|
140666
|
-
firstCommitTs = onDisk.commits[0]?.timestamp ?? null;
|
|
140667
140722
|
}
|
|
140668
|
-
return { index: buildCpuSampleIndex(cpuProfile
|
|
140723
|
+
return { index: buildCpuSampleIndex(cpuProfile), commitTree };
|
|
140724
|
+
}
|
|
140725
|
+
function explainEmptyWindow(res, startMs, endMs) {
|
|
140726
|
+
const range = `${res.sampleRangeMs.start.toFixed(1)}\u2013${res.sampleRangeMs.end.toFixed(1)}ms`;
|
|
140727
|
+
const window2 = `${startMs.toFixed(1)}\u2013${endMs.toFixed(1)}ms`;
|
|
140728
|
+
if (res.sampleRangeMs.end === 0 && res.samplesInWindow === 0) {
|
|
140729
|
+
return "_The CPU profile contains no samples. Sampling produced no data for this session \u2014 that is a capture failure, not a measurement of idleness._";
|
|
140730
|
+
}
|
|
140731
|
+
if (endMs < res.sampleRangeMs.start || startMs > res.sampleRangeMs.end) {
|
|
140732
|
+
return `_No CPU samples exist in ${window2} \u2014 that is outside the recorded sample range (${range}). This is a coverage gap, not a measurement: nothing can be concluded about CPU cost here. Sample times are ms since profiling started, the same clock \`profiler-commit-query\` prints._`;
|
|
140733
|
+
}
|
|
140734
|
+
if (res.samplesInWindow > 0) {
|
|
140735
|
+
return `_${res.samplesInWindow} sample(s) covering ${res.coveredMs.toFixed(1)}ms fell inside ${window2}, and all of them were idle \u2014 the JS thread was not executing during this window. Native or UI-thread work would not appear here; use \`native-profiler-start\` for that._`;
|
|
140736
|
+
}
|
|
140737
|
+
return `_No CPU samples fell inside ${window2} (${(endMs - startMs).toFixed(1)}ms wide), although it lies within the recorded range (${range}). The sampler runs roughly every ${res.medianIntervalMs > 0 ? res.medianIntervalMs.toFixed(1) : "13"}ms, so a window this narrow can contain none at all. **Absence of samples is not evidence that this commit was cheap.** Widen the window, or use \`mode=component_cpu\`._`;
|
|
140738
|
+
}
|
|
140739
|
+
function coverageNote(res, startMs, endMs) {
|
|
140740
|
+
const widthMs = endMs - startMs;
|
|
140741
|
+
const lines = [
|
|
140742
|
+
`**Window:** ${startMs.toFixed(1)}ms \u2192 ${endMs.toFixed(1)}ms (${widthMs.toFixed(1)}ms)`,
|
|
140743
|
+
`**Samples:** ${res.samplesInWindow} covering ${res.coveredMs.toFixed(1)}ms` + (res.idleMs > 0 ? `, of which ${res.idleMs.toFixed(1)}ms idle` : "") + ` \u2014 sampling interval ~${res.medianIntervalMs.toFixed(1)}ms. Self-times sum to sampled coverage, not to the window width.`
|
|
140744
|
+
];
|
|
140745
|
+
if (widthMs > 0 && widthMs < 3 * res.medianIntervalMs) {
|
|
140746
|
+
lines.push(
|
|
140747
|
+
`> This window is narrower than ~3 sampling intervals, so every figure carries \xB11 sample (\u2248${res.medianIntervalMs.toFixed(1)}ms).`
|
|
140748
|
+
);
|
|
140749
|
+
}
|
|
140750
|
+
if (res.maxIntervalMs > 50 && res.maxIntervalMs > 5 * res.medianIntervalMs) {
|
|
140751
|
+
lines.push(
|
|
140752
|
+
`> The sampler stalled for ${res.maxIntervalMs.toFixed(1)}ms inside this window; that whole gap is attributed to whichever function was caught by the sample that ended it.`
|
|
140753
|
+
);
|
|
140754
|
+
}
|
|
140755
|
+
return lines.join("\n\n");
|
|
140669
140756
|
}
|
|
140670
140757
|
function renderTopFunctions(index, topN2, startMs, endMs) {
|
|
140671
|
-
const windowStart = startMs ?? index.
|
|
140672
|
-
const windowEnd = endMs ?? index.timestampsMs[index.timestampsMs.length - 1];
|
|
140673
|
-
const
|
|
140674
|
-
if (hotspots.length === 0) return
|
|
140758
|
+
const windowStart = startMs ?? index.intervalStartsMs[0] ?? 0;
|
|
140759
|
+
const windowEnd = endMs ?? index.timestampsMs[index.timestampsMs.length - 1] ?? 0;
|
|
140760
|
+
const res = queryCpuWindow(index, windowStart, windowEnd, topN2);
|
|
140761
|
+
if (res.hotspots.length === 0) return explainEmptyWindow(res, windowStart, windowEnd);
|
|
140675
140762
|
const header = "| Function | Self (ms) | Total (ms) | Location |";
|
|
140676
140763
|
const sep7 = "|---|---|---|---|";
|
|
140677
|
-
const rows = hotspots.map((hs) => {
|
|
140764
|
+
const rows = res.hotspots.map((hs) => {
|
|
140678
140765
|
const loc = hs.url ? `${shortenUrl(hs.url)}${hs.lineNumber != null ? `:${hs.lineNumber}` : ""}` : "\u2014";
|
|
140679
140766
|
return `| \`${hs.name}\` | ${hs.selfMs} | ${hs.totalMs} | ${loc} |`;
|
|
140680
140767
|
});
|
|
140681
|
-
const rangeNote = startMs != null ? `**Window:** ${startMs.toFixed(1)}ms \u2192 ${endMs.toFixed(1)}ms
|
|
140682
|
-
|
|
140683
|
-
` : "";
|
|
140684
140768
|
return `## CPU Hotspots
|
|
140685
140769
|
|
|
140686
|
-
${
|
|
140770
|
+
${coverageNote(res, windowStart, windowEnd)}
|
|
140771
|
+
|
|
140772
|
+
${header}
|
|
140687
140773
|
${sep7}
|
|
140688
140774
|
${rows.join("\n")}`;
|
|
140689
140775
|
}
|
|
@@ -140813,7 +140899,7 @@ function renderComponentCpu(index, commitTree, componentName, topN2) {
|
|
|
140813
140899
|
}
|
|
140814
140900
|
const aggregated = /* @__PURE__ */ new Map();
|
|
140815
140901
|
for (const window2 of commitWindows.values()) {
|
|
140816
|
-
const hotspots = queryCpuWindow(index, window2.start, window2.end, 50);
|
|
140902
|
+
const { hotspots } = queryCpuWindow(index, window2.start, window2.end, 50);
|
|
140817
140903
|
for (const hs of hotspots) {
|
|
140818
140904
|
const existing = aggregated.get(hs.name);
|
|
140819
140905
|
if (existing) {
|
|
@@ -140867,6 +140953,10 @@ Requires react-profiler-stop (and ideally react-profiler-analyze) to have been c
|
|
|
140867
140953
|
Modes:
|
|
140868
140954
|
- top_functions: Global CPU hotspots ranked by self-time. Optional time_window_ms to filter.
|
|
140869
140955
|
- time_window: CPU breakdown for a specific time range (e.g. during a slow commit or hang).
|
|
140956
|
+
|
|
140957
|
+
Self-times are the summed sampling intervals of the samples that landed in the window, so they
|
|
140958
|
+
measure sampled coverage rather than the window's width and do not change if you widen the query.
|
|
140959
|
+
Every table states how many samples it covers and how much of that was idle.
|
|
140870
140960
|
- call_tree: For a given function_name, show its callees and optionally callers.
|
|
140871
140961
|
- component_cpu: For a given component_name, aggregate CPU activity across all its commits.
|
|
140872
140962
|
Use when investigating JS CPU hotspots or correlating CPU cost with specific components.
|
|
@@ -140952,8 +141042,12 @@ Fails if no CPU profile is stored \u2014 run react-profiler-stop first.`,
|
|
|
140952
141042
|
init_zod();
|
|
140953
141043
|
init_src();
|
|
140954
141044
|
var timeRangeSchema = external_exports.object({
|
|
140955
|
-
start: external_exports.coerce.number().describe(
|
|
140956
|
-
|
|
141045
|
+
start: external_exports.coerce.number().describe(
|
|
141046
|
+
"Start of range in ms since profiling started \u2014 the same clock profiler-commit-query prints"
|
|
141047
|
+
),
|
|
141048
|
+
end: external_exports.coerce.number().describe(
|
|
141049
|
+
"End of range in ms since profiling started \u2014 the same clock profiler-commit-query prints"
|
|
141050
|
+
)
|
|
140957
141051
|
});
|
|
140958
141052
|
var zodSchema54 = external_exports.object({
|
|
140959
141053
|
port: external_exports.coerce.number().default(8081).describe("Metro server port"),
|
|
@@ -145165,19 +145259,18 @@ async function queryVegaTree(device) {
|
|
|
145165
145259
|
|
|
145166
145260
|
// ../tool-server/src/tools/flows/flow-tree.ts
|
|
145167
145261
|
async function fetchFlowTree(registry2, device, launchedNativeApp) {
|
|
145168
|
-
|
|
145169
|
-
|
|
145170
|
-
|
|
145171
|
-
|
|
145172
|
-
|
|
145173
|
-
|
|
145174
|
-
|
|
145175
|
-
|
|
145176
|
-
|
|
145177
|
-
|
|
145178
|
-
|
|
145179
|
-
|
|
145180
|
-
return fetchTree(registry2, device);
|
|
145262
|
+
const source = FLOW_TREE_SOURCES[device.platform];
|
|
145263
|
+
if (!source) return fetchTree(registry2, device);
|
|
145264
|
+
return source(registry2, device, launchedNativeApp);
|
|
145265
|
+
}
|
|
145266
|
+
var FLOW_TREE_SOURCES = {
|
|
145267
|
+
ios: (registry2, device, launchedNativeApp) => queryFullHierarchyTree(registry2, device, launchedNativeApp),
|
|
145268
|
+
android: (registry2, device) => queryAndroidFullHierarchy(registry2, device),
|
|
145269
|
+
chromium: (registry2, device) => queryChromiumTree(registry2, device),
|
|
145270
|
+
vega: (_registry, device) => queryVegaTree(device)
|
|
145271
|
+
};
|
|
145272
|
+
function supportsFlowTree(platform) {
|
|
145273
|
+
return FLOW_TREE_SOURCES[platform] !== void 0;
|
|
145181
145274
|
}
|
|
145182
145275
|
|
|
145183
145276
|
// ../tool-server/src/tools/flows/flow-add-step.ts
|
|
@@ -145511,11 +145604,16 @@ function runSequenceOutcome(result) {
|
|
|
145511
145604
|
}
|
|
145512
145605
|
return void 0;
|
|
145513
145606
|
}
|
|
145607
|
+
var NESTED_ORCHESTRATORS = /* @__PURE__ */ new Map([
|
|
145608
|
+
[FLOW_EXECUTE_TOOL_ID, flowExecuteOutcome],
|
|
145609
|
+
[RUN_SEQUENCE_TOOL_ID, runSequenceOutcome]
|
|
145610
|
+
]);
|
|
145611
|
+
function isNestedOrchestratorTool(tool) {
|
|
145612
|
+
return NESTED_ORCHESTRATORS.has(tool);
|
|
145613
|
+
}
|
|
145514
145614
|
function nestedOrchestratorOutcome(tool, result) {
|
|
145515
145615
|
if (!isRecord(result)) return void 0;
|
|
145516
|
-
|
|
145517
|
-
if (tool === RUN_SEQUENCE_TOOL_ID) return runSequenceOutcome(result);
|
|
145518
|
-
return void 0;
|
|
145616
|
+
return NESTED_ORCHESTRATORS.get(tool)?.(result);
|
|
145519
145617
|
}
|
|
145520
145618
|
|
|
145521
145619
|
// ../tool-server/src/tools/flows/flow-pixels.ts
|
|
@@ -145659,6 +145757,7 @@ var FOCUS_REPORTING_SOURCES = /* @__PURE__ */ new Set([
|
|
|
145659
145757
|
]);
|
|
145660
145758
|
var SETTLE_POLL_MS = 250;
|
|
145661
145759
|
var SETTLE_TIMEOUT_MS = 3e3;
|
|
145760
|
+
var SETTLE_MIN_READS = 2;
|
|
145662
145761
|
var MAX_SCROLL_ITERATIONS = 25;
|
|
145663
145762
|
var SCROLL_INCREMENT = 0.5;
|
|
145664
145763
|
var MIN_SCROLL_INCREMENT = 0.05;
|
|
@@ -145709,19 +145808,34 @@ function flowSelectorToFrame(tree, sel) {
|
|
|
145709
145808
|
}
|
|
145710
145809
|
return void 0;
|
|
145711
145810
|
}
|
|
145712
|
-
|
|
145811
|
+
function provenTreeOutage(env) {
|
|
145812
|
+
const proven = env.treeOutage?.proven;
|
|
145813
|
+
return proven && proven.deviceId === env.device.id ? proven.error : void 0;
|
|
145814
|
+
}
|
|
145815
|
+
function readFlowTree(env) {
|
|
145816
|
+
return fetchFlowTree(env.registry, env.device, env.launchedNativeApp).then((data) => {
|
|
145817
|
+
if (env.treeOutage) env.treeOutage.proven = void 0;
|
|
145818
|
+
return data;
|
|
145819
|
+
});
|
|
145820
|
+
}
|
|
145821
|
+
async function settleTree(env, opts = {}) {
|
|
145713
145822
|
const deadline = Date.now() + SETTLE_TIMEOUT_MS;
|
|
145714
145823
|
let prevFp;
|
|
145715
145824
|
let prevTree;
|
|
145716
145825
|
let lastError;
|
|
145826
|
+
let reads = 0;
|
|
145717
145827
|
for (; ; ) {
|
|
145718
145828
|
if (env.signal?.aborted) return void 0;
|
|
145829
|
+
const proven = provenTreeOutage(env);
|
|
145830
|
+
if (opts.skipProvenOutage && proven) throw proven;
|
|
145831
|
+
const read = await settleWithin(readFlowTree(env), void 0, env.signal);
|
|
145719
145832
|
let tree;
|
|
145720
|
-
|
|
145721
|
-
|
|
145722
|
-
}
|
|
145723
|
-
lastError =
|
|
145833
|
+
if (read.type === "value") {
|
|
145834
|
+
tree = read.value.tree;
|
|
145835
|
+
} else if (read.type === "error") {
|
|
145836
|
+
lastError = read.cause;
|
|
145724
145837
|
}
|
|
145838
|
+
reads += 1;
|
|
145725
145839
|
if (env.signal?.aborted) return void 0;
|
|
145726
145840
|
if (tree !== void 0) {
|
|
145727
145841
|
const fp = treeFingerprint(tree);
|
|
@@ -145730,7 +145844,11 @@ async function settleTree(env) {
|
|
|
145730
145844
|
prevTree = tree;
|
|
145731
145845
|
}
|
|
145732
145846
|
if (Date.now() >= deadline) {
|
|
145733
|
-
if (
|
|
145847
|
+
if (reads < SETTLE_MIN_READS) continue;
|
|
145848
|
+
if (prevTree === void 0 && lastError !== void 0) {
|
|
145849
|
+
if (env.treeOutage) env.treeOutage.proven = { deviceId: env.device.id, error: lastError };
|
|
145850
|
+
throw lastError;
|
|
145851
|
+
}
|
|
145734
145852
|
return prevTree;
|
|
145735
145853
|
}
|
|
145736
145854
|
if (!await sleepOrAbort(SETTLE_POLL_MS, env.signal)) return void 0;
|
|
@@ -145779,7 +145897,7 @@ async function waitForFocus(env, into, tappedFrame) {
|
|
|
145779
145897
|
for (; ; ) {
|
|
145780
145898
|
if (env.signal?.aborted) return;
|
|
145781
145899
|
try {
|
|
145782
|
-
const { tree, source } = await
|
|
145900
|
+
const { tree, source } = await readFlowTree(env);
|
|
145783
145901
|
if (!FOCUS_REPORTING_SOURCES.has(source)) return;
|
|
145784
145902
|
const target = flowSelectorToFrame(tree, into) ?? tappedFrame;
|
|
145785
145903
|
if (collectFocused(tree, []).some((n) => framesOverlap(n.frame, target))) return;
|
|
@@ -145892,6 +146010,25 @@ async function runDirective(env, step) {
|
|
|
145892
146010
|
return runRotate(env, step);
|
|
145893
146011
|
}
|
|
145894
146012
|
}
|
|
146013
|
+
async function settleForGesture(env) {
|
|
146014
|
+
let warning;
|
|
146015
|
+
if (supportsFlowTree(env.device.platform)) {
|
|
146016
|
+
try {
|
|
146017
|
+
await settleTree(env, { skipProvenOutage: true });
|
|
146018
|
+
} catch (err) {
|
|
146019
|
+
warning = unsettledGestureWarning(err);
|
|
146020
|
+
}
|
|
146021
|
+
}
|
|
146022
|
+
if (env.signal?.aborted) return { aborted: true };
|
|
146023
|
+
return warning !== void 0 ? { warning } : {};
|
|
146024
|
+
}
|
|
146025
|
+
function warned(settle) {
|
|
146026
|
+
return settle.warning !== void 0 ? { warning: settle.warning } : {};
|
|
146027
|
+
}
|
|
146028
|
+
function unsettledGestureWarning(err) {
|
|
146029
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
146030
|
+
return `dispatched without settling the screen first: the UI tree could not be read (${reason}), so there was no way to tell whether anything was moving. The gesture went out against whatever was in flight, and one aimed at a moving element can miss it entirely - this step passing says it was sent, not that it landed. Restore the tree source, or put an explicit \`wait:\` in front of gestures that follow a transition.`;
|
|
146031
|
+
}
|
|
145895
146032
|
async function resolveTargetPoint(env, target) {
|
|
145896
146033
|
if (target.selector) {
|
|
145897
146034
|
const frame = await waitForFrame(env, target.selector);
|
|
@@ -145899,26 +146036,29 @@ async function resolveTargetPoint(env, target) {
|
|
|
145899
146036
|
if (!frame) {
|
|
145900
146037
|
return { fail: { ok: false, reason: offscreenHint(target.selector) } };
|
|
145901
146038
|
}
|
|
145902
|
-
return getDescribeTapPoint(frame);
|
|
146039
|
+
return { point: getDescribeTapPoint(frame) };
|
|
145903
146040
|
}
|
|
145904
146041
|
if (typeof target.x === "number" && typeof target.y === "number") {
|
|
145905
|
-
|
|
146042
|
+
const settle = await settleForGesture(env);
|
|
146043
|
+
if (settle.aborted) return { fail: ABORTED_OUTCOME };
|
|
146044
|
+
return { point: { x: target.x, y: target.y }, ...warned(settle) };
|
|
145906
146045
|
}
|
|
145907
146046
|
return { fail: { ok: false, reason: "gesture needs a selector or x/y coordinates" } };
|
|
145908
146047
|
}
|
|
145909
146048
|
async function runTap(env, target) {
|
|
145910
|
-
const
|
|
145911
|
-
if ("fail" in
|
|
146049
|
+
const resolved = await resolveTargetPoint(env, target);
|
|
146050
|
+
if ("fail" in resolved) return resolved.fail;
|
|
145912
146051
|
await invokeOnDevice(env, "gesture-tap", {
|
|
145913
|
-
...point,
|
|
146052
|
+
...resolved.point,
|
|
145914
146053
|
...target.times !== void 0 ? { clickCount: target.times } : {}
|
|
145915
146054
|
});
|
|
145916
|
-
return { ok: true };
|
|
146055
|
+
return { ok: true, ...warned(resolved) };
|
|
145917
146056
|
}
|
|
145918
146057
|
var DEFAULT_LONG_PRESS_MS = 800;
|
|
145919
146058
|
async function runLongPress(env, step) {
|
|
145920
|
-
const
|
|
145921
|
-
if ("fail" in
|
|
146059
|
+
const resolved = await resolveTargetPoint(env, step);
|
|
146060
|
+
if ("fail" in resolved) return resolved.fail;
|
|
146061
|
+
const point = resolved.point;
|
|
145922
146062
|
const duration3 = step.duration ?? DEFAULT_LONG_PRESS_MS;
|
|
145923
146063
|
if (env.device.platform === "chromium") {
|
|
145924
146064
|
await invokeOnDevice(env, "gesture-drag", {
|
|
@@ -145936,17 +146076,21 @@ async function runLongPress(env, step) {
|
|
|
145936
146076
|
]
|
|
145937
146077
|
});
|
|
145938
146078
|
}
|
|
145939
|
-
return { ok: true };
|
|
146079
|
+
return { ok: true, ...warned(resolved) };
|
|
145940
146080
|
}
|
|
145941
146081
|
async function runPinch(env, step) {
|
|
145942
146082
|
let center = { x: 0.5, y: 0.5 };
|
|
145943
146083
|
let frame;
|
|
146084
|
+
let settle = {};
|
|
145944
146085
|
if (step.selector) {
|
|
145945
146086
|
const resolved = await waitForFrame(env, step.selector);
|
|
145946
146087
|
if (resolved === "aborted") return ABORTED_OUTCOME;
|
|
145947
146088
|
if (!resolved) return { ok: false, reason: offscreenHint(step.selector) };
|
|
145948
146089
|
frame = resolved;
|
|
145949
146090
|
center = getDescribeTapPoint(resolved);
|
|
146091
|
+
} else {
|
|
146092
|
+
settle = await settleForGesture(env);
|
|
146093
|
+
if (settle.aborted) return ABORTED_OUTCOME;
|
|
145950
146094
|
}
|
|
145951
146095
|
const { n, per } = decomposePinch(step.scale);
|
|
145952
146096
|
const guards = systemEdgeGuards(env.device);
|
|
@@ -145977,11 +146121,11 @@ async function runPinch(env, step) {
|
|
|
145977
146121
|
await invokeOnDevice(env, "gesture-pinch", args);
|
|
145978
146122
|
if (i < n - 1 && !await sleepOrAbort(PINCH_SETTLE_MS, env.signal)) return ABORTED_OUTCOME;
|
|
145979
146123
|
}
|
|
145980
|
-
return { ok: true };
|
|
146124
|
+
return { ok: true, ...warned(settle) };
|
|
145981
146125
|
}
|
|
145982
146126
|
async function fetchScreenAspect(env) {
|
|
145983
146127
|
try {
|
|
145984
|
-
const { screen } = await
|
|
146128
|
+
const { screen } = await readFlowTree(env);
|
|
145985
146129
|
return screen && screen.width > 0 && screen.height > 0 ? screen.width / screen.height : void 0;
|
|
145986
146130
|
} catch {
|
|
145987
146131
|
return void 0;
|
|
@@ -145990,12 +146134,16 @@ async function fetchScreenAspect(env) {
|
|
|
145990
146134
|
async function runRotate(env, step) {
|
|
145991
146135
|
let center = { x: 0.5, y: 0.5 };
|
|
145992
146136
|
let frame;
|
|
146137
|
+
let settle = {};
|
|
145993
146138
|
if (step.selector) {
|
|
145994
146139
|
const resolved = await waitForFrame(env, step.selector);
|
|
145995
146140
|
if (resolved === "aborted") return ABORTED_OUTCOME;
|
|
145996
146141
|
if (!resolved) return { ok: false, reason: offscreenHint(step.selector) };
|
|
145997
146142
|
frame = resolved;
|
|
145998
146143
|
center = getDescribeTapPoint(resolved);
|
|
146144
|
+
} else {
|
|
146145
|
+
settle = await settleForGesture(env);
|
|
146146
|
+
if (settle.aborted) return ABORTED_OUTCOME;
|
|
145999
146147
|
}
|
|
146000
146148
|
const aspect = await fetchScreenAspect(env);
|
|
146001
146149
|
const guards = systemEdgeGuards(env.device);
|
|
@@ -146037,7 +146185,7 @@ async function runRotate(env, step) {
|
|
|
146037
146185
|
if (env.signal?.aborted) return ABORTED_OUTCOME;
|
|
146038
146186
|
throw err;
|
|
146039
146187
|
}
|
|
146040
|
-
return { ok: true };
|
|
146188
|
+
return { ok: true, ...warned(settle) };
|
|
146041
146189
|
}
|
|
146042
146190
|
async function runType(env, step) {
|
|
146043
146191
|
const frame = await waitForFrame(env, step.into);
|
|
@@ -146069,7 +146217,7 @@ async function waitForCondition(env, step, timeoutMs) {
|
|
|
146069
146217
|
for (; ; ) {
|
|
146070
146218
|
if (env.signal?.aborted) return ABORTED_OUTCOME;
|
|
146071
146219
|
try {
|
|
146072
|
-
const data = await
|
|
146220
|
+
const data = await readFlowTree(env);
|
|
146073
146221
|
lastMatches = flowFindAll(data.tree, step.selector);
|
|
146074
146222
|
fetchError = void 0;
|
|
146075
146223
|
everMatched ||= lastMatches.length > 0;
|
|
@@ -146164,11 +146312,7 @@ async function waitForIdle(env, step) {
|
|
|
146164
146312
|
const roundStartedAt = Date.now();
|
|
146165
146313
|
let answeredReadMs;
|
|
146166
146314
|
const [read, frame] = await Promise.all([
|
|
146167
|
-
settleWithin(
|
|
146168
|
-
fetchFlowTree(env.registry, env.device, env.launchedNativeApp),
|
|
146169
|
-
roundBudget,
|
|
146170
|
-
env.signal
|
|
146171
|
-
).then((r) => {
|
|
146315
|
+
settleWithin(readFlowTree(env), roundBudget, env.signal).then((r) => {
|
|
146172
146316
|
answeredReadMs = Date.now() - roundStartedAt;
|
|
146173
146317
|
return r;
|
|
146174
146318
|
}),
|
|
@@ -148807,6 +148951,7 @@ async function treeSourceGate(registry2, device, bundleId, signal) {
|
|
|
148807
148951
|
async function runLaunch(state3, app) {
|
|
148808
148952
|
const env = deviceEnv(state3);
|
|
148809
148953
|
const { registry: registry2, device, signal } = env;
|
|
148954
|
+
if (state3.treeOutage) state3.treeOutage.proven = void 0;
|
|
148810
148955
|
if (device.platform === "chromium") return runChromiumLaunch(state3, app);
|
|
148811
148956
|
const bundleId = appIdForPlatform(app, device.platform);
|
|
148812
148957
|
if (!bundleId) {
|
|
@@ -149028,8 +149173,8 @@ when \`on\` is omitted; distinct from the \`rotate\` tool, which changes device
|
|
|
149028
149173
|
for a UI condition, and additionally takes the one condition that has no selector: \`idle: true\` waits
|
|
149029
149174
|
until the screen has content and stops moving in BOTH the UI tree and the rendered pixels (it never
|
|
149030
149175
|
fails a run \u2014 a screen that never settles passes carrying a \`warning\`, which is what makes it safe to
|
|
149031
|
-
persist; the one outcome that does stop the run is an \`error\` for a tree source
|
|
149032
|
-
at all \u2014 a broken window rather than a verdict about the app, which leaves the run not-ok and skips
|
|
149176
|
+
persist; the one idle outcome that does stop the run is an \`error\` for a tree source THIS step could not
|
|
149177
|
+
read at all \u2014 a broken window rather than a verdict about the app, which leaves the run not-ok and skips
|
|
149033
149178
|
every later step; it says nothing about WHICH screen settled \u2014 a dropped tap leaves the source screen
|
|
149034
149179
|
perfectly idle \u2014 so pair it with the element check that names the destination); \`wait\` pauses for a fixed number of milliseconds; \`assert\` checks one now; \`snapshot\`
|
|
149035
149180
|
diffs a screenshot \u2014 or, with \`cropOn: <selector>\`, one element's cropped region \u2014 against a stored
|
|
@@ -149037,6 +149182,14 @@ baseline (a missing baseline fails the step \u2014 set updateBaselines to adopt
|
|
|
149037
149182
|
cropped element whose size drifted fails on dimensions); \`echo\` annotates; \`run\` executes another flow
|
|
149038
149183
|
inline \u2014 a YAML path resolved against the directory of the flow file that references it (co-located
|
|
149039
149184
|
runs only).
|
|
149185
|
+
A selector-less gesture \u2014 a coordinate \`tap\`/\`long-press\`, or a \`pinch\`/\`rotate\` with no \`on\` \u2014 resolves
|
|
149186
|
+
no frame out of the tree, so an unreadable tree source does NOT stop it the way it stops \`idle\`: it
|
|
149187
|
+
settles best-effort, dispatches anyway, and the step PASSES carrying a \`warning\` that quotes the source's
|
|
149188
|
+
own error. That green says the gesture was SENT, not that it landed. Restore the tree source (usually
|
|
149189
|
+
relaunch the app so the instrumentation loads), or accept the warning where the app can serve no tree;
|
|
149190
|
+
the first such gesture proves the outage and later ones spend that verdict without paying the settle
|
|
149191
|
+
window again. A tree read that comes back, or a relaunch, retires that verdict \u2014 which only makes the
|
|
149192
|
+
next gesture pay a fresh window, and it warns again if the source is still down.
|
|
149040
149193
|
A \`when:\` block (condition + \`steps:\`, no else) runs its steps only if the condition holds \u2014
|
|
149041
149194
|
checked once with the short assert grace \u2014 for one-sided divergences like interstitials and coach
|
|
149042
149195
|
marks; a skipped block reports distinctly and failures inside an entered block are real failures.
|
|
@@ -149118,6 +149271,12 @@ Pass exactly one flow source: name for a saved flow under project_root, or flow_
|
|
|
149118
149271
|
device,
|
|
149119
149272
|
deviceIsExplicit: Boolean(params.device),
|
|
149120
149273
|
signal,
|
|
149274
|
+
// One holder per ExecState, shared by nested `run:` flows: `deviceEnv`
|
|
149275
|
+
// spreads the reference, so what one step's settle learns about the
|
|
149276
|
+
// tree source the next one already has. A `tool: flow-execute` builds
|
|
149277
|
+
// its own instead, which is why that step spends this verdict rather
|
|
149278
|
+
// than inheriting whatever the sub-run proved.
|
|
149279
|
+
treeOutage: {},
|
|
149121
149280
|
flowsDir,
|
|
149122
149281
|
viaUpload,
|
|
149123
149282
|
baselineKey: baselineKeyFor(canonicalPath, flowName),
|
|
@@ -149711,6 +149870,10 @@ async function execLeafStep(state3, step, index, scope) {
|
|
|
149711
149870
|
try {
|
|
149712
149871
|
if (FOREGROUND_CHANGING_TOOLS.has(step.name)) {
|
|
149713
149872
|
state3.launchedNativeApp = void 0;
|
|
149873
|
+
if (state3.treeOutage) state3.treeOutage.proven = void 0;
|
|
149874
|
+
}
|
|
149875
|
+
if (isNestedOrchestratorTool(step.name) && state3.treeOutage) {
|
|
149876
|
+
state3.treeOutage.proven = void 0;
|
|
149714
149877
|
}
|
|
149715
149878
|
const result = await invokeSubTool(registry2, ctx, step.name, args);
|
|
149716
149879
|
if (isUnmetUiWaitResult(step.name, result)) {
|
package/package.json
CHANGED
|
@@ -112,6 +112,8 @@ In a `scroll-to` map, put the selector under `target:`. The map supports `up`, `
|
|
|
112
112
|
|
|
113
113
|
`type` presses Enter unless `submit: false`. A polished focus tap plus keyboard call usually needs `submit: false`. Store external values as `{{secret:NAME}}`. The runner uses the first source that defines the name: environment `ARGENT_SECRET_NAME`; project `.argent/secrets.env`; project `.env.local`, then `.env`; then `~/.argent/secrets.env`. The two `secrets.env` files accept the bare `NAME`, but the shared dotenv files expose only `ARGENT_SECRET_`-prefixed keys, so a bare `NAME=…` in `.env` or `.env.local` stays unresolved. The runner redacts every resolved value, so do not use a placeholder for content a report must show.
|
|
114
114
|
|
|
115
|
+
A **selector-less gesture** — a coordinate `tap`/`long-press`, or a `pinch`/`rotate` with no `on:` — resolves no frame, so a tree source it cannot read does not fail it. It settles best effort, dispatches anyway, and the step **passes carrying a warning** that quotes the source's own error. That green says the gesture was sent, not that it landed: one aimed at a moving element can miss it entirely. Restore the tree source, usually by relaunching the app so the instrumentation loads. Accept the warning only where the app serves no tree at all, and put an explicit `wait:` before a gesture that follows a transition. The first such gesture proves the outage and later ones spend that verdict without paying the settle window again. A tree read that comes back, or a relaunch, retires that verdict — which only makes the next gesture pay a fresh window, and it warns again if the source is still down.
|
|
116
|
+
|
|
115
117
|
## Verification conditions
|
|
116
118
|
|
|
117
119
|
```yaml
|
|
@@ -164,7 +166,7 @@ It **never fails a run.** Every outcome short of a clean settle passes with a wa
|
|
|
164
166
|
- **settled on the UI tree alone** — no screenshot pair could be read, so presentation-layer animation was never waited out.
|
|
165
167
|
- **too few reads** — a settle needs three reads across two intervals and this step got fewer, so it ended with no evidence either way.
|
|
166
168
|
|
|
167
|
-
Only a tree source
|
|
169
|
+
Only a tree source this step could not read stops the run, as an errored step — one still failing when the wait ends, one that wedges after answering, one that answers with an empty tree it flags as degraded (an unattached Vega toolkit, an AX service asking to be relaunched), or one that never answers (raise `timeout` before suspecting the app). The run is then not ok and every later step is skipped. A single failed read is not that: the hold restarts from the next good read. The same outage stops no [selector-less gesture](#directives), which needs no frame and passes with its own warning instead.
|
|
168
170
|
|
|
169
171
|
`idle` proves readiness only and never identifies the screen, so it cannot serve as acceptance evidence or replace the identity gate. Gate the next action on a stable element. Add `idle` during polish after each screen change, not after every step.
|
|
170
172
|
|
|
@@ -239,4 +239,6 @@ Manual rescue invalidates the pass. An `errored` step was never evaluated: an `i
|
|
|
239
239
|
|
|
240
240
|
**A passing step that carries a `warning` is a finding, not noise.** `await: { idle: true }` raises [six different warnings](flow-yaml.md#idle-readiness) and they do not share one meaning. Two say the screen was moving; one says the wait ran out mid-hold and is repaired by raising the step's `timeout:`; one says the tree stayed empty; one says the tree did hold still and only the screenshot pairs were missing, so the capture path is what to check; one says the step ended with no evidence either way. No report separates intended motion from a load that never finished. Read which one it is, look at that screen, disclose what you found, and confirm the following step targets a stable element rather than stillness.
|
|
241
241
|
|
|
242
|
+
A [selector-less gesture](flow-yaml.md#directives) raises a warning of a different shape, not one of those six: a tree-source outage left it unsettled, so it dispatched blind and the green says only that the gesture was sent. Restore the source, usually by relaunching the app so the instrumentation loads. Accept it only where the app serves no tree at all, such as the [injection-free iOS form](reliability-and-recovery.md#terminally-non-injectable-ios-apps).
|
|
243
|
+
|
|
242
244
|
One uninterrupted full pass completes a normal flow. `argent-qa-flows` requires two consecutive passes of unchanged YAML. For CI, use `argent flow run <name> [--platform ...]`; it exits non-zero on failure.
|
|
@@ -62,6 +62,8 @@ Apple system apps cannot load the instrumentation, and nothing in the launch pat
|
|
|
62
62
|
- A point focus tap plus raw keyboard with `delayMs: 500`.
|
|
63
63
|
- Raw swipes with `settle: true` because `scroll-to` needs the missing flow tree. Momentum-free scrolling keeps later coordinate taps valid.
|
|
64
64
|
|
|
65
|
+
Every point tap or long-press in such a flow passes **carrying a warning**. The app loads no instrumentation, so every tree read fails and each [selector-less gesture](flow-yaml.md#directives) dispatches unsettled. Nothing here repairs it. Accept the warnings, read each green as "the gesture was sent, not that it landed", and put an explicit `wait:` or a raw `tool: await-ui-element` before a gesture that follows a transition. Raw `tool:` steps never take that settle, so they never warn.
|
|
66
|
+
|
|
65
67
|
Report that the flow is injection-free and its coordinates are not portable. It cannot satisfy the QA contract. Report the artifact and platform blocker instead.
|
|
66
68
|
|
|
67
69
|
The same fragment fallback covers a normally injectable app that is broken in the environment: raw `restart-app` in place of `launch:` still makes a self-resetting flow. Either way it is not e2e and cannot complete `argent-qa-flows`, which requires a leading `launch:`. Report the blocker rather than labeling that fallback a completed QA test.
|
|
@@ -111,6 +113,7 @@ Classify before editing:
|
|
|
111
113
|
| Partial divergence | An intermediate result disagrees with its echo | Find the first divergent transition |
|
|
112
114
|
| Acceptance failure | Actions pass but a requested check fails | Preserve the check and investigate behavior |
|
|
113
115
|
| Idle warning | A readiness step passes without settling | Read [which of the six warnings](flow-yaml.md#idle-readiness) it is, then gate the next action on a stable element |
|
|
116
|
+
| Unsettled gesture | A selector-less gesture passes unsettled | Restore the tree source, usually by relaunching the app; the green says [only that the gesture was sent](flow-yaml.md#directives) |
|
|
114
117
|
|
|
115
118
|
Then:
|
|
116
119
|
|
|
@@ -101,7 +101,7 @@ After the last edit and audit, set the streak to zero:
|
|
|
101
101
|
2. Seed, review, and freeze snapshot baselines. Baseline updates do not count as passes.
|
|
102
102
|
3. Before mobile pass 1, recycle Argent services for this flow's device: two warm passes are correlated evidence, because a fixed timing margin can pass twice simply because environment speed did not change. Scope `stop-all-simulator-servers` to `devices: [<device>]`. Never omit the scope — a bare call is the machine-wide sweep, and step 7 restarts this proof often enough to reap every other agent's devices repeatedly. Use the MCP call for `flow-execute`, or `argent run stop-all-simulator-servers --devices <device>` from the standalone runner's install. The reset must not change app or account data. For Chromium, let the runner boot the declared app and omit `device`. Vega owns no recyclable Argent services, so the teardown is a no-op there and both passes are warm.
|
|
103
103
|
4. Run from the flow's launch and setup without baseline-update mode. Count a pass only when `ok: true` and every acceptance check executed. A false `when:` can skip optional setup only. An errored step does not advance the streak, and the count mixes two kinds — read each reason. One that could not run (an unreadable tree under `idle`, an unresolvable `run:` target) is environment: fix it and rerun. **A failed `launch:` also scores `errored`, and it is a verdict about the app** — an app that no longer installs or starts is the regression this test exists to catch, so report it instead of rerunning.
|
|
104
|
-
5. Resolve every passing-step warning before completion. `await: { idle: true }` raises [six different warnings](../argent-create-flow/references/flow-yaml.md#idle-readiness), so read which one it is first. Two say the screen was moving. One says the wait ran out mid-hold and needs a larger `timeout:`. One says the tree stayed empty. One — **settled on the UI tree alone** — says the hierarchy did hold still and only the screenshot pairs were missing, so inspect the capture path rather than the app's rendering. One says the step ended with no evidence either way. Inspect the screen, disclose the cause, and verify that surrounding acceptance checks use stable elements rather than stillness.
|
|
104
|
+
5. Resolve every passing-step warning before completion. `await: { idle: true }` raises [six different warnings](../argent-create-flow/references/flow-yaml.md#idle-readiness), so read which one it is first. Two say the screen was moving. One says the wait ran out mid-hold and needs a larger `timeout:`. One says the tree stayed empty. One — **settled on the UI tree alone** — says the hierarchy did hold still and only the screenshot pairs were missing, so inspect the capture path rather than the app's rendering. One says the step ended with no evidence either way. Inspect the screen, disclose the cause, and verify that surrounding acceptance checks use stable elements rather than stillness. A [selector-less gesture](../argent-create-flow/references/flow-yaml.md#directives) — a coordinate `tap`/`long-press`, or a `pinch`/`rotate` with no `on:` — warns in a different shape: a tree-source outage left it unsettled, so it dispatched blind and the green says only that the gesture was sent. Restore the tree source, usually by relaunching the app so the instrumentation loads, and rerun. Accepting that warning needs an app that serves no tree, which cannot satisfy this contract anyway.
|
|
105
105
|
6. Run the same YAML again immediately with the same runner. Do not manually reset app or account data.
|
|
106
106
|
7. Reset the streak after any failure, edit, re-recording, baseline update, or state-changing manual recovery. Repair through `argent-create-flow`, audit again, and restart with fresh services.
|
|
107
107
|
|