e2e 0.16.0 → 0.18.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 +10 -0
- package/dist/.build.json +1 -1
- package/dist/agent/act.js +7 -4
- package/dist/agent/action-dispatcher.js +4 -5
- package/dist/agent/actions.d.ts +32 -23
- package/dist/agent/actions.js +38 -24
- package/dist/agent/ai-sdk.js +5 -4
- package/dist/agent/error.js +3 -3
- package/dist/agent/invocation.js +3 -4
- package/dist/agent/model/sdk.js +2 -0
- package/dist/agent/model/tool-choice.d.ts +9 -0
- package/dist/agent/model/tool-choice.js +14 -0
- package/dist/agent/observation-feed.d.ts +5 -6
- package/dist/agent/observation-feed.js +8 -11
- package/dist/agent/observation.d.ts +40 -11
- package/dist/agent/observation.js +109 -59
- package/dist/agent/phases.d.ts +7 -1
- package/dist/agent/phases.js +12 -3
- package/dist/agent/point-tap.d.ts +8 -8
- package/dist/agent/point-tap.js +1 -1
- package/dist/agent/prompts.js +3 -0
- package/dist/agent/replay.d.ts +19 -8
- package/dist/agent/replay.js +29 -29
- package/dist/agent/scroll-target.d.ts +2 -3
- package/dist/agent/step-cache.d.ts +43 -15
- package/dist/agent/step-cache.js +156 -67
- package/dist/agent/tool-loop.js +94 -5
- package/dist/cache/anchors.d.ts +72 -23
- package/dist/cache/anchors.js +233 -60
- package/dist/cache/context.d.ts +25 -14
- package/dist/cache/context.js +50 -8
- package/dist/cache/decide.d.ts +2 -2
- package/dist/cache/decide.js +4 -6
- package/dist/cache/identity.d.ts +18 -4
- package/dist/cache/identity.js +9 -4
- package/dist/cache/recorder.d.ts +15 -1
- package/dist/cache/recorder.js +36 -23
- package/dist/cache/rekeyed.d.ts +46 -0
- package/dist/cache/rekeyed.js +89 -0
- package/dist/cache/relocate.d.ts +14 -14
- package/dist/cache/relocate.js +33 -25
- package/dist/cache/route.d.ts +32 -33
- package/dist/cache/route.js +94 -50
- package/dist/cache/store.d.ts +6 -0
- package/dist/cache/store.js +21 -1
- package/dist/cache/trace.d.ts +53 -14
- package/dist/cache/trace.js +76 -30
- package/dist/cli/bin.js +2 -2
- package/dist/cli/index.js +43 -32
- package/dist/cli/init/engines.d.ts +1 -3
- package/dist/cli/init/engines.js +2 -2
- package/dist/cli/init/gateways.d.ts +1 -1
- package/dist/cli/init/gateways.js +11 -1
- package/dist/cli/init/mcp-config.d.ts +2 -1
- package/dist/cli/init/mcp-config.js +8 -3
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/cli/init/versions.d.ts +0 -8
- package/dist/cli/init/versions.js +1 -10
- package/dist/cli/mcp.d.ts +3 -2
- package/dist/cli/mcp.js +52 -16
- package/dist/cli/run-output.d.ts +17 -0
- package/dist/cli/run-output.js +45 -0
- package/dist/collect/collect.js +8 -4
- package/dist/collect/registry.js +27 -16
- package/dist/config/compiled-files.d.ts +36 -0
- package/dist/config/compiled-files.js +50 -0
- package/dist/config/diagnose.js +21 -19
- package/dist/config/esm-hooks.d.ts +39 -44
- package/dist/config/esm-hooks.js +238 -63
- package/dist/config/load.d.ts +1 -7
- package/dist/config/load.js +23 -66
- package/dist/config/register.d.ts +8 -0
- package/dist/config/register.js +9 -0
- package/dist/config/resolve.d.ts +1 -0
- package/dist/config/resolve.js +33 -1
- package/dist/config/specifiers.d.ts +19 -0
- package/dist/config/specifiers.js +33 -0
- package/dist/config/tsconfig.d.ts +56 -0
- package/dist/config/tsconfig.js +115 -0
- package/dist/config/typescript.d.ts +19 -0
- package/dist/config/typescript.js +253 -0
- package/dist/engine/contract.d.ts +1 -0
- package/dist/engine/index.d.ts +4 -2
- package/dist/engine/session.js +2 -2
- package/dist/expect/async.js +4 -2
- package/dist/expect/poll-scope.d.ts +52 -0
- package/dist/expect/poll-scope.js +113 -0
- package/dist/expect/poll.js +33 -6
- package/dist/expect/values.js +11 -4
- package/dist/internal/abandoned.d.ts +11 -0
- package/dist/internal/abandoned.js +23 -0
- package/dist/internal/brands.d.ts +2 -0
- package/dist/internal/brands.js +2 -0
- package/dist/internal/client-identity.d.ts +9 -0
- package/dist/internal/client-identity.js +14 -0
- package/dist/internal/errors.d.ts +10 -3
- package/dist/internal/errors.js +23 -8
- package/dist/internal/node-version.d.ts +16 -7
- package/dist/internal/node-version.js +50 -15
- package/dist/internal/package-manager.js +1 -1
- package/dist/internal/realm-slot.d.ts +5 -4
- package/dist/internal/redact.d.ts +9 -4
- package/dist/internal/redact.js +120 -23
- package/dist/internal/time.d.ts +26 -4
- package/dist/internal/time.js +58 -14
- package/dist/internal/urls.d.ts +9 -5
- package/dist/internal/urls.js +19 -10
- package/dist/locator/engine.d.ts +12 -2
- package/dist/locator/engine.js +55 -14
- package/dist/locator/screen.js +59 -9
- package/dist/mcp/catalog.d.ts +3 -2
- package/dist/mcp/catalog.js +6 -6
- package/dist/mcp/output.d.ts +59 -0
- package/dist/mcp/output.js +129 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +9 -12
- package/dist/mcp/session.d.ts +8 -1
- package/dist/mcp/session.js +56 -15
- package/dist/mcp/tools.js +7 -5
- package/dist/oauth/chatgpt.js +3 -23
- package/dist/oauth/cli.js +1 -0
- package/dist/oauth/copilot.d.ts +5 -4
- package/dist/oauth/copilot.js +80 -7
- package/dist/oauth/grok.js +2 -1
- package/dist/oauth/models.js +2 -1
- package/dist/oauth/opencode-console.d.ts +10 -0
- package/dist/oauth/opencode-console.js +133 -0
- package/dist/oauth/providers/github-copilot.d.ts +12 -0
- package/dist/oauth/providers/github-copilot.js +77 -23
- package/dist/oauth/providers/opencode-console.d.ts +31 -0
- package/dist/oauth/providers/opencode-console.js +148 -0
- package/dist/oauth/providers.d.ts +2 -6
- package/dist/oauth/providers.js +3 -7
- package/dist/oauth/responses.d.ts +9 -0
- package/dist/oauth/responses.js +23 -0
- package/dist/report/build.d.ts +26 -0
- package/dist/report/build.js +16 -8
- package/dist/report/failure-text.d.ts +6 -1
- package/dist/report/failure-text.js +14 -4
- package/dist/report/format.d.ts +10 -5
- package/dist/report/format.js +12 -5
- package/dist/report/junit.js +11 -12
- package/dist/report/list.d.ts +12 -0
- package/dist/report/list.js +61 -24
- package/dist/report/markdown.d.ts +1 -1
- package/dist/report/markdown.js +37 -14
- package/dist/report/repeats.d.ts +6 -1
- package/dist/report/repeats.js +12 -3
- package/dist/run/artifacts.d.ts +22 -4
- package/dist/run/artifacts.js +71 -3
- package/dist/run/execute.d.ts +34 -5
- package/dist/run/execute.js +93 -44
- package/dist/run/extended-fixtures.js +6 -2
- package/dist/run/failure-evidence.js +10 -4
- package/dist/run/fixtures.js +6 -8
- package/dist/run/in-process.d.ts +2 -0
- package/dist/run/in-process.js +52 -10
- package/dist/run/last-run.d.ts +42 -8
- package/dist/run/last-run.js +121 -14
- package/dist/run/process-output.d.ts +73 -0
- package/dist/run/process-output.js +130 -0
- package/dist/run/provision.d.ts +2 -0
- package/dist/run/provision.js +1 -0
- package/dist/run/realm.d.ts +4 -1
- package/dist/run/realm.js +20 -6
- package/dist/run/records.d.ts +20 -4
- package/dist/run/records.js +26 -0
- package/dist/run/retry.d.ts +10 -1
- package/dist/run/retry.js +30 -12
- package/dist/run/runner.d.ts +9 -1
- package/dist/run/runner.js +101 -39
- package/dist/run/scheduler.js +255 -19
- package/dist/run/secrecy.d.ts +22 -19
- package/dist/run/secrecy.js +22 -18
- package/dist/run/serial.d.ts +75 -2
- package/dist/run/serial.js +243 -59
- package/dist/run/standalone.js +1 -1
- package/dist/run/steps.d.ts +0 -6
- package/dist/run/steps.js +3 -20
- package/dist/run/trace-redaction.d.ts +5 -6
- package/dist/run/trace-redaction.js +5 -6
- package/dist/run/worker/entry.js +40 -18
- package/dist/run/worker/handle.js +10 -5
- package/dist/run/worker/protocol.d.ts +85 -3
- package/dist/run/worker/session.d.ts +2 -0
- package/dist/run/worker/session.js +29 -0
- package/dist/telemetry/environment.d.ts +2 -1
- package/dist/telemetry/environment.js +12 -2
- package/dist/telemetry/events.js +1 -0
- package/dist/telemetry/project.d.ts +5 -6
- package/dist/telemetry/project.js +6 -11
- package/dist/telemetry/store.d.ts +5 -5
- package/dist/telemetry/store.js +5 -7
- package/dist/telemetry/telemetry.d.ts +2 -3
- package/dist/telemetry/telemetry.js +2 -4
- package/dist/types.d.ts +15 -6
- package/docs/agent-steps.mdx +1 -0
- package/docs/agents.mdx +7 -3
- package/docs/api-testing.mdx +2 -1
- package/docs/authentication.mdx +6 -3
- package/docs/browser.mdx +49 -12
- package/docs/bug-bash.mdx +2 -1
- package/docs/cache.mdx +66 -19
- package/docs/ci.mdx +14 -9
- package/docs/coding-agents.mdx +2 -1
- package/docs/core-concepts.mdx +208 -0
- package/docs/debugging.mdx +25 -12
- package/docs/decision-models.mdx +142 -0
- package/docs/example-projects.mdx +32 -0
- package/docs/executors.mdx +7 -2
- package/docs/explore.mdx +1 -0
- package/docs/github.mdx +14 -5
- package/docs/index.mdx +9 -5
- package/docs/integrations/eas.mdx +1 -0
- package/docs/integrations/index.mdx +1 -0
- package/docs/integrations/kernel.mdx +3 -2
- package/docs/migrate/cypress.mdx +26 -3
- package/docs/migrate/detox.mdx +24 -1
- package/docs/migrate/maestro.mdx +28 -4
- package/docs/migrate/playwright.mdx +44 -18
- package/docs/migrate/selenium.mdx +27 -3
- package/docs/mobile-ci.mdx +6 -5
- package/docs/mobile.mdx +96 -8
- package/docs/models.mdx +60 -9
- package/docs/quickstart.mdx +28 -16
- package/docs/reference/agent.mdx +13 -3
- package/docs/reference/app.mdx +2 -1
- package/docs/reference/cli.mdx +41 -18
- package/docs/reference/config.mdx +67 -13
- package/docs/reference/engine.mdx +25 -13
- package/docs/reference/environment.mdx +10 -2
- package/docs/reference/errors.mdx +13 -10
- package/docs/reference/expect.mdx +36 -13
- package/docs/reference/mcp.mdx +21 -8
- package/docs/reference/mobile.mdx +24 -13
- package/docs/reference/reporters.mdx +9 -2
- package/docs/reference/screen.mdx +49 -16
- package/docs/reference/test.mdx +32 -5
- package/docs/reference/web.mdx +133 -23
- package/docs/security.mdx +58 -42
- package/docs/subscriptions.mdx +92 -12
- package/docs/telemetry.mdx +9 -6
- package/docs/tools.mdx +1 -0
- package/docs/{starting-your-app.mdx → web.mdx} +157 -11
- package/docs/writing-an-engine.mdx +2 -1
- package/package.json +25 -8
- package/schema/report-v1.schema.json +14 -1
- package/skills/e2e/SKILL.md +5 -3
- package/skills/e2e/references/agent.md +33 -11
- package/skills/e2e/references/debugging.md +4 -4
- package/skills/e2e/references/mcp.md +8 -3
- package/skills/e2e/references/running.md +15 -9
- package/skills/e2e/references/setup.md +45 -16
- package/skills/e2e/references/writing-tests.md +35 -18
- package/docs/viewports.mdx +0 -146
- package/docs/writing-tests.mdx +0 -256
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/** Observation capture, redaction, and model serialization. */
|
|
2
2
|
import type { Observation, SemanticNode, ViewportSize } from '../engine/surface.ts';
|
|
3
|
+
import type { SecretLedger } from '../internal/redact.ts';
|
|
3
4
|
import type { VisionDegradation } from '../run/steps.ts';
|
|
4
5
|
import type { ExecutorNode, ExecutorObservation, ExecutorPixels } from './executor.ts';
|
|
5
6
|
/** Why pixels the caller asked for are not part of this observation. */
|
|
6
7
|
type PixelsWithheld = 'MASKING_UNPROVEN';
|
|
7
8
|
export type AgentObservation = AgentObservationMetadata & ({
|
|
8
9
|
readonly kind: 'semantic';
|
|
9
|
-
readonly nodes: ReadonlyMap<string,
|
|
10
|
+
readonly nodes: ReadonlyMap<string, RedactedNode>;
|
|
10
11
|
readonly parents: ReadonlyMap<string, string>;
|
|
11
|
-
readonly tree:
|
|
12
|
+
readonly tree: RedactedNode;
|
|
12
13
|
readonly truncated: boolean;
|
|
13
14
|
readonly pixels?: ExecutorPixels | undefined;
|
|
14
15
|
readonly pixelsWithheld?: PixelsWithheld | undefined;
|
|
@@ -37,10 +38,11 @@ interface AgentObservationMetadata {
|
|
|
37
38
|
*
|
|
38
39
|
* Pixels whose masking the engine cannot prove (fewer masked regions than
|
|
39
40
|
* secure nodes) are withheld before they reach a model or disk; the semantic
|
|
40
|
-
* tree never carries secure values.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* tree never carries secure values. Every string of every node is redacted
|
|
42
|
+
* once, here (`redactNode`): registered secret values are replaced by their
|
|
43
|
+
* stable secret name, and so is the leading part of one a field the engine
|
|
44
|
+
* cut at its limit ends with. A field holding one keeps no selection, as a
|
|
45
|
+
* secure field keeps none.
|
|
44
46
|
*/
|
|
45
47
|
export declare function prepareObservation(observation: Observation, options: {
|
|
46
48
|
redact: (text: string) => string;
|
|
@@ -52,6 +54,34 @@ export declare function prepareObservation(observation: Observation, options: {
|
|
|
52
54
|
/** The app base URL's origin: link targets on it render as paths, which navigation resolves against it. */
|
|
53
55
|
appOrigin?: string | undefined;
|
|
54
56
|
}): AgentObservation;
|
|
57
|
+
/** What redacts a node: whole values, and the leading part of one a cut field ends with. */
|
|
58
|
+
export type NodeRedaction = Pick<SecretLedger, 'redact' | 'redactCut'>;
|
|
59
|
+
declare const REDACTED: unique symbol;
|
|
60
|
+
/**
|
|
61
|
+
* A semantic node every string of which has passed the attempt's secret
|
|
62
|
+
* ledger (`redactNode`). Model-facing text and executor trees render only
|
|
63
|
+
* from this shape, so a node no redaction touched cannot reach them.
|
|
64
|
+
*/
|
|
65
|
+
export interface RedactedNode extends Omit<SemanticNode, 'children'> {
|
|
66
|
+
readonly [REDACTED]: true;
|
|
67
|
+
readonly children?: readonly RedactedNode[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The node and its subtree with every secret taken out of every field
|
|
71
|
+
* (`NODE_FIELDS`), before any consumer reads it: the model's text, the
|
|
72
|
+
* executor's tree, cache descriptors and anchors, failure evidence.
|
|
73
|
+
*/
|
|
74
|
+
export declare function redactNode(node: SemanticNode, redaction: NodeRedaction): RedactedNode;
|
|
75
|
+
/**
|
|
76
|
+
* An earlier capture's nodes redacted again through the ledger as it is now,
|
|
77
|
+
* each on its own fields, then linked to its redacted-again children, so the
|
|
78
|
+
* tree keeps its shape (which nodes are leaves) without a subtree redacted
|
|
79
|
+
* once per ancestor. A secret a provider resolved after the capture masks
|
|
80
|
+
* text the capture still shows, so the two captures read alike wherever the
|
|
81
|
+
* screen did not change. Redacting redacted text changes nothing else: a
|
|
82
|
+
* marker is never read again.
|
|
83
|
+
*/
|
|
84
|
+
export declare function redactNodesAgain(nodes: ReadonlyMap<string, RedactedNode>, redaction: NodeRedaction): ReadonlyMap<string, RedactedNode>;
|
|
55
85
|
/** Phase metrics describe a node count only when the capture actually read nodes. */
|
|
56
86
|
export declare function observationDetail(observation: AgentObservation): {
|
|
57
87
|
count?: number;
|
|
@@ -82,21 +112,20 @@ export declare const INTERACTIVE_ROLES: ReadonlySet<string>;
|
|
|
82
112
|
*/
|
|
83
113
|
export declare function interactiveNodeCount(observation: Pick<ExecutorObservation, 'text'>): number;
|
|
84
114
|
/**
|
|
85
|
-
* Projects
|
|
86
|
-
*
|
|
87
|
-
* selection at all for a secure node, and a link target bounded as a line
|
|
115
|
+
* Projects a redacted tree onto the executor-facing node shape, the fields
|
|
116
|
+
* the text serialization renders, with a link target bounded as a line
|
|
88
117
|
* bounds it: a target on `appOrigin` keeps that origin and loses no more of
|
|
89
118
|
* its path than the line does. Selectors stay behind — they are relocation
|
|
90
119
|
* material for the trace cache, not something a brain reasons about.
|
|
91
120
|
*/
|
|
92
|
-
export declare function projectTree(node:
|
|
121
|
+
export declare function projectTree(node: RedactedNode, appOrigin?: string): ExecutorNode;
|
|
93
122
|
/**
|
|
94
123
|
* Renders one node as `#id role "name" text="..." value="..." selection="..." [states]`. Role-less text
|
|
95
124
|
* holders omit the role token entirely: on a large screen they are half the
|
|
96
125
|
* lines, and the model needs their text, not a filler word. A link target on
|
|
97
126
|
* `appOrigin`, the app base URL's, renders as its path.
|
|
98
127
|
*/
|
|
99
|
-
export declare function formatNode(node:
|
|
128
|
+
export declare function formatNode(node: RedactedNode, depth: number, appOrigin?: string): string;
|
|
100
129
|
/**
|
|
101
130
|
* What the screen looks like, independent of which observation looked at it.
|
|
102
131
|
*
|
|
@@ -15,10 +15,11 @@ const UNAVAILABLE_TREE_MARKER = '[semantic capture unavailable: no nodes were re
|
|
|
15
15
|
*
|
|
16
16
|
* Pixels whose masking the engine cannot prove (fewer masked regions than
|
|
17
17
|
* secure nodes) are withheld before they reach a model or disk; the semantic
|
|
18
|
-
* tree never carries secure values.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* tree never carries secure values. Every string of every node is redacted
|
|
19
|
+
* once, here (`redactNode`): registered secret values are replaced by their
|
|
20
|
+
* stable secret name, and so is the leading part of one a field the engine
|
|
21
|
+
* cut at its limit ends with. A field holding one keeps no selection, as a
|
|
22
|
+
* secure field keeps none.
|
|
22
23
|
*/
|
|
23
24
|
export function prepareObservation(observation, options) {
|
|
24
25
|
const location = observation.location === undefined ? undefined : options.redact(observation.location);
|
|
@@ -42,11 +43,10 @@ export function prepareObservation(observation, options) {
|
|
|
42
43
|
pixels: pixels.cleared,
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
|
-
const tree =
|
|
46
|
+
const tree = redactNode(observation.tree, options);
|
|
46
47
|
const nodes = new Map();
|
|
47
48
|
const parents = new Map();
|
|
48
49
|
indexNodes(tree, nodes, parents);
|
|
49
|
-
const redact = options.redact;
|
|
50
50
|
const lines = [];
|
|
51
51
|
const encoder = new TextEncoder();
|
|
52
52
|
// The marker is reserved up front so a truncated observation still fits the
|
|
@@ -63,7 +63,7 @@ export function prepareObservation(observation, options) {
|
|
|
63
63
|
const emit = (node, depth) => {
|
|
64
64
|
if (cutByBudget)
|
|
65
65
|
return;
|
|
66
|
-
const line = formatNode(node, depth,
|
|
66
|
+
const line = formatNode(node, depth, options.appOrigin);
|
|
67
67
|
const size = encoder.encode(`${line}\n`).byteLength;
|
|
68
68
|
if (lines.length > 0 && bytes + size > budget) {
|
|
69
69
|
cutByBudget = true;
|
|
@@ -97,40 +97,94 @@ export function prepareObservation(observation, options) {
|
|
|
97
97
|
...(pixels.withheld === undefined ? {} : { pixelsWithheld: pixels.withheld }),
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
-
/**
|
|
101
|
-
const
|
|
102
|
-
['name', OBSERVED_NAME_LIMIT],
|
|
103
|
-
['text', OBSERVED_TEXT_LIMIT],
|
|
104
|
-
['value', OBSERVED_TEXT_LIMIT],
|
|
105
|
-
['selection', OBSERVED_TEXT_LIMIT],
|
|
106
|
-
];
|
|
100
|
+
/** Leaves a field the engine fills from a closed set (an enum, booleans, numbers) or mints itself as it is. */
|
|
101
|
+
const keep = (value) => value;
|
|
107
102
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* with nothing changed is returned as it is.
|
|
103
|
+
* How each field of a node is redacted, one entry per field of
|
|
104
|
+
* `SemanticNode`: a field added to the contract does not compile until it
|
|
105
|
+
* says how its text is redacted, and a key an engine sends that the
|
|
106
|
+
* contract does not name is dropped. Every string passes through
|
|
107
|
+
* `redactText`. A secure node keeps no value or selection, and a selection
|
|
108
|
+
* that may show part of a secret is dropped, as a secure field's is. The ref
|
|
109
|
+
* is the engine's own handle, sent back to it to act, never page text.
|
|
116
110
|
*/
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
111
|
+
const NODE_FIELDS = {
|
|
112
|
+
ref: keep,
|
|
113
|
+
role: (role, _node, redaction) => redactText(role, redaction),
|
|
114
|
+
name: (name, _node, redaction) => redactText(name, redaction, OBSERVED_NAME_LIMIT),
|
|
115
|
+
text: (text, _node, redaction) => redactText(text, redaction, OBSERVED_TEXT_LIMIT),
|
|
116
|
+
value: (value, node, redaction) => (node.states?.secure === true ? undefined : redactText(value, redaction, OBSERVED_TEXT_LIMIT)),
|
|
117
|
+
selection: (selection, node, redaction) => node.states?.secure === true || selectionMayHoldSecret(node, selection, redaction)
|
|
118
|
+
? undefined
|
|
119
|
+
: redactText(selection, redaction, OBSERVED_TEXT_LIMIT),
|
|
120
|
+
testId: (testId, _node, redaction) => redactText(testId, redaction),
|
|
121
|
+
inputPurpose: keep,
|
|
122
|
+
states: keep,
|
|
123
|
+
level: keep,
|
|
124
|
+
attributes: (attributes, _node, redaction) => Object.fromEntries(Object.entries(attributes).map(([key, value]) => [redactText(key, redaction), redactText(value, redaction)])),
|
|
125
|
+
rect: keep,
|
|
126
|
+
selector: (selector, _node, redaction) => redactText(selector, redaction),
|
|
127
|
+
framePath: (framePath, _node, redaction) => framePath.map((selector) => redactText(selector, redaction)),
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* The node and its subtree with every secret taken out of every field
|
|
131
|
+
* (`NODE_FIELDS`), before any consumer reads it: the model's text, the
|
|
132
|
+
* executor's tree, cache descriptors and anchors, failure evidence.
|
|
133
|
+
*/
|
|
134
|
+
export function redactNode(node, redaction) {
|
|
135
|
+
const redacted = {};
|
|
136
|
+
for (const [field, value] of Object.entries(node)) {
|
|
137
|
+
if (field === 'children' || value === undefined || !Object.hasOwn(NODE_FIELDS, field))
|
|
123
138
|
continue;
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
const redactValue = NODE_FIELDS[field];
|
|
140
|
+
const result = redactValue(value, node, redaction);
|
|
141
|
+
if (result !== undefined)
|
|
142
|
+
redacted[field] = result;
|
|
127
143
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
if (node.children !== undefined)
|
|
145
|
+
redacted['children'] = node.children.map((child) => redactNode(child, redaction));
|
|
146
|
+
return redacted;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* An earlier capture's nodes redacted again through the ledger as it is now,
|
|
150
|
+
* each on its own fields, then linked to its redacted-again children, so the
|
|
151
|
+
* tree keeps its shape (which nodes are leaves) without a subtree redacted
|
|
152
|
+
* once per ancestor. A secret a provider resolved after the capture masks
|
|
153
|
+
* text the capture still shows, so the two captures read alike wherever the
|
|
154
|
+
* screen did not change. Redacting redacted text changes nothing else: a
|
|
155
|
+
* marker is never read again.
|
|
156
|
+
*/
|
|
157
|
+
export function redactNodesAgain(nodes, redaction) {
|
|
158
|
+
const again = new Map();
|
|
159
|
+
for (const [id, { children: _children, ...fields }] of nodes)
|
|
160
|
+
again.set(id, redactNode(fields, redaction));
|
|
161
|
+
for (const [id, { children }] of nodes) {
|
|
162
|
+
const node = again.get(id);
|
|
163
|
+
if (node === undefined || children === undefined)
|
|
164
|
+
continue;
|
|
165
|
+
node.children = children.map((child) => again.get(child.ref.id)).filter((child) => child !== undefined);
|
|
166
|
+
}
|
|
167
|
+
return again;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* One observed string with every secret taken out, in the form it is written
|
|
171
|
+
* and in the one-line form a line, a descriptor, or an anchor shows it
|
|
172
|
+
* (`collapseText`). A field exactly as long as `limit`, and so possibly cut
|
|
173
|
+
* there, passes through `redactCut`: a secret the cut stopped partway through
|
|
174
|
+
* leaves a leading part at the end that no whole value matches. A shorter
|
|
175
|
+
* field is whole, and so is a longer one (a native input's value, which no
|
|
176
|
+
* engine cuts); both pass through `redact`. A field whose collapsed form
|
|
177
|
+
* still holds a secret (the whitespace inside a value widened) is kept
|
|
178
|
+
* collapsed and redacted, so no later collapse brings the value back.
|
|
179
|
+
*/
|
|
180
|
+
function redactText(text, redaction, limit) {
|
|
181
|
+
const pass = text.length === limit ? redaction.redactCut : redaction.redact;
|
|
182
|
+
const redacted = pass(text);
|
|
183
|
+
const collapsed = collapseText(redacted);
|
|
184
|
+
if (collapsed === redacted)
|
|
185
|
+
return redacted;
|
|
186
|
+
const read = pass(collapsed);
|
|
187
|
+
return read === collapsed ? redacted : read;
|
|
134
188
|
}
|
|
135
189
|
/**
|
|
136
190
|
* Whether a node's selection may show part of a secret, which no whole-value
|
|
@@ -142,7 +196,7 @@ function protectSecrets(node, redaction) {
|
|
|
142
196
|
function selectionMayHoldSecret(node, selection, redaction) {
|
|
143
197
|
const fields = [node.value, node.text].filter((text) => text !== undefined);
|
|
144
198
|
const cut = (text) => text.length === OBSERVED_TEXT_LIMIT;
|
|
145
|
-
if (fields.some((text) => (
|
|
199
|
+
if (fields.some((text) => redactText(text, redaction, OBSERVED_TEXT_LIMIT) !== text))
|
|
146
200
|
return true;
|
|
147
201
|
return fields.some(cut) && !fields.some((text) => text.includes(selection));
|
|
148
202
|
}
|
|
@@ -234,33 +288,29 @@ export function interactiveNodeCount(observation) {
|
|
|
234
288
|
return count;
|
|
235
289
|
}
|
|
236
290
|
/**
|
|
237
|
-
* Projects
|
|
238
|
-
*
|
|
239
|
-
* selection at all for a secure node, and a link target bounded as a line
|
|
291
|
+
* Projects a redacted tree onto the executor-facing node shape, the fields
|
|
292
|
+
* the text serialization renders, with a link target bounded as a line
|
|
240
293
|
* bounds it: a target on `appOrigin` keeps that origin and loses no more of
|
|
241
294
|
* its path than the line does. Selectors stay behind — they are relocation
|
|
242
295
|
* material for the trace cache, not something a brain reasons about.
|
|
243
296
|
*/
|
|
244
|
-
export function projectTree(node,
|
|
245
|
-
const secure = node.states?.secure === true;
|
|
297
|
+
export function projectTree(node, appOrigin) {
|
|
246
298
|
const attributes = node.attributes === undefined
|
|
247
299
|
? undefined
|
|
248
|
-
: Object.fromEntries(Object.entries(node.attributes).map(([key, value]) => [key, key === 'href' ? treeHref(
|
|
300
|
+
: Object.fromEntries(Object.entries(node.attributes).map(([key, value]) => [key, key === 'href' ? treeHref(value, appOrigin) : value]));
|
|
249
301
|
return {
|
|
250
302
|
id: node.ref.id,
|
|
251
303
|
...(node.role === undefined ? {} : { role: node.role }),
|
|
252
|
-
...(node.name === undefined ? {} : { name:
|
|
253
|
-
...(node.text === undefined ? {} : { text:
|
|
254
|
-
...(node.value === undefined
|
|
255
|
-
...(node.selection === undefined
|
|
304
|
+
...(node.name === undefined ? {} : { name: node.name }),
|
|
305
|
+
...(node.text === undefined ? {} : { text: node.text }),
|
|
306
|
+
...(node.value === undefined ? {} : { value: node.value }),
|
|
307
|
+
...(node.selection === undefined ? {} : { selection: node.selection }),
|
|
256
308
|
...(node.inputPurpose === undefined ? {} : { inputPurpose: node.inputPurpose }),
|
|
257
309
|
...(node.states === undefined ? {} : { states: node.states }),
|
|
258
310
|
...(attributes === undefined ? {} : { attributes }),
|
|
259
311
|
...(node.rect === undefined ? {} : { rect: node.rect }),
|
|
260
312
|
...(node.framePath === undefined ? {} : { framePath: node.framePath }),
|
|
261
|
-
...(node.children === undefined
|
|
262
|
-
? {}
|
|
263
|
-
: { children: node.children.map((child) => projectTree(child, redact, appOrigin)) }),
|
|
313
|
+
...(node.children === undefined ? {} : { children: node.children.map((child) => projectTree(child, appOrigin)) }),
|
|
264
314
|
};
|
|
265
315
|
}
|
|
266
316
|
/** Depth beyond this renders flat; deep chrome must not buy tokens with spaces. */
|
|
@@ -273,37 +323,37 @@ const MAX_HREF_LENGTH = 256;
|
|
|
273
323
|
* lines, and the model needs their text, not a filler word. A link target on
|
|
274
324
|
* `appOrigin`, the app base URL's, renders as its path.
|
|
275
325
|
*/
|
|
276
|
-
export function formatNode(node, depth,
|
|
326
|
+
export function formatNode(node, depth, appOrigin) {
|
|
277
327
|
const parts = [`#${node.ref.id}`];
|
|
278
328
|
if (node.role !== undefined && node.role !== '')
|
|
279
329
|
parts.push(node.role);
|
|
280
330
|
if (node.name !== undefined && node.name !== '')
|
|
281
|
-
parts.push(JSON.stringify(
|
|
331
|
+
parts.push(JSON.stringify(node.name));
|
|
282
332
|
const text = node.text === undefined ? '' : collapseText(node.text);
|
|
283
333
|
if (text !== '' && text !== node.name)
|
|
284
|
-
parts.push(`text=${JSON.stringify(
|
|
334
|
+
parts.push(`text=${JSON.stringify(text)}`);
|
|
285
335
|
// Disambiguators the model needs when role and name repeat. The engine has
|
|
286
336
|
// already reduced href to origin and path.
|
|
287
337
|
if (node.testId !== undefined && node.testId !== '')
|
|
288
338
|
parts.push(`testid=${JSON.stringify(node.testId)}`);
|
|
289
339
|
const href = node.attributes?.['href'];
|
|
290
340
|
if (href !== undefined && href !== '')
|
|
291
|
-
parts.push(`href=${JSON.stringify(renderHref(
|
|
341
|
+
parts.push(`href=${JSON.stringify(renderHref(href, appOrigin))}`);
|
|
292
342
|
const placeholder = node.attributes?.['placeholder'];
|
|
293
343
|
if (placeholder !== undefined && placeholder !== '' && (node.name ?? '') === '') {
|
|
294
|
-
parts.push(`placeholder=${JSON.stringify(
|
|
344
|
+
parts.push(`placeholder=${JSON.stringify(placeholder)}`);
|
|
295
345
|
}
|
|
296
346
|
if (node.states?.secure === true) {
|
|
297
347
|
parts.push('value=<secure>');
|
|
298
348
|
}
|
|
299
349
|
else if (node.value !== undefined && node.value !== '') {
|
|
300
|
-
parts.push(`value=${JSON.stringify(
|
|
350
|
+
parts.push(`value=${JSON.stringify(node.value)}`);
|
|
301
351
|
}
|
|
302
352
|
// What a Shift+Arrow press selected: without it the model extends a
|
|
303
353
|
// selection blind and cannot tell one word from its neighbour. Rendered
|
|
304
354
|
// verbatim (escaped), since a selected space or newline is a selection too.
|
|
305
|
-
if (node.selection !== undefined && node.selection !== ''
|
|
306
|
-
parts.push(`selection=${JSON.stringify(
|
|
355
|
+
if (node.selection !== undefined && node.selection !== '') {
|
|
356
|
+
parts.push(`selection=${JSON.stringify(node.selection)}`);
|
|
307
357
|
}
|
|
308
358
|
if (node.inputPurpose !== undefined && node.inputPurpose !== 'none') {
|
|
309
359
|
parts.push(`purpose=${node.inputPurpose}`);
|
package/dist/agent/phases.d.ts
CHANGED
|
@@ -67,11 +67,17 @@ export declare function checkStepClock(options: {
|
|
|
67
67
|
* re-read rather than surfaced as a failed call. `guard` is the caller's
|
|
68
68
|
* clock check, so a capture that outlives the deadline reports the step's own
|
|
69
69
|
* timeout rather than whichever transport error the truncated budget produced.
|
|
70
|
+
* `fallback` asks the engine for fallback pixels, which a secret fill
|
|
71
|
+
* (`tainted`, read per capture) withholds; a capture that then times out
|
|
72
|
+
* before the step clock does is the policy denial it is, not an engine
|
|
73
|
+
* timeout, since only those pixels could have answered.
|
|
70
74
|
*/
|
|
71
75
|
export declare function retryingObserve(options: {
|
|
72
|
-
readonly observe: (operation: OperationContext) => Promise<Observation>;
|
|
76
|
+
readonly observe: (operation: OperationContext, pixelFallback: boolean) => Promise<Observation>;
|
|
73
77
|
readonly operation: () => OperationContext;
|
|
74
78
|
readonly guard: (cause?: unknown) => void;
|
|
75
79
|
readonly signal: AbortSignal;
|
|
76
80
|
readonly api: string;
|
|
81
|
+
readonly fallback: boolean;
|
|
82
|
+
readonly tainted: () => boolean;
|
|
77
83
|
}): Promise<Observation>;
|
package/dist/agent/phases.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* these, so event recording, `--debug` buckets, tracing, deadline semantics,
|
|
5
5
|
* and observation race-hardening cannot drift between the two paths.
|
|
6
6
|
*/
|
|
7
|
-
import { asEngineError,
|
|
7
|
+
import { asEngineError, isE2EError } from '../internal/errors.js';
|
|
8
8
|
import { timestamp } from '../internal/ids.js';
|
|
9
9
|
import { POLL_INTERVAL_MS, sleep } from '../internal/time.js';
|
|
10
10
|
import { AgentError, toAgentError } from './error.js';
|
|
@@ -112,14 +112,23 @@ function timedOutAtDeadline(cause, deadline) {
|
|
|
112
112
|
* re-read rather than surfaced as a failed call. `guard` is the caller's
|
|
113
113
|
* clock check, so a capture that outlives the deadline reports the step's own
|
|
114
114
|
* timeout rather than whichever transport error the truncated budget produced.
|
|
115
|
+
* `fallback` asks the engine for fallback pixels, which a secret fill
|
|
116
|
+
* (`tainted`, read per capture) withholds; a capture that then times out
|
|
117
|
+
* before the step clock does is the policy denial it is, not an engine
|
|
118
|
+
* timeout, since only those pixels could have answered.
|
|
115
119
|
*/
|
|
116
120
|
export async function retryingObserve(options) {
|
|
117
121
|
for (;;) {
|
|
122
|
+
const withheld = options.fallback && options.tainted();
|
|
118
123
|
try {
|
|
119
|
-
return await options.observe(options.operation());
|
|
124
|
+
return await options.observe(options.operation(), options.fallback && !withheld);
|
|
120
125
|
}
|
|
121
126
|
catch (cause) {
|
|
122
127
|
options.guard(cause);
|
|
128
|
+
if (withheld && asEngineError(cause)?.code === 'OPERATION_TIMEOUT') {
|
|
129
|
+
throw new AgentError('POLICY_DENIED', `${options.api} could not read the semantic tree, and its screenshot fallback is denied: ` +
|
|
130
|
+
'a secret was filled in this attempt, so no pixels leave the runner until it ends (PIXEL_TAINTED)', { cause });
|
|
131
|
+
}
|
|
123
132
|
// Structural, not instanceof: an engine a config file imported lives in
|
|
124
133
|
// another module registry, and its retryable race would otherwise fail
|
|
125
134
|
// the observation the moment a page navigates under it.
|
|
@@ -133,7 +142,7 @@ export async function retryingObserve(options) {
|
|
|
133
142
|
}
|
|
134
143
|
/** Event code for a failed phase: the runner or engine code, or the error's name. */
|
|
135
144
|
function phaseErrorCode(cause) {
|
|
136
|
-
if (cause
|
|
145
|
+
if (isE2EError(cause))
|
|
137
146
|
return cause.code;
|
|
138
147
|
const engineError = asEngineError(cause);
|
|
139
148
|
if (engineError !== undefined)
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* screenshot are scaled into the observation's CSS pixels first.
|
|
8
8
|
*/
|
|
9
9
|
import type { PointActionName } from '../cache/trace.ts';
|
|
10
|
-
import type {
|
|
10
|
+
import type { ViewportPoint, ViewportSize } from '../engine/surface.ts';
|
|
11
11
|
import type { ExecutorPixels } from './executor.ts';
|
|
12
|
-
import { type AgentObservation } from './observation.ts';
|
|
12
|
+
import { type AgentObservation, type RedactedNode } from './observation.ts';
|
|
13
13
|
/**
|
|
14
14
|
* Scales a point read off a screenshot into the observation's CSS pixels and
|
|
15
15
|
* clamps it to the viewport. `pixels.scale` is image pixels per CSS pixel
|
|
@@ -23,9 +23,9 @@ export declare function imagePointToViewport(point: {
|
|
|
23
23
|
}, pixels: Pick<ExecutorPixels, 'width' | 'height' | 'scale'>, viewport: ViewportSize): ViewportPoint;
|
|
24
24
|
export interface HitTest {
|
|
25
25
|
/** The innermost enabled control whose box contains the point, if any. */
|
|
26
|
-
readonly control:
|
|
26
|
+
readonly control: RedactedNode | undefined;
|
|
27
27
|
/** The innermost listed node of any role whose box contains the point, if any. */
|
|
28
|
-
readonly under:
|
|
28
|
+
readonly under: RedactedNode | undefined;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* Finds what the newest observation lists at a viewport point. Only an
|
|
@@ -54,12 +54,12 @@ export declare const POINT_VERBS: Readonly<Record<PointActionName, {
|
|
|
54
54
|
export declare function describePointAction(input: PointProse & {
|
|
55
55
|
readonly verb: PointActionName;
|
|
56
56
|
readonly point: ViewportPoint;
|
|
57
|
-
readonly control:
|
|
58
|
-
readonly under:
|
|
57
|
+
readonly control: RedactedNode | undefined;
|
|
58
|
+
readonly under: RedactedNode | undefined;
|
|
59
59
|
}): string;
|
|
60
60
|
/** What one hit test found, for the model that named the point. */
|
|
61
61
|
export declare function describePointHit(input: PointProse & {
|
|
62
62
|
readonly point: ViewportPoint;
|
|
63
|
-
readonly control:
|
|
64
|
-
readonly under:
|
|
63
|
+
readonly control: RedactedNode | undefined;
|
|
64
|
+
readonly under: RedactedNode | undefined;
|
|
65
65
|
}): string;
|
package/dist/agent/point-tap.js
CHANGED
|
@@ -41,7 +41,7 @@ export function hitTest(observation, point) {
|
|
|
41
41
|
* The innermost listed node whose box contains the point, among those
|
|
42
42
|
* `accept` admits: deepest in the tree, then the smallest box. Hidden nodes
|
|
43
43
|
* and nodes without a box are skipped. Every box is in the top-level
|
|
44
|
-
* viewport's CSS pixels, nested documents included (`
|
|
44
|
+
* viewport's CSS pixels, nested documents included (`RedactedNode.rect`), so
|
|
45
45
|
* a node inside an iframe is found like any other.
|
|
46
46
|
*/
|
|
47
47
|
function innermostAt(observation, point, accept = () => true) {
|
package/dist/agent/prompts.js
CHANGED
|
@@ -95,6 +95,9 @@ export const JUDGMENT_REQUEST = [
|
|
|
95
95
|
'Decide whether the instruction is true for the observation right now.',
|
|
96
96
|
'Answer "holds" only when the observation shows it is true, and "fails" only when the observation shows it is false.',
|
|
97
97
|
'Answer "inconclusive" when the observation does not contain enough evidence to decide either way: the relevant part is not on screen, is still loading, or cannot be read. Never guess.',
|
|
98
|
+
'When the same value or state is shown in more than one place, such as an order total in a summary and again as the amount on a pay button, the instruction holds only when every place agrees with it; one place that contradicts it is a "fails", even if another agrees.',
|
|
99
|
+
'This applies only when the instruction does not say where to look: an instruction that names the place, such as "the order summary total", is judged on that place alone.',
|
|
100
|
+
'It is about one value shown twice, not about different items: a claim about some item, such as "a todo is marked done", holds when one item matches.',
|
|
98
101
|
'Quotation marks in the instruction delimit the words to look for; the marks themselves, their style (straight or curly), and the punctuation around them are not part of those words, so text that reads the same holds.',
|
|
99
102
|
'Write the explanation first and let the verdict follow from it: the two must agree.',
|
|
100
103
|
'Respond with { "protocolVersion": "agent-judgment-2", "explanation": <short reason grounded in the observation>, "verdict": "holds" | "fails" | "inconclusive" }.',
|
package/dist/agent/replay.d.ts
CHANGED
|
@@ -11,15 +11,20 @@
|
|
|
11
11
|
* timeout, cancellation) rethrow untouched; they are the step's truth, not a
|
|
12
12
|
* divergence.
|
|
13
13
|
*/
|
|
14
|
-
import { type ActionTrace, type DerivedReason, type
|
|
15
|
-
import type {
|
|
14
|
+
import { type ActionTrace, type DerivedReason, type TraceViewport } from '../cache/trace.ts';
|
|
15
|
+
import type { RedactedNode } from './observation.ts';
|
|
16
16
|
import { type ExecutorActions, type ReplayHandOffReason } from './executor.ts';
|
|
17
17
|
import { type SettleMode } from './settle-policy.ts';
|
|
18
18
|
/** The nodes of one observation, keyed by their per-observation ids. */
|
|
19
|
-
export type ObservedNodes = ReadonlyMap<string,
|
|
19
|
+
export type ObservedNodes = ReadonlyMap<string, RedactedNode>;
|
|
20
20
|
/** One capture's location and viewport, with nodes only when semantic evidence is available. */
|
|
21
21
|
export type ObservedScreen = {
|
|
22
22
|
readonly viewport: TraceViewport;
|
|
23
|
+
/**
|
|
24
|
+
* Where the capture was, as the cache compares it (`appLocation`): on the
|
|
25
|
+
* app's own origin its path, query, and fragment; elsewhere the whole
|
|
26
|
+
* location; a device's screen title as it is.
|
|
27
|
+
*/
|
|
23
28
|
readonly path?: string;
|
|
24
29
|
} & ({
|
|
25
30
|
readonly kind: 'semantic';
|
|
@@ -45,7 +50,6 @@ export interface ReplayHost {
|
|
|
45
50
|
readonly actions: ExecutorActions;
|
|
46
51
|
readonly signal: AbortSignal;
|
|
47
52
|
remainingMs(): number;
|
|
48
|
-
readonly redact: (text: string) => string;
|
|
49
53
|
}
|
|
50
54
|
export interface ReplayOutcome {
|
|
51
55
|
/** True when every recorded action executed; the step self-finalizes. */
|
|
@@ -67,20 +71,27 @@ export interface ReplayOptions {
|
|
|
67
71
|
* instead of capturing again: nothing has happened since it was taken.
|
|
68
72
|
*/
|
|
69
73
|
readonly initial?: SemanticScreen;
|
|
74
|
+
/**
|
|
75
|
+
* Asked before a free action (a navigate, typed text, a key) that follows
|
|
76
|
+
* another action; true takes the look a targeted action would have taken.
|
|
77
|
+
* A free action reads no screen, so without it the screen the previous
|
|
78
|
+
* action left goes unseen.
|
|
79
|
+
*/
|
|
80
|
+
readonly looksBeforeFree?: () => boolean;
|
|
70
81
|
}
|
|
71
82
|
/** Replays one trace until it completes or diverges. */
|
|
72
83
|
export declare function replayTrace(host: ReplayHost, trace: ActionTrace, options?: ReplayOptions): Promise<ReplayOutcome>;
|
|
73
84
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
85
|
+
* Waits for a trace's recorded end state on the live screen: `holds` (the
|
|
86
|
+
* recorded delta, `cache/anchors.ts`) must be true of one look, or the
|
|
87
|
+
* replay must not pass on its own. Waits on the same settling backoff
|
|
77
88
|
* relocation uses, because the recording run's final look came seconds of
|
|
78
89
|
* model latency after its last action and a replay's comes right away: a
|
|
79
90
|
* save still in flight is a wait, not a divergence. A surface that cannot be
|
|
80
91
|
* observed at all is a mismatch too — the executor gets the step and judges
|
|
81
92
|
* the live state; only runtime hard stops propagate.
|
|
82
93
|
*/
|
|
83
|
-
export declare function
|
|
94
|
+
export declare function verifyEndState(host: ReplayHost, holds: (screen: SemanticScreen) => boolean, options?: {
|
|
84
95
|
readonly waitMs?: number;
|
|
85
96
|
readonly initial?: SemanticScreen;
|
|
86
97
|
}): Promise<boolean>;
|