@deeeed/metamask-harness 0.47.2 → 0.47.3
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.47.3 - 2026-09-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Select the requested Mobile Perps mode through the first-entry chooser, verify its detail root, and restore the requested market after mode selection changes the navigation stack.
|
|
10
|
+
- Preserve earlier `call` evidence by assigning each invocation a fresh default artifact directory; approval retries still use the explicit directory in the printed next command.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Teach authors to inspect existing state and limit setup mutations to prerequisites required by their proof.
|
|
15
|
+
|
|
5
16
|
## 0.47.2 - 2026-09-05
|
|
6
17
|
|
|
7
18
|
### Changed
|
|
@@ -195,6 +195,7 @@ const NESTED_ROUTE_PARENTS = {
|
|
|
195
195
|
PerpsPnlHeroCard: 'Perps', PerpsHIP3Debug: 'Perps',
|
|
196
196
|
PerpsSelectModifyAction: 'Perps', PerpsSelectAdjustMarginAction: 'Perps',
|
|
197
197
|
PerpsSelectOrderType: 'Perps', PerpsTradingView: 'Perps',
|
|
198
|
+
PerpsModeSelection: 'PerpsModals',
|
|
198
199
|
// Predict
|
|
199
200
|
PredictMarketList: 'Predict', PredictMarketDetails: 'Predict',
|
|
200
201
|
PredictActivityDetail: 'Predict',
|
package/dist/commands/call.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { resolveActionManifest } from "../manifest.js";
|
|
5
5
|
import { importRecipeProtocol } from "../paths.js";
|
|
@@ -212,7 +212,7 @@ async function handleCall(argv) {
|
|
|
212
212
|
}
|
|
213
213
|
return EXIT.validation;
|
|
214
214
|
}
|
|
215
|
-
const artifactsDir = optionString(options, "artifactsDir") ?? defaultCallArtifactsDir(target, resolvedAction
|
|
215
|
+
const artifactsDir = optionString(options, "artifactsDir") ?? defaultCallArtifactsDir(target, resolvedAction);
|
|
216
216
|
recordCommandEvidence(artifactsDir);
|
|
217
217
|
const requestedRuntimeOptions = runtimeOptionsFromCli(options);
|
|
218
218
|
const inheritedSource = process.env.FARMSLOT_RECIPE_SOURCE_TRUST || process.env.FARMSLOT_RECIPE_SOURCE_KIND || process.env.FARMSLOT_RECIPE_SOURCE_NAME || process.env.FARMSLOT_RECIPE_SOURCE_DIGEST;
|
|
@@ -493,10 +493,9 @@ function renderDefaultsUsed(defaults, stream) {
|
|
|
493
493
|
const values = Object.entries(defaults).map(([name, value]) => `${out("cmd", name)}=${out("accent", JSON.stringify(value) ?? String(value))}`).join(", ");
|
|
494
494
|
return `${out("label", "Defaults used:")} ${values}`;
|
|
495
495
|
}
|
|
496
|
-
function defaultCallArtifactsDir(target, action
|
|
496
|
+
function defaultCallArtifactsDir(target, action) {
|
|
497
497
|
const actionStem = action.replace(/[^a-zA-Z0-9._-]/gu, "_");
|
|
498
|
-
|
|
499
|
-
return path.join(target, "temp", "recipe", "calls", `${actionStem}-${digest}`);
|
|
498
|
+
return path.join(target, "temp", "recipe", "calls", `${actionStem}-${randomUUID()}`);
|
|
500
499
|
}
|
|
501
500
|
function readCallOutput(tracePath) {
|
|
502
501
|
try {
|
|
@@ -131,7 +131,7 @@ function withRemainingDeadline(input, requestedDeadline, attemptTimeoutMs) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
async function
|
|
134
|
+
async function queryModeState(input, mode, deadline) {
|
|
135
135
|
const rootTarget = JSON.stringify({
|
|
136
136
|
testId: MODE_ROOT_TEST_IDS[mode],
|
|
137
137
|
visibility: 'viewport',
|
|
@@ -140,6 +140,10 @@ async function isModePresent(input, mode, deadline) {
|
|
|
140
140
|
testId: MODE_TEST_IDS[mode],
|
|
141
141
|
visibility: 'tree',
|
|
142
142
|
});
|
|
143
|
+
const choiceTarget = JSON.stringify({
|
|
144
|
+
testId: `perps-mode-selection-${mode}-option`,
|
|
145
|
+
visibility: 'viewport',
|
|
146
|
+
});
|
|
143
147
|
const result = await evalAsync(
|
|
144
148
|
withRemainingDeadline(input, deadline, READ_ATTEMPT_TIMEOUT_MS),
|
|
145
149
|
`(function(){
|
|
@@ -149,21 +153,25 @@ async function isModePresent(input, mode, deadline) {
|
|
|
149
153
|
}
|
|
150
154
|
return Promise.all([
|
|
151
155
|
api.queryUiTarget(${rootTarget}),
|
|
152
|
-
api.queryUiTarget(${controlTarget})
|
|
156
|
+
api.queryUiTarget(${controlTarget}),
|
|
157
|
+
api.queryUiTarget(${choiceTarget})
|
|
153
158
|
]).then(function(values){
|
|
154
|
-
return JSON.stringify({ root: values[0], control: values[1] });
|
|
159
|
+
return JSON.stringify({ root: values[0], control: values[1], choice: values[2] });
|
|
155
160
|
});
|
|
156
161
|
})()`,
|
|
157
162
|
);
|
|
158
163
|
if (result?.unsupported) {
|
|
159
164
|
throw new Error('metamask.perps.ensure_mode requires UI target queries.');
|
|
160
165
|
}
|
|
161
|
-
return
|
|
166
|
+
return {
|
|
167
|
+
present: result?.root?.visible === true && result?.control?.present === true,
|
|
168
|
+
choiceVisible: result?.choice?.visible === true,
|
|
169
|
+
};
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
async function
|
|
172
|
+
async function readModeState(input, mode, deadline) {
|
|
165
173
|
try {
|
|
166
|
-
return await
|
|
174
|
+
return await queryModeState(input, mode, deadline);
|
|
167
175
|
} catch (error) {
|
|
168
176
|
if (!isTargetTransition(error)) throw error;
|
|
169
177
|
return null;
|
|
@@ -181,14 +189,15 @@ export async function ensureMode(input) {
|
|
|
181
189
|
Number(input.node?.timeout_ms ?? 30_000),
|
|
182
190
|
);
|
|
183
191
|
let switched = false;
|
|
192
|
+
let choiceSelected = false;
|
|
184
193
|
|
|
185
194
|
while (Date.now() < deadline) {
|
|
186
|
-
const
|
|
195
|
+
const requestedState = await readModeState(
|
|
187
196
|
input,
|
|
188
197
|
requestedMode,
|
|
189
198
|
deadline,
|
|
190
199
|
);
|
|
191
|
-
if (
|
|
200
|
+
if (requestedState?.present && !requestedState.choiceVisible) {
|
|
192
201
|
return {
|
|
193
202
|
action: input.action,
|
|
194
203
|
mode: requestedMode,
|
|
@@ -197,13 +206,23 @@ export async function ensureMode(input) {
|
|
|
197
206
|
};
|
|
198
207
|
}
|
|
199
208
|
if (Date.now() >= deadline) break;
|
|
200
|
-
if (
|
|
201
|
-
const
|
|
209
|
+
if (requestedState?.choiceVisible && !choiceSelected) {
|
|
210
|
+
const pressed = await bridgeCommand(withRemainingDeadline(input, deadline), [
|
|
211
|
+
'press-test-id',
|
|
212
|
+
`perps-mode-selection-${requestedMode}-option`,
|
|
213
|
+
]);
|
|
214
|
+
if (pressed?.ok === false) {
|
|
215
|
+
throw new Error(String(pressed.error || 'Perps mode choice press failed.'));
|
|
216
|
+
}
|
|
217
|
+
choiceSelected = true;
|
|
218
|
+
switched = true;
|
|
219
|
+
} else if (requestedState !== null && !switched) {
|
|
220
|
+
const otherState = await readModeState(
|
|
202
221
|
input,
|
|
203
222
|
otherMode,
|
|
204
223
|
deadline,
|
|
205
224
|
);
|
|
206
|
-
if (
|
|
225
|
+
if (otherState?.present && Date.now() < deadline) {
|
|
207
226
|
// The visible control names the mode it will switch to, not the mode
|
|
208
227
|
// currently selected.
|
|
209
228
|
const pressed = await bridgeCommand(withRemainingDeadline(input, deadline), [
|
|
@@ -1818,7 +1837,7 @@ export async function startState(input) {
|
|
|
1818
1837
|
const navigation = await applyStateNavigation(stateInput(), config);
|
|
1819
1838
|
const provider = await ensureProvider(stateInput(), config);
|
|
1820
1839
|
const network = await ensureNetwork(stateInput(), config);
|
|
1821
|
-
|
|
1840
|
+
let resolvedNavigation = provider.changed || network.changed
|
|
1822
1841
|
? await applyStateNavigation(stateInput(), config)
|
|
1823
1842
|
: navigation;
|
|
1824
1843
|
const modeInput = stateInput();
|
|
@@ -1833,6 +1852,9 @@ export async function startState(input) {
|
|
|
1833
1852
|
},
|
|
1834
1853
|
})
|
|
1835
1854
|
: { skipped: true };
|
|
1855
|
+
if (marketMode.alreadySelected === false) {
|
|
1856
|
+
resolvedNavigation = await applyStateNavigation(stateInput(), config);
|
|
1857
|
+
}
|
|
1836
1858
|
const tutorial = await applyTutorialState(stateInput(), config);
|
|
1837
1859
|
const readyToTrade = await assertReadyToTrade(stateInput(), config);
|
|
1838
1860
|
const balance = await assertBalance(stateInput(), config);
|
package/package.json
CHANGED
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"instruction": "Use static call references for unchanged sub-journeys. Keep preparation, action, assertion, evidence, and guaranteed teardown visible as separate nodes with human-facing intent.",
|
|
53
53
|
"details": [
|
|
54
54
|
"Use idempotent ensure actions for required start state and independently assert their postconditions.",
|
|
55
|
+
"Inspect existing state before creating or cleaning fixtures. Establish only prerequisites required by the claim; a history or display check may already have usable records and need no trade or cleanup. Record the selected records and verify they still satisfy the proof boundary.",
|
|
55
56
|
"Actions translate stable UI, CDP, or controller operations. Recipes compose them. Product code owns business rules."
|
|
56
57
|
],
|
|
57
58
|
"commands": []
|