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
package/dist/cache/context.js
CHANGED
|
@@ -38,6 +38,18 @@ function flowOf(trace) {
|
|
|
38
38
|
}),
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Writes the step's full provenance into a kept entry recorded before the
|
|
43
|
+
* occurrence fields were, leaving every other entry untouched. Its replay
|
|
44
|
+
* proved which step it belongs to, and `cache.strict` matches only entries
|
|
45
|
+
* that say so exactly (`rekeyed.ts`).
|
|
46
|
+
*/
|
|
47
|
+
async function completeProvenance(store, keyHash, recordedFor) {
|
|
48
|
+
const existing = await store.read(keyHash);
|
|
49
|
+
if (existing.status !== 'hit' || existing.entry.payload.recordedFor?.callIndex !== undefined)
|
|
50
|
+
return;
|
|
51
|
+
await store.write(keyHash, { ...existing.entry.payload, recordedFor });
|
|
52
|
+
}
|
|
41
53
|
/**
|
|
42
54
|
* Settles the attempt's staged trace writes. A staged trace is confirmed only
|
|
43
55
|
* when a verification step — a deterministic assertion or an agent judgment
|
|
@@ -54,7 +66,7 @@ function flowOf(trace) {
|
|
|
54
66
|
* for an interrupted attempt: interruption implicates nothing, so it writes
|
|
55
67
|
* nothing and evicts nothing. An entry a step replayed whole is staged too,
|
|
56
68
|
* so the same rule evicts it when nothing confirmed it; when something did,
|
|
57
|
-
* it is left exactly as it was found.
|
|
69
|
+
* it is left exactly as it was found, but for provenance it lacked.
|
|
58
70
|
*/
|
|
59
71
|
export async function flushStagedTraces(context, settlement) {
|
|
60
72
|
const staged = context.staged.splice(0);
|
|
@@ -68,7 +80,11 @@ export async function flushStagedTraces(context, settlement) {
|
|
|
68
80
|
await context.store.delete?.(entry.keyHash);
|
|
69
81
|
continue;
|
|
70
82
|
}
|
|
71
|
-
if (entry.kind === 'keep'
|
|
83
|
+
if (entry.kind === 'keep') {
|
|
84
|
+
await completeProvenance(context.store, entry.keyHash, entry.recordedFor);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (await holdsSameFlow(context.store, entry.keyHash, entry.trace))
|
|
72
88
|
continue;
|
|
73
89
|
await context.store.write(entry.keyHash, entry.trace);
|
|
74
90
|
}
|
|
@@ -95,23 +111,49 @@ export function createAgentCacheContext(options) {
|
|
|
95
111
|
writable: mode === 'read-write',
|
|
96
112
|
});
|
|
97
113
|
const project = projectIdentity(options.projectId);
|
|
98
|
-
|
|
114
|
+
// One occurrence count per agent: the agent is part of the key, so another
|
|
115
|
+
// agent's call of the same instruction must not renumber this one's.
|
|
116
|
+
const indexers = new Map();
|
|
117
|
+
const nextCallIndex = (agent, signature) => {
|
|
118
|
+
// Folded like the key folds it (`buildTraceCacheKey`): no context is the empty one.
|
|
119
|
+
const agentKey = canonicalJson([agent.name, agent.context ?? '']);
|
|
120
|
+
const indexer = indexers.get(agentKey) ?? createCallIndexer();
|
|
121
|
+
indexers.set(agentKey, indexer);
|
|
122
|
+
return indexer(signature);
|
|
123
|
+
};
|
|
99
124
|
return {
|
|
100
125
|
mode,
|
|
101
126
|
store,
|
|
102
|
-
identity: { testId: options.testId, targetId: options.target.targetId },
|
|
103
127
|
replayEligible: options.attemptIndex === 0,
|
|
104
|
-
strict: options.cache.strict === false
|
|
105
|
-
|
|
128
|
+
strict: options.cache.strict === false
|
|
129
|
+
? false
|
|
130
|
+
: {
|
|
131
|
+
advice: staleAdvice(options.cache, options.cache.strict, options.projectRoot),
|
|
132
|
+
...(options.recordings === undefined ? {} : { recordings: options.recordings }),
|
|
133
|
+
},
|
|
134
|
+
claimKey: (kind, instruction, params, agent) => {
|
|
106
135
|
const signature = traceCallSignature(kind, instruction, params);
|
|
107
|
-
|
|
136
|
+
const callIndex = nextCallIndex(agent, signature);
|
|
137
|
+
const keyHash = traceCacheKeyHash(buildTraceCacheKey({
|
|
108
138
|
project,
|
|
109
139
|
testId: options.testId,
|
|
110
140
|
target: options.target,
|
|
111
141
|
signature,
|
|
112
|
-
callIndex
|
|
142
|
+
callIndex,
|
|
143
|
+
agent,
|
|
113
144
|
policyVersion: REPLAY_POLICY_VERSION,
|
|
114
145
|
}));
|
|
146
|
+
return {
|
|
147
|
+
keyHash,
|
|
148
|
+
step: {
|
|
149
|
+
testId: options.testId,
|
|
150
|
+
targetId: options.target.targetId,
|
|
151
|
+
instructionDigest: signature.instructionDigest,
|
|
152
|
+
paramsDigest: signature.paramsDigest,
|
|
153
|
+
callIndex,
|
|
154
|
+
agent: agent.name,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
115
157
|
},
|
|
116
158
|
staged: [],
|
|
117
159
|
};
|
package/dist/cache/decide.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type TraceReplayDecision = {
|
|
|
31
31
|
* not open with a navigate carries a start precondition: the app must be on
|
|
32
32
|
* the screen where the recording began, or the recorded actions would run
|
|
33
33
|
* against a different screen than they were proven on. The screen is its
|
|
34
|
-
* route (`
|
|
35
|
-
*
|
|
34
|
+
* route (`cache/route.ts`), not its URL: the same origin, path, and query,
|
|
35
|
+
* whatever the ids in them.
|
|
36
36
|
*/
|
|
37
37
|
export declare function decideTraceReplay(entry: TraceEntry, currentPath: string | undefined): TraceReplayDecision;
|
package/dist/cache/decide.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* decision takes an already-trusted entry and answers only the questions the
|
|
8
8
|
* entry alone cannot: is it whole, and is the app where the recording began?
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { sameRoute } from './route.js';
|
|
11
11
|
/**
|
|
12
12
|
* Whether a trace establishes its own starting point by navigating first.
|
|
13
13
|
* Such a trace needs no start-path precondition — and a trace with neither
|
|
@@ -21,8 +21,8 @@ export function opensWithNavigate(trace) {
|
|
|
21
21
|
* not open with a navigate carries a start precondition: the app must be on
|
|
22
22
|
* the screen where the recording began, or the recorded actions would run
|
|
23
23
|
* against a different screen than they were proven on. The screen is its
|
|
24
|
-
* route (`
|
|
25
|
-
*
|
|
24
|
+
* route (`cache/route.ts`), not its URL: the same origin, path, and query,
|
|
25
|
+
* whatever the ids in them.
|
|
26
26
|
*/
|
|
27
27
|
export function decideTraceReplay(entry, currentPath) {
|
|
28
28
|
const trace = entry.payload;
|
|
@@ -32,7 +32,5 @@ export function decideTraceReplay(entry, currentPath) {
|
|
|
32
32
|
return { action: 'replay' };
|
|
33
33
|
if (trace.startPath === undefined || currentPath === undefined)
|
|
34
34
|
return { action: 'miss', reason: 'wrong-context' };
|
|
35
|
-
return
|
|
36
|
-
? { action: 'replay' }
|
|
37
|
-
: { action: 'miss', reason: 'wrong-context' };
|
|
35
|
+
return sameRoute(trace.startPath, currentPath) ? { action: 'replay' } : { action: 'miss', reason: 'wrong-context' };
|
|
38
36
|
}
|
package/dist/cache/identity.d.ts
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* contributes only its stable name and purpose, exactly as the executor sees
|
|
8
8
|
* it in the projected params.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The agent a step runs with is part of the key: its configured name, since
|
|
11
|
+
* two agents of one test (`agent: ['buyer', 'admin']`) act as different
|
|
12
|
+
* people, and a digest of the context it reads, since a test's
|
|
13
|
+
* `agentContext` decides what the step does. The context is digested after
|
|
14
|
+
* redaction, so a registered secret in it contributes its name only. The
|
|
15
|
+
* executor that produced a trace stays provenance on the entry: the agent's
|
|
16
|
+
* name already keeps two configured agents apart.
|
|
14
17
|
*/
|
|
15
18
|
import type { JsonValue } from '../types.ts';
|
|
16
19
|
import { TRACE_SCHEMA_VERSION } from './trace.ts';
|
|
@@ -37,8 +40,18 @@ export interface TraceCacheKey {
|
|
|
37
40
|
readonly instructionDigest: string;
|
|
38
41
|
readonly paramsDigest: string;
|
|
39
42
|
readonly appIdentity: string;
|
|
43
|
+
/** Name of the configured agent the step ran with. */
|
|
44
|
+
readonly agent: string;
|
|
45
|
+
/** SHA-256 of the agent's redacted context (its `context` then the test's `agentContext`), or of the empty string. */
|
|
46
|
+
readonly agentContextDigest: string;
|
|
40
47
|
readonly policyVersion: string;
|
|
41
48
|
}
|
|
49
|
+
/** The agent a step runs with, as the key sees it. */
|
|
50
|
+
export interface CacheAgentIdentity {
|
|
51
|
+
readonly name: string;
|
|
52
|
+
/** The context the agent reads, already redacted. */
|
|
53
|
+
readonly context: string | undefined;
|
|
54
|
+
}
|
|
42
55
|
/** Identity of the target/engine/app a trace was recorded against. */
|
|
43
56
|
export interface CacheTargetIdentity {
|
|
44
57
|
readonly targetId: string;
|
|
@@ -106,6 +119,7 @@ export declare function buildTraceCacheKey(parts: {
|
|
|
106
119
|
readonly target: CacheTargetIdentity;
|
|
107
120
|
readonly signature: TraceCallSignature;
|
|
108
121
|
readonly callIndex: number;
|
|
122
|
+
readonly agent: CacheAgentIdentity;
|
|
109
123
|
readonly policyVersion: string;
|
|
110
124
|
}): TraceCacheKey;
|
|
111
125
|
/** SHA-256/JCS of one key, used as both the entry digest and its file name. */
|
package/dist/cache/identity.js
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* contributes only its stable name and purpose, exactly as the executor sees
|
|
8
8
|
* it in the projected params.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The agent a step runs with is part of the key: its configured name, since
|
|
11
|
+
* two agents of one test (`agent: ['buyer', 'admin']`) act as different
|
|
12
|
+
* people, and a digest of the context it reads, since a test's
|
|
13
|
+
* `agentContext` decides what the step does. The context is digested after
|
|
14
|
+
* redaction, so a registered secret in it contributes its name only. The
|
|
15
|
+
* executor that produced a trace stays provenance on the entry: the agent's
|
|
16
|
+
* name already keeps two configured agents apart.
|
|
14
17
|
*/
|
|
15
18
|
import { canonicalDigest, sha256Hex } from '../internal/ids.js';
|
|
16
19
|
import { TRACE_SCHEMA_VERSION } from './trace.js';
|
|
@@ -99,6 +102,8 @@ export function buildTraceCacheKey(parts) {
|
|
|
99
102
|
instructionDigest: parts.signature.instructionDigest,
|
|
100
103
|
paramsDigest: parts.signature.paramsDigest,
|
|
101
104
|
appIdentity: parts.target.appIdentity,
|
|
105
|
+
agent: parts.agent.name,
|
|
106
|
+
agentContextDigest: sha256Hex(parts.agent.context ?? ''),
|
|
102
107
|
policyVersion: parts.policyVersion,
|
|
103
108
|
};
|
|
104
109
|
}
|
package/dist/cache/recorder.d.ts
CHANGED
|
@@ -20,12 +20,15 @@ import { type ActionTrace, type DerivedReason, type TraceProvenance, type TraceT
|
|
|
20
20
|
export interface TraceRecorderOptions {
|
|
21
21
|
/** The run's secret redactor; applied to every recorded string. */
|
|
22
22
|
readonly redact: (text: string) => string;
|
|
23
|
+
/** `redact` for a field cut at its observed limit (`SecretLedger.redactCut`). */
|
|
24
|
+
readonly redactCut: (text: string) => string;
|
|
23
25
|
readonly maxActions?: number;
|
|
24
26
|
}
|
|
25
27
|
export declare class TraceRecorder {
|
|
26
28
|
private readonly actions;
|
|
27
29
|
private truncated;
|
|
28
30
|
private readonly redact;
|
|
31
|
+
private readonly redactCut;
|
|
29
32
|
private readonly maxActions;
|
|
30
33
|
constructor(options: TraceRecorderOptions);
|
|
31
34
|
private lastActionAt;
|
|
@@ -61,8 +64,10 @@ export declare class TraceRecorder {
|
|
|
61
64
|
readonly summary: string;
|
|
62
65
|
readonly startPath?: string;
|
|
63
66
|
readonly endPath?: string;
|
|
64
|
-
/** Already projected and capped by `
|
|
67
|
+
/** Already projected and capped by `describeDelta`; recorded as given. */
|
|
65
68
|
readonly endAnchors?: readonly TraceTargetDescriptor[];
|
|
69
|
+
/** The other side of the same delta: what was on screen at the start and gone at the end. */
|
|
70
|
+
readonly goneAnchors?: readonly TraceTargetDescriptor[];
|
|
66
71
|
/** How long the recorded run took to reach its end state, plus margin. */
|
|
67
72
|
readonly endWaitMs?: number;
|
|
68
73
|
}): ActionTrace | undefined;
|
|
@@ -84,3 +89,12 @@ export declare class TraceRecorder {
|
|
|
84
89
|
private verbatim;
|
|
85
90
|
private push;
|
|
86
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Provenance as an entry stores it. Harness identity rather than screen
|
|
94
|
+
* content, so the redactor here is belt and braces: a title or agent name
|
|
95
|
+
* that happened to carry a registered secret is masked like any other
|
|
96
|
+
* recorded string, and provenance is not replay input, so masking it poisons
|
|
97
|
+
* nothing. A step compares its own provenance in this form to the entries
|
|
98
|
+
* the store holds.
|
|
99
|
+
*/
|
|
100
|
+
export declare function recordedProvenance(provenance: TraceProvenance, redact: (text: string) => string): TraceProvenance;
|
package/dist/cache/recorder.js
CHANGED
|
@@ -24,9 +24,11 @@ export class TraceRecorder {
|
|
|
24
24
|
actions = [];
|
|
25
25
|
truncated = false;
|
|
26
26
|
redact;
|
|
27
|
+
redactCut;
|
|
27
28
|
maxActions;
|
|
28
29
|
constructor(options) {
|
|
29
30
|
this.redact = options.redact;
|
|
31
|
+
this.redactCut = options.redactCut;
|
|
30
32
|
this.maxActions = Math.min(options.maxActions ?? MAX_TRACE_ACTIONS, MAX_TRACE_ACTIONS);
|
|
31
33
|
}
|
|
32
34
|
lastActionAt;
|
|
@@ -40,7 +42,7 @@ export class TraceRecorder {
|
|
|
40
42
|
}
|
|
41
43
|
/** Records one committed grammar action. */
|
|
42
44
|
record(action) {
|
|
43
|
-
this.push(this.toRecorded(action, describeAction(action, this.redact)));
|
|
45
|
+
this.push(this.toRecorded(action, describeAction(action, { redact: this.redact, redactCut: this.redactCut })));
|
|
44
46
|
this.lastActionAt = Date.now();
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
@@ -69,9 +71,10 @@ export class TraceRecorder {
|
|
|
69
71
|
if (this.actions.length === 0)
|
|
70
72
|
return undefined;
|
|
71
73
|
const summary = bound(this.redact(conclusion.summary), MAX_TRACE_SUMMARY_CHARS);
|
|
72
|
-
//
|
|
73
|
-
// the redactor alters carried a secret (a token in a query string)
|
|
74
|
-
//
|
|
74
|
+
// Locations are replay preconditions, so they follow the input rule: a
|
|
75
|
+
// path the redactor alters carried a secret (a token in a query string),
|
|
76
|
+
// and one too long to keep whole cannot be compared, so either marks the
|
|
77
|
+
// trace non-replayable rather than storing it bent.
|
|
75
78
|
const startPath = this.anchorPath(conclusion.startPath);
|
|
76
79
|
const endPath = this.anchorPath(conclusion.endPath);
|
|
77
80
|
return {
|
|
@@ -80,21 +83,16 @@ export class TraceRecorder {
|
|
|
80
83
|
name: conclusion.executor.name,
|
|
81
84
|
...(conclusion.executor.version === undefined ? {} : { version: conclusion.executor.version }),
|
|
82
85
|
},
|
|
83
|
-
recordedFor:
|
|
84
|
-
// Harness identity rather than screen content, so the redactor here is
|
|
85
|
-
// belt and braces: a title that happened to carry a registered secret
|
|
86
|
-
// is masked like any other recorded string, and provenance is not
|
|
87
|
-
// replay input, so masking it poisons nothing.
|
|
88
|
-
testId: bound(this.redact(conclusion.recordedFor.testId), MAX_TRACE_DESCRIPTOR_CHARS),
|
|
89
|
-
targetId: bound(this.redact(conclusion.recordedFor.targetId), MAX_TRACE_DESCRIPTOR_CHARS),
|
|
90
|
-
instructionDigest: conclusion.recordedFor.instructionDigest,
|
|
91
|
-
},
|
|
86
|
+
recordedFor: recordedProvenance(conclusion.recordedFor, this.redact),
|
|
92
87
|
summary: summary.trim() === '' ? 'step passed' : summary,
|
|
93
88
|
...(startPath === undefined ? {} : { startPath }),
|
|
94
89
|
...(endPath === undefined ? {} : { endPath }),
|
|
95
90
|
...(conclusion.endAnchors === undefined || conclusion.endAnchors.length === 0
|
|
96
91
|
? {}
|
|
97
92
|
: { endAnchors: conclusion.endAnchors }),
|
|
93
|
+
...(conclusion.goneAnchors === undefined || conclusion.goneAnchors.length === 0
|
|
94
|
+
? {}
|
|
95
|
+
: { goneAnchors: conclusion.goneAnchors }),
|
|
98
96
|
...(conclusion.endWaitMs === undefined
|
|
99
97
|
? {}
|
|
100
98
|
: { endWaitMs: Math.min(MAX_TRACE_END_WAIT_MS, Math.max(0, Math.round(conclusion.endWaitMs))) }),
|
|
@@ -102,12 +100,7 @@ export class TraceRecorder {
|
|
|
102
100
|
};
|
|
103
101
|
}
|
|
104
102
|
anchorPath(value) {
|
|
105
|
-
|
|
106
|
-
return undefined;
|
|
107
|
-
const redacted = this.redact(value);
|
|
108
|
-
if (redacted !== value)
|
|
109
|
-
this.truncated = true;
|
|
110
|
-
return bound(redacted, MAX_TRACE_DESCRIPTOR_CHARS);
|
|
103
|
+
return value === undefined || value === '' ? undefined : this.verbatim(value);
|
|
111
104
|
}
|
|
112
105
|
/**
|
|
113
106
|
* Builds the stored variant for one action. The descriptor and the prose
|
|
@@ -117,13 +110,15 @@ export class TraceRecorder {
|
|
|
117
110
|
* kept whole.
|
|
118
111
|
*/
|
|
119
112
|
toRecorded(action, { target, summary, destination }) {
|
|
120
|
-
// A targeted commit whose node yields no
|
|
121
|
-
// re-
|
|
113
|
+
// A targeted commit whose node yields no descriptor a replay could
|
|
114
|
+
// re-find it by (none at all, or an unnamed twin with no named row to
|
|
115
|
+
// tell it apart) cannot replay; the trace stays honest by poisoning
|
|
116
|
+
// instead of guessing.
|
|
122
117
|
const require = (descriptor) => {
|
|
123
|
-
if (descriptor !== undefined)
|
|
118
|
+
if (descriptor !== undefined && isRelocatableDescriptor(descriptor))
|
|
124
119
|
return descriptor;
|
|
125
120
|
this.truncated = true;
|
|
126
|
-
return { role: 'unknown' };
|
|
121
|
+
return descriptor ?? { role: 'unknown' };
|
|
127
122
|
};
|
|
128
123
|
const requireTarget = () => require(target);
|
|
129
124
|
if (isNodeAction(action))
|
|
@@ -240,3 +235,21 @@ function sameTarget(a, b) {
|
|
|
240
235
|
a.position?.index === b.position?.index &&
|
|
241
236
|
a.position?.of === b.position?.of);
|
|
242
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Provenance as an entry stores it. Harness identity rather than screen
|
|
240
|
+
* content, so the redactor here is belt and braces: a title or agent name
|
|
241
|
+
* that happened to carry a registered secret is masked like any other
|
|
242
|
+
* recorded string, and provenance is not replay input, so masking it poisons
|
|
243
|
+
* nothing. A step compares its own provenance in this form to the entries
|
|
244
|
+
* the store holds.
|
|
245
|
+
*/
|
|
246
|
+
export function recordedProvenance(provenance, redact) {
|
|
247
|
+
return {
|
|
248
|
+
testId: bound(redact(provenance.testId), MAX_TRACE_DESCRIPTOR_CHARS),
|
|
249
|
+
targetId: bound(redact(provenance.targetId), MAX_TRACE_DESCRIPTOR_CHARS),
|
|
250
|
+
instructionDigest: provenance.instructionDigest,
|
|
251
|
+
...(provenance.paramsDigest === undefined ? {} : { paramsDigest: provenance.paramsDigest }),
|
|
252
|
+
...(provenance.callIndex === undefined ? {} : { callIndex: provenance.callIndex }),
|
|
253
|
+
...(provenance.agent === undefined ? {} : { agent: bound(redact(provenance.agent), MAX_TRACE_DESCRIPTOR_CHARS) }),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recordings stored under a key the runner no longer derives.
|
|
3
|
+
*
|
|
4
|
+
* A key names everything that can change replay (`identity.ts`), so a new
|
|
5
|
+
* replay policy, an engine minor, or an agent's context changing turns every
|
|
6
|
+
* entry recorded before into a lookup miss, the same `no-entry` as a step
|
|
7
|
+
* that was never recorded. `cache.strict` lets a never-recorded step run
|
|
8
|
+
* live, so without this it would let a whole committed cache go quietly
|
|
9
|
+
* unused. The entries say which step they were recorded for
|
|
10
|
+
* (`TraceProvenance`), and a step whose own key misses while the store holds
|
|
11
|
+
* a recording made for exactly that step is stale, not new.
|
|
12
|
+
*
|
|
13
|
+
* Only the file store can be listed. The listing is read once per worker
|
|
14
|
+
* and target, on the first strict miss that asks, and kept: every entry
|
|
15
|
+
* written since has a key the run derives, so it is a hit rather than a
|
|
16
|
+
* candidate here.
|
|
17
|
+
*/
|
|
18
|
+
import type { ResolvedCacheConfig } from '../config/resolve.ts';
|
|
19
|
+
import { FileCacheStore } from './store.ts';
|
|
20
|
+
import type { TraceProvenance } from './trace.ts';
|
|
21
|
+
export declare class StoredRecordings {
|
|
22
|
+
private readonly store;
|
|
23
|
+
/** Key hashes by `stepKey` of the step each entry was recorded for. */
|
|
24
|
+
private listing;
|
|
25
|
+
constructor(store: FileCacheStore);
|
|
26
|
+
/**
|
|
27
|
+
* The key hash of an entry recorded for `step` under a key other than
|
|
28
|
+
* `keyHash`, or undefined when there is none or the store cannot be
|
|
29
|
+
* listed. `step` is in the form an entry stores it (`recordedProvenance`).
|
|
30
|
+
* Only an entry that records the whole step counts: one written before the
|
|
31
|
+
* params, occurrence, and agent were recorded could belong to another call
|
|
32
|
+
* of the same instruction, and a read-write run that replays it completes
|
|
33
|
+
* it (`flushStagedTraces`). A truncated entry never replays under any key,
|
|
34
|
+
* so it is never the reason a step is stale.
|
|
35
|
+
*/
|
|
36
|
+
underAnotherKey(keyHash: string, step: TraceProvenance): Promise<string | undefined>;
|
|
37
|
+
/** Every replayable entry that records its whole step, by `stepKey`. */
|
|
38
|
+
private list;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The listing `cache.strict` checks a run's misses against, for the file
|
|
42
|
+
* store only. Undefined when strict is off, the cache is off, or a custom
|
|
43
|
+
* `cache.store` replaced the file store, which `CacheStore` gives no way to
|
|
44
|
+
* list.
|
|
45
|
+
*/
|
|
46
|
+
export declare function storedRecordingsFor(cache: ResolvedCacheConfig): StoredRecordings | undefined;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recordings stored under a key the runner no longer derives.
|
|
3
|
+
*
|
|
4
|
+
* A key names everything that can change replay (`identity.ts`), so a new
|
|
5
|
+
* replay policy, an engine minor, or an agent's context changing turns every
|
|
6
|
+
* entry recorded before into a lookup miss, the same `no-entry` as a step
|
|
7
|
+
* that was never recorded. `cache.strict` lets a never-recorded step run
|
|
8
|
+
* live, so without this it would let a whole committed cache go quietly
|
|
9
|
+
* unused. The entries say which step they were recorded for
|
|
10
|
+
* (`TraceProvenance`), and a step whose own key misses while the store holds
|
|
11
|
+
* a recording made for exactly that step is stale, not new.
|
|
12
|
+
*
|
|
13
|
+
* Only the file store can be listed. The listing is read once per worker
|
|
14
|
+
* and target, on the first strict miss that asks, and kept: every entry
|
|
15
|
+
* written since has a key the run derives, so it is a hit rather than a
|
|
16
|
+
* candidate here.
|
|
17
|
+
*/
|
|
18
|
+
import { FileCacheStore, MAX_CACHE_WIRE_BYTES } from './store.js';
|
|
19
|
+
export class StoredRecordings {
|
|
20
|
+
store;
|
|
21
|
+
/** Key hashes by `stepKey` of the step each entry was recorded for. */
|
|
22
|
+
listing;
|
|
23
|
+
constructor(store) {
|
|
24
|
+
this.store = store;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The key hash of an entry recorded for `step` under a key other than
|
|
28
|
+
* `keyHash`, or undefined when there is none or the store cannot be
|
|
29
|
+
* listed. `step` is in the form an entry stores it (`recordedProvenance`).
|
|
30
|
+
* Only an entry that records the whole step counts: one written before the
|
|
31
|
+
* params, occurrence, and agent were recorded could belong to another call
|
|
32
|
+
* of the same instruction, and a read-write run that replays it completes
|
|
33
|
+
* it (`flushStagedTraces`). A truncated entry never replays under any key,
|
|
34
|
+
* so it is never the reason a step is stale.
|
|
35
|
+
*/
|
|
36
|
+
async underAnotherKey(keyHash, step) {
|
|
37
|
+
const key = stepKey(step);
|
|
38
|
+
if (key === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
let listing;
|
|
41
|
+
try {
|
|
42
|
+
this.listing ??= this.list();
|
|
43
|
+
listing = await this.listing;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
for (const candidate of listing.get(key) ?? []) {
|
|
49
|
+
if (candidate === keyHash)
|
|
50
|
+
continue;
|
|
51
|
+
// Listed once: an entry evicted since is no evidence.
|
|
52
|
+
const read = await this.store.read(candidate).catch(() => undefined);
|
|
53
|
+
if (read?.status === 'hit')
|
|
54
|
+
return candidate;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Every replayable entry that records its whole step, by `stepKey`. */
|
|
59
|
+
async list() {
|
|
60
|
+
const steps = new Map();
|
|
61
|
+
for (const keyHash of await this.store.keyHashes()) {
|
|
62
|
+
const read = await this.store.read(keyHash);
|
|
63
|
+
if (read.status !== 'hit' || read.entry.payload.truncated === true)
|
|
64
|
+
continue;
|
|
65
|
+
const key = read.entry.payload.recordedFor === undefined ? undefined : stepKey(read.entry.payload.recordedFor);
|
|
66
|
+
if (key !== undefined)
|
|
67
|
+
steps.set(key, [...(steps.get(key) ?? []), keyHash]);
|
|
68
|
+
}
|
|
69
|
+
return steps;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The listing `cache.strict` checks a run's misses against, for the file
|
|
74
|
+
* store only. Undefined when strict is off, the cache is off, or a custom
|
|
75
|
+
* `cache.store` replaced the file store, which `CacheStore` gives no way to
|
|
76
|
+
* list.
|
|
77
|
+
*/
|
|
78
|
+
export function storedRecordingsFor(cache) {
|
|
79
|
+
if (cache.strict === false || cache.mode === 'off' || cache.store !== undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
return new StoredRecordings(new FileCacheStore({ directory: cache.dir, maxBytes: MAX_CACHE_WIRE_BYTES, writable: false }));
|
|
82
|
+
}
|
|
83
|
+
/** The whole step as one map key, or undefined when the provenance does not record all of it. */
|
|
84
|
+
function stepKey(step) {
|
|
85
|
+
const { testId, targetId, instructionDigest, paramsDigest, callIndex, agent } = step;
|
|
86
|
+
if (paramsDigest === undefined || callIndex === undefined || agent === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
return JSON.stringify([testId, targetId, instructionDigest, paramsDigest, callIndex, agent]);
|
|
89
|
+
}
|
package/dist/cache/relocate.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* means — is exported so end anchors (`anchors.ts`) are checked by the same
|
|
15
15
|
* rules and can never drift from relocation.
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
17
|
+
import type { RedactedNode } from '../agent/observation.ts';
|
|
18
18
|
import type { TracePosition, TraceTargetDescriptor } from './trace.ts';
|
|
19
19
|
/**
|
|
20
20
|
* Version of the replay/relocation policy, part of every cache key. Bumping
|
|
@@ -22,7 +22,7 @@ import type { TracePosition, TraceTargetDescriptor } from './trace.ts';
|
|
|
22
22
|
* change, because an entry recorded under different rules could relocate to a
|
|
23
23
|
* different node.
|
|
24
24
|
*/
|
|
25
|
-
export declare const REPLAY_POLICY_VERSION = "conservative/
|
|
25
|
+
export declare const REPLAY_POLICY_VERSION = "conservative/6";
|
|
26
26
|
/**
|
|
27
27
|
* The share of the viewport a scrolled node must have covered when it was
|
|
28
28
|
* addressed (`ScrollAction.spans`) to scroll as the viewport does once it
|
|
@@ -45,14 +45,16 @@ export type RelocationResult = {
|
|
|
45
45
|
readonly candidates: readonly string[];
|
|
46
46
|
};
|
|
47
47
|
export type DescriptorField = keyof TraceTargetDescriptor;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Whether a descriptor can identify a node at all. Role or selector alone
|
|
50
|
+
* cannot: a wrong match acts on the wrong control, so such a descriptor is
|
|
51
|
+
* not relocatable and, as an anchor, would prove nothing. An anonymous one
|
|
52
|
+
* is relocatable by its recorded place only when that place means something
|
|
53
|
+
* on the next screen: it was the one unnamed control of its role, or it sits
|
|
54
|
+
* in a named container (`within`) that tells it from its twins. A place
|
|
55
|
+
* counted among twins with no container is an order, and rows that reorder
|
|
56
|
+
* would hand the action to the wrong one, so such a target hands off.
|
|
57
|
+
*/
|
|
56
58
|
export declare function isRelocatableDescriptor(descriptor: TraceTargetDescriptor): boolean;
|
|
57
59
|
/**
|
|
58
60
|
* The tiers a recorded descriptor is matched in, strictest first:
|
|
@@ -70,8 +72,6 @@ export declare function isRelocatableDescriptor(descriptor: TraceTargetDescripto
|
|
|
70
72
|
export declare function descriptorTiers(descriptor: TraceTargetDescriptor): readonly TraceTargetDescriptor[];
|
|
71
73
|
/** Every listed field the recording captured must be present and equal on the candidate. */
|
|
72
74
|
export declare function fieldsEqual(recorded: TraceTargetDescriptor, candidate: TraceTargetDescriptor, fields: readonly DescriptorField[]): boolean;
|
|
73
|
-
/** Projects every node of an observation the way the recorder described its targets. */
|
|
74
|
-
export declare function describeNodes(nodes: ReadonlyMap<string, SemanticNode>, options: DescriptorMatchOptions): readonly DescribedNode[];
|
|
75
75
|
/**
|
|
76
76
|
* Relocates one descriptor against the nodes of a fresh observation, tier by
|
|
77
77
|
* tier (`descriptorTiers`), each exactly-one-or-diverge. Ambiguity at any
|
|
@@ -81,11 +81,11 @@ export declare function describeNodes(nodes: ReadonlyMap<string, SemanticNode>,
|
|
|
81
81
|
* same twins and noted which one it acted on, so the same count of twins
|
|
82
82
|
* resolves to the same one; any other count diverges as before.
|
|
83
83
|
*/
|
|
84
|
-
export declare function relocateDescriptor(descriptor: TraceTargetDescriptor, nodes: ReadonlyMap<string,
|
|
84
|
+
export declare function relocateDescriptor(descriptor: TraceTargetDescriptor, nodes: ReadonlyMap<string, RedactedNode>): RelocationResult;
|
|
85
85
|
/**
|
|
86
86
|
* Where `node` stands among the controls its own description matches on the
|
|
87
87
|
* screen it was acted on, or undefined when the description already names it
|
|
88
88
|
* alone. Recorded with the target so a replay can tell one "Set up" button per
|
|
89
89
|
* card apart without an app change; a distinct label makes it unnecessary.
|
|
90
90
|
*/
|
|
91
|
-
export declare function describePosition(node:
|
|
91
|
+
export declare function describePosition(node: RedactedNode, within: string | undefined, nodes: ReadonlyMap<string, RedactedNode>): TracePosition | undefined;
|