@e2e-dev/decision 0.2.0-nightly-20261009094406 → 0.2.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/README.md +14 -21
- package/dist/.build.json +1 -1
- package/dist/budget.d.ts +19 -0
- package/dist/budget.js +29 -0
- package/dist/decide.d.ts +8 -1
- package/dist/decide.js +70 -37
- package/dist/dispatch.d.ts +37 -0
- package/dist/dispatch.js +67 -0
- package/dist/elements.d.ts +32 -7
- package/dist/elements.js +153 -74
- package/dist/executor.js +300 -215
- package/dist/hash.d.ts +2 -0
- package/dist/hash.js +21 -0
- package/dist/locator.d.ts +38 -0
- package/dist/locator.js +91 -0
- package/dist/overlay.d.ts +59 -0
- package/dist/overlay.js +121 -0
- package/dist/params.d.ts +3 -0
- package/dist/params.js +39 -0
- package/dist/picks.d.ts +36 -0
- package/dist/picks.js +49 -0
- package/dist/questions.d.ts +56 -35
- package/dist/questions.js +203 -112
- package/dist/text.d.ts +31 -5
- package/dist/text.js +67 -53
- package/dist/types.d.ts +12 -0
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @e2e-dev/decision
|
|
2
2
|
|
|
3
|
-
Run `agent.act` and `agent.assert` through a decision model instead of an
|
|
4
|
-
|
|
5
|
-
distributions
|
|
6
|
-
|
|
7
|
-
later.
|
|
3
|
+
Run `agent.act` and `agent.assert` through a decision model instead of an
|
|
4
|
+
LLM. Any AI SDK decision model that answers `choice` questions with
|
|
5
|
+
probability distributions works, such as TypeSafe Jev or OpenAI `gpt-6-luna`
|
|
6
|
+
through `@ai-sdk/openai` 4.0.90 or later. A small language model writes
|
|
7
|
+
field values and upload paths. Needs `ai` 7.0.134 or later.
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import type { E2EConfig } from 'e2e';
|
|
@@ -24,21 +24,14 @@ export default {
|
|
|
24
24
|
} satisfies E2EConfig;
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
only; the text model keeps the agents entry's `providerOptions`.
|
|
36
|
-
|
|
37
|
-
The runner authorizes every dispatched action and records every model call
|
|
38
|
-
against the step budget. Secrets stay declared handles filled only through
|
|
39
|
-
`typeSecret`; password fields never reach the text model. `minProbability`
|
|
40
|
-
and `minConfidence` gates are off by default. A `done`/`failed` claim passes
|
|
41
|
-
an independent fresh-screen check before it concludes the step.
|
|
27
|
+
Tests stay plain natural language, with no params. Each turn asks the
|
|
28
|
+
operation, then the target for it. Every operation the engine declares is
|
|
29
|
+
offered, and every target question carries a `none` option. With
|
|
30
|
+
`vision: true` and a model that takes images, such as
|
|
31
|
+
`openai.decisionModel('gpt-6-luna')`, verdicts see a masked screenshot and
|
|
32
|
+
`tap_at` reaches a drawn control. The runner authorizes every action and
|
|
33
|
+
records every model call against the step budget. Secrets stay declared
|
|
34
|
+
handles that only `typeSecret` fills.
|
|
42
35
|
|
|
43
36
|
See the [decision models guide](https://e2e.tester.army/docs/decision-models)
|
|
44
|
-
for setup, gates,
|
|
37
|
+
for setup, vision, gates, provider options, and limits.
|
package/dist/.build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.2.0
|
|
1
|
+
{"version":"0.2.0"}
|
package/dist/budget.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StepExecutorContext } from 'e2e';
|
|
2
|
+
/** What one model call came back with: its value, the tokens it used, and the model id the response named. */
|
|
3
|
+
export interface Recorded<T> {
|
|
4
|
+
readonly value: T;
|
|
5
|
+
readonly usage: {
|
|
6
|
+
readonly inputTokens?: number | undefined;
|
|
7
|
+
readonly outputTokens?: number | undefined;
|
|
8
|
+
};
|
|
9
|
+
readonly modelId?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Runs one model call against the step budget: aborted before it starts or
|
|
13
|
+
* after it returns, and recorded with its timing and tokens even when it
|
|
14
|
+
* throws. Errors propagate untouched for the caller to map.
|
|
15
|
+
*/
|
|
16
|
+
export declare function recorded<T>(ctx: StepExecutorContext, model: {
|
|
17
|
+
readonly provider: string;
|
|
18
|
+
readonly modelId: string;
|
|
19
|
+
}, call: () => Promise<Recorded<T>>): Promise<T>;
|
package/dist/budget.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one model call against the step budget: aborted before it starts or
|
|
3
|
+
* after it returns, and recorded with its timing and tokens even when it
|
|
4
|
+
* throws. Errors propagate untouched for the caller to map.
|
|
5
|
+
*/
|
|
6
|
+
export async function recorded(ctx, model, call) {
|
|
7
|
+
ctx.signal.throwIfAborted();
|
|
8
|
+
const started = performance.now();
|
|
9
|
+
const startedAt = new Date().toISOString();
|
|
10
|
+
let usage = {};
|
|
11
|
+
let modelId = model.modelId;
|
|
12
|
+
try {
|
|
13
|
+
const result = await call();
|
|
14
|
+
ctx.signal.throwIfAborted();
|
|
15
|
+
usage = result.usage;
|
|
16
|
+
modelId = result.modelId ?? modelId;
|
|
17
|
+
return result.value;
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
ctx.budgets.recordModelCall({
|
|
21
|
+
provider: model.provider,
|
|
22
|
+
modelId,
|
|
23
|
+
startedAt,
|
|
24
|
+
durationMs: performance.now() - started,
|
|
25
|
+
...(usage.inputTokens === undefined ? {} : { inputTokens: usage.inputTokens }),
|
|
26
|
+
...(usage.outputTokens === undefined ? {} : { outputTokens: usage.outputTokens }),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/decide.d.ts
CHANGED
|
@@ -8,11 +8,18 @@ import type { DecisionExecutorOptions } from './types.ts';
|
|
|
8
8
|
* the user gets the version to install instead of a link error.
|
|
9
9
|
*/
|
|
10
10
|
export declare function requireDecide(): typeof ai.experimental_decide;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* One gated answer: the chosen option, its probability, and the provider
|
|
13
|
+
* confidence. A score answer reads its most probable level as the choice,
|
|
14
|
+
* carries the probability-weighted level as `score`, and its probability
|
|
15
|
+
* is the mass on the levels within half a level of that score: a spread
|
|
16
|
+
* over two adjacent columns is a precise position, not an uncertain one.
|
|
17
|
+
*/
|
|
12
18
|
export interface Decision {
|
|
13
19
|
readonly choice: string;
|
|
14
20
|
readonly probability: number;
|
|
15
21
|
readonly confidence: number;
|
|
22
|
+
readonly score?: number;
|
|
16
23
|
}
|
|
17
24
|
/**
|
|
18
25
|
* Makes one decide call under the step budget and returns the answers.
|
package/dist/decide.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { AgentError, isAgentError } from 'e2e/agent';
|
|
2
2
|
import { ConfigurationError } from 'e2e/engine';
|
|
3
3
|
import * as ai from 'ai';
|
|
4
|
-
import { InvalidArgumentError, InvalidResponseDataError, JSONParseError, LoadAPIKeyError, TypeValidationError, } from 'ai';
|
|
4
|
+
import { Experimental_DecisionRefusalError, InvalidArgumentError, InvalidResponseDataError, JSONParseError, LoadAPIKeyError, TypeValidationError, } from 'ai';
|
|
5
5
|
import { missingKey } from './api-key.js';
|
|
6
|
+
import { recorded } from './budget.js';
|
|
7
|
+
/** Transport retries per decide call. */
|
|
8
|
+
const MAX_RETRIES = 3;
|
|
6
9
|
/**
|
|
7
10
|
* The SDK's decide call, or INVALID_CONFIG when the installed `ai` predates
|
|
8
11
|
* it. Read off the namespace so an older `ai` still loads this module and
|
|
@@ -11,7 +14,7 @@ import { missingKey } from './api-key.js';
|
|
|
11
14
|
export function requireDecide() {
|
|
12
15
|
const found = ai.experimental_decide;
|
|
13
16
|
if (typeof found !== 'function') {
|
|
14
|
-
throw new ConfigurationError('INVALID_CONFIG', 'decisionExecutor() needs ai 7.0.
|
|
17
|
+
throw new ConfigurationError('INVALID_CONFIG', 'decisionExecutor() needs ai 7.0.134 or later; update the ai package');
|
|
15
18
|
}
|
|
16
19
|
return found;
|
|
17
20
|
}
|
|
@@ -21,46 +24,37 @@ export function requireDecide() {
|
|
|
21
24
|
* there stays a one-file change.
|
|
22
25
|
*/
|
|
23
26
|
export async function decide(ctx, model, request, providerOptions) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let modelId = model.modelId;
|
|
30
|
-
let providerMetadata;
|
|
27
|
+
const screenshot = request.screenshot;
|
|
28
|
+
// The screenshot rides beside the JSON state as a file part; a text-only provider rejects it before the request.
|
|
29
|
+
const state = screenshot === undefined
|
|
30
|
+
? request.state
|
|
31
|
+
: [{ type: 'json', value: request.state }, { type: 'file', mediaType: screenshot.mediaType, data: screenshot.data }];
|
|
31
32
|
let answers;
|
|
33
|
+
let providerMetadata;
|
|
32
34
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
({ answers, providerMetadata } = await recorded(ctx, model, async () => {
|
|
36
|
+
const result = await requireDecide()({
|
|
37
|
+
model,
|
|
38
|
+
state,
|
|
39
|
+
questions: request.questions,
|
|
40
|
+
...(providerOptions === undefined ? {} : { providerOptions }),
|
|
41
|
+
// Transport retries for a rate limit or a 5xx, as the runner's own adapter has; a bad answer never retries.
|
|
42
|
+
maxRetries: MAX_RETRIES,
|
|
43
|
+
abortSignal: ctx.signal,
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
value: {
|
|
47
|
+
answers: result.answers,
|
|
48
|
+
providerMetadata: result.providerMetadata,
|
|
49
|
+
},
|
|
50
|
+
usage: result.usage,
|
|
51
|
+
modelId: result.response.modelId,
|
|
52
|
+
};
|
|
53
|
+
}));
|
|
50
54
|
}
|
|
51
55
|
catch (error) {
|
|
52
56
|
throw decideError(error, ctx.signal);
|
|
53
57
|
}
|
|
54
|
-
finally {
|
|
55
|
-
ctx.budgets.recordModelCall({
|
|
56
|
-
provider: model.provider,
|
|
57
|
-
modelId,
|
|
58
|
-
startedAt,
|
|
59
|
-
durationMs: performance.now() - started,
|
|
60
|
-
...(inputTokens === undefined ? {} : { inputTokens }),
|
|
61
|
-
...(outputTokens === undefined ? {} : { outputTokens }),
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
58
|
const decisions = {};
|
|
65
59
|
for (const [id, answer] of Object.entries(answers)) {
|
|
66
60
|
decisions[id] = { ...selected(answer), confidence: reportedConfidence(providerMetadata, id) };
|
|
@@ -69,9 +63,24 @@ export async function decide(ctx, model, request, providerOptions) {
|
|
|
69
63
|
}
|
|
70
64
|
/** The chosen option and its probability; anything without a valid distribution is MODEL_OUTPUT_INVALID. */
|
|
71
65
|
function selected(answer) {
|
|
72
|
-
if (answer === undefined || answer.type !== 'choice'
|
|
66
|
+
if (answer === undefined || answer.probabilities === undefined || (answer.type !== 'choice' && answer.type !== 'score')) {
|
|
73
67
|
throw new AgentError('MODEL_OUTPUT_INVALID', 'The decision model returned an answer without a choice distribution.');
|
|
74
68
|
}
|
|
69
|
+
if (answer.type === 'score') {
|
|
70
|
+
const score = answer.score;
|
|
71
|
+
if (typeof score !== 'number' || !Number.isFinite(score)) {
|
|
72
|
+
throw new AgentError('MODEL_OUTPUT_INVALID', 'The decision model returned a score answer without a score.');
|
|
73
|
+
}
|
|
74
|
+
let top = { choice: '', probability: -1 };
|
|
75
|
+
let nearby = 0;
|
|
76
|
+
for (const [level, probability] of Object.entries(answer.probabilities)) {
|
|
77
|
+
if (probability > top.probability)
|
|
78
|
+
top = { choice: level, probability };
|
|
79
|
+
if (Math.abs(Number(level) - score) <= 0.5)
|
|
80
|
+
nearby += probability;
|
|
81
|
+
}
|
|
82
|
+
return { choice: top.choice, probability: Math.min(1, nearby), score };
|
|
83
|
+
}
|
|
75
84
|
const probability = answer.choice === undefined ? 0 : (answer.probabilities[answer.choice] ?? 0);
|
|
76
85
|
if (!Number.isFinite(probability) || probability < 0 || probability > 1) {
|
|
77
86
|
throw new AgentError('MODEL_OUTPUT_INVALID', 'The decision model returned a probability outside [0, 1].');
|
|
@@ -99,8 +108,32 @@ function decideError(error, signal) {
|
|
|
99
108
|
return missingKey('decision', error);
|
|
100
109
|
if (InvalidArgumentError.isInstance(error))
|
|
101
110
|
return error;
|
|
111
|
+
const refusedIds = refused(error);
|
|
112
|
+
if (refusedIds !== undefined) {
|
|
113
|
+
const which = refusedIds.length === 0 ? 'a question' : refusedIds.map((id) => JSON.stringify(id)).join(', ');
|
|
114
|
+
return new AgentError('MODEL_OUTPUT_INVALID', `The decision model refused to answer ${which}.`);
|
|
115
|
+
}
|
|
102
116
|
if (InvalidResponseDataError.isInstance(error) || TypeValidationError.isInstance(error) || JSONParseError.isInstance(error)) {
|
|
103
117
|
return new AgentError('MODEL_OUTPUT_INVALID', 'The decision model returned an invalid answer.');
|
|
104
118
|
}
|
|
105
119
|
return new AgentError('MODEL_PROVIDER_FAILED', 'The decision model call failed.');
|
|
106
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The question ids the decision model refused, or undefined when the error
|
|
123
|
+
* is no refusal. The SDK throws its own refusal error naming them; a
|
|
124
|
+
* provider that reports the refusal as an answer of the wrong type is read
|
|
125
|
+
* from the rejected answers.
|
|
126
|
+
*/
|
|
127
|
+
function refused(error) {
|
|
128
|
+
if (Experimental_DecisionRefusalError.isInstance(error))
|
|
129
|
+
return error.questionIds;
|
|
130
|
+
if (!InvalidResponseDataError.isInstance(error))
|
|
131
|
+
return undefined;
|
|
132
|
+
const answers = error.data;
|
|
133
|
+
if (typeof answers !== 'object' || answers === null)
|
|
134
|
+
return undefined;
|
|
135
|
+
const ids = Object.entries(answers)
|
|
136
|
+
.filter(([, answer]) => typeof answer === 'object' && answer !== null && answer.type === 'refusal')
|
|
137
|
+
.map(([id]) => id);
|
|
138
|
+
return ids.length === 0 ? undefined : ids;
|
|
139
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { StepExecutorContext } from 'e2e';
|
|
2
|
+
import type { Control, Target } from './elements.ts';
|
|
3
|
+
import type { Point } from './overlay.ts';
|
|
4
|
+
/** Operations that take a target and nothing else. */
|
|
5
|
+
export type Pointer = 'tap' | 'double_tap' | 'long_press' | 'hover' | 'secondary_tap' | 'scroll_to' | 'submit' | 'check' | 'select';
|
|
6
|
+
/** One action the loop dispatches, with everything its operation needs. */
|
|
7
|
+
export type Action = {
|
|
8
|
+
readonly operation: Pointer;
|
|
9
|
+
readonly target: Target;
|
|
10
|
+
} | {
|
|
11
|
+
readonly operation: 'type';
|
|
12
|
+
readonly target: Target;
|
|
13
|
+
readonly text: string;
|
|
14
|
+
} | {
|
|
15
|
+
readonly operation: 'typeSecret';
|
|
16
|
+
readonly target: Target;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly operation: 'upload';
|
|
20
|
+
readonly target: Target;
|
|
21
|
+
readonly paths: readonly string[];
|
|
22
|
+
} | {
|
|
23
|
+
readonly operation: 'drag';
|
|
24
|
+
readonly target: Target;
|
|
25
|
+
readonly destinationId: string;
|
|
26
|
+
} | {
|
|
27
|
+
readonly operation: 'tap_at';
|
|
28
|
+
readonly point: Point;
|
|
29
|
+
} | {
|
|
30
|
+
readonly operation: Control;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Performs one action through the runner's grammar: the only place that
|
|
34
|
+
* touches `ctx.actions`. Resolves to a note for the model when the engine
|
|
35
|
+
* reports something worth telling it, nothing otherwise.
|
|
36
|
+
*/
|
|
37
|
+
export declare function perform(ctx: StepExecutorContext, action: Action): Promise<string | undefined>;
|
package/dist/dispatch.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs one action through the runner's grammar: the only place that
|
|
3
|
+
* touches `ctx.actions`. Resolves to a note for the model when the engine
|
|
4
|
+
* reports something worth telling it, nothing otherwise.
|
|
5
|
+
*/
|
|
6
|
+
export async function perform(ctx, action) {
|
|
7
|
+
const { actions } = ctx;
|
|
8
|
+
switch (action.operation) {
|
|
9
|
+
case 'scroll_up':
|
|
10
|
+
await actions.scroll('up');
|
|
11
|
+
return undefined;
|
|
12
|
+
case 'scroll_down':
|
|
13
|
+
await actions.scroll('down');
|
|
14
|
+
return undefined;
|
|
15
|
+
case 'back':
|
|
16
|
+
await actions.back();
|
|
17
|
+
return undefined;
|
|
18
|
+
case 'tap':
|
|
19
|
+
await actions.tap(node(action.target));
|
|
20
|
+
return undefined;
|
|
21
|
+
case 'double_tap':
|
|
22
|
+
await actions.doubleTap(node(action.target));
|
|
23
|
+
return undefined;
|
|
24
|
+
case 'long_press':
|
|
25
|
+
await actions.longPress(node(action.target));
|
|
26
|
+
return undefined;
|
|
27
|
+
case 'hover':
|
|
28
|
+
await actions.hover(node(action.target));
|
|
29
|
+
return undefined;
|
|
30
|
+
case 'secondary_tap':
|
|
31
|
+
await actions.secondaryTap(node(action.target));
|
|
32
|
+
return undefined;
|
|
33
|
+
case 'scroll_to':
|
|
34
|
+
await actions.scrollTo(node(action.target));
|
|
35
|
+
return undefined;
|
|
36
|
+
case 'submit':
|
|
37
|
+
await actions.press(node(action.target), 'Enter');
|
|
38
|
+
return undefined;
|
|
39
|
+
case 'check':
|
|
40
|
+
await actions.check(node(action.target), !(action.target.checked ?? false));
|
|
41
|
+
return undefined;
|
|
42
|
+
case 'select':
|
|
43
|
+
await actions.select(node(action.target), action.target.optionLabel ?? '');
|
|
44
|
+
return undefined;
|
|
45
|
+
case 'type':
|
|
46
|
+
await actions.type(node(action.target), action.text);
|
|
47
|
+
return undefined;
|
|
48
|
+
case 'typeSecret':
|
|
49
|
+
await actions.typeSecret(node(action.target), action.name);
|
|
50
|
+
return undefined;
|
|
51
|
+
case 'upload':
|
|
52
|
+
await actions.upload(node(action.target), action.paths);
|
|
53
|
+
return undefined;
|
|
54
|
+
case 'drag':
|
|
55
|
+
await actions.drag(node(action.target), { id: action.destinationId });
|
|
56
|
+
return undefined;
|
|
57
|
+
case 'tap_at': {
|
|
58
|
+
// The engine's prose for a bare point ("no listed control is there")
|
|
59
|
+
// reads as a miss to a classifier; whether the page changed says more.
|
|
60
|
+
const result = await actions.tapAt(action.point);
|
|
61
|
+
return result.target === undefined ? undefined : `landed on listed control ${result.target.id}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function node(target) {
|
|
66
|
+
return { id: target.id };
|
|
67
|
+
}
|
package/dist/elements.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ExecutorNode, StepExecutorContext } from 'e2e';
|
|
1
|
+
import type { ExecutorNode, ExecutorPixels, StepExecutorContext } from 'e2e';
|
|
2
2
|
/** Operations the executor offers on elements. */
|
|
3
|
-
export type Operation = 'tap' | 'type' | 'typeSecret' | 'submit' | 'select' | 'check';
|
|
3
|
+
export type Operation = 'tap' | 'type' | 'typeSecret' | 'submit' | 'select' | 'check' | 'hover' | 'secondary_tap' | 'double_tap' | 'long_press' | 'drag' | 'scroll_to' | 'upload' | 'tap_at';
|
|
4
4
|
/** Viewport and history controls, always offered when the engine declares the verb. */
|
|
5
5
|
export type Control = 'scroll_up' | 'scroll_down' | 'back';
|
|
6
6
|
/** Terminal choices: the step claims completion, reports a product failure, or gives up. */
|
|
@@ -15,23 +15,42 @@ export interface Element {
|
|
|
15
15
|
readonly expanded?: boolean;
|
|
16
16
|
readonly operations: readonly Operation[];
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** One target of an operation: the node it acts on, as data. The model only ever names the key. */
|
|
19
19
|
export interface Target {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
/** How the action reads in history, e.g. `tap Save [n4]`. */
|
|
20
22
|
readonly description: string;
|
|
21
|
-
/** Text for `type`, the secret name for `typeSecret`, nothing otherwise. */
|
|
22
|
-
run(argument?: string): Promise<void>;
|
|
23
23
|
/** Native-select option label, set only for `select` options; names the choice criterion. */
|
|
24
24
|
readonly optionLabel?: string;
|
|
25
|
+
/** The checked state a `check` flips. */
|
|
26
|
+
readonly checked?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** One drop destination for `drag`, keyed like an element. */
|
|
29
|
+
export interface Destination {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly label: string;
|
|
32
|
+
readonly role: string;
|
|
25
33
|
}
|
|
26
34
|
export interface ActionSpace {
|
|
27
35
|
readonly elements: readonly Element[];
|
|
28
|
-
/**
|
|
36
|
+
/** The element behind a target key, for labels and field descriptions. */
|
|
37
|
+
element(key: string): Element | undefined;
|
|
38
|
+
/** Per operation: target key -> target. Only operations with at least one target appear. */
|
|
29
39
|
readonly targets: ReadonlyMap<Operation, ReadonlyMap<string, Target>>;
|
|
30
|
-
readonly controls:
|
|
40
|
+
readonly controls: ReadonlySet<Control>;
|
|
41
|
+
/** The operation or control an answer names, typed; undefined for anything not offered. */
|
|
42
|
+
operation(choice: string): Operation | undefined;
|
|
43
|
+
control(choice: string): Control | undefined;
|
|
44
|
+
/** Where a `drag` can drop, by element key; empty when nothing can receive a drop. */
|
|
45
|
+
readonly destinations: ReadonlyMap<string, Destination>;
|
|
46
|
+
/** Whether `tap_at` is offered: a screenshot, the `tapAt` verb, and a step that locates. */
|
|
47
|
+
readonly tapAt: boolean;
|
|
31
48
|
/** Elements left out to stay under the per-question cap; scrolling can bring them into view. */
|
|
32
49
|
readonly omitted: number;
|
|
33
50
|
/** Non-interactive page text from the tree, without node ids, clipped to 6000 chars. */
|
|
34
51
|
readonly pageText: string;
|
|
52
|
+
/** Live regions (status, alert, log) as `name: text`, the lines a completion check reads first. */
|
|
53
|
+
readonly statuses: readonly string[];
|
|
35
54
|
/** Stable hash of path, tree content with node ids removed, and which nodes are in view. */
|
|
36
55
|
readonly fingerprint: string;
|
|
37
56
|
}
|
|
@@ -43,7 +62,13 @@ interface SpaceObservation {
|
|
|
43
62
|
readonly height: number;
|
|
44
63
|
};
|
|
45
64
|
readonly tree: ExecutorNode;
|
|
65
|
+
/** Granted pixels; with the `tapAt` verb and a step that locates they open `tap_at`. */
|
|
66
|
+
readonly pixels?: ExecutorPixels;
|
|
67
|
+
/** Whether the step can locate a drawn control on the pixels: the decision model scores and a text model names it. */
|
|
68
|
+
readonly locates?: boolean;
|
|
46
69
|
}
|
|
47
70
|
/** Builds the element table and bound targets from the newest observation. */
|
|
48
71
|
export declare function actionSpace(ctx: StepExecutorContext, observation: SpaceObservation, canType: boolean): ActionSpace;
|
|
72
|
+
/** Element index behind a target key (`7` for both `7` and `7:2`). */
|
|
73
|
+
export declare function targetKeyIndex(key: string): string;
|
|
49
74
|
export {};
|