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/README.md
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
<a href="https://tester.army/discord"><img alt="Join the community on Discord" src="https://img.shields.io/badge/Join%20the%20community-5865F2.svg?style=for-the-badge&logo=discord&logoColor=white&labelColor=000000" /></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://www.star-history.com/tester-army/e2e"><picture><source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/badge?repo=tester-army/e2e&type=trending&theme=dark" /><img alt="GitHub Trending Repository of the Day" src="https://api.star-history.com/badge?repo=tester-army/e2e&type=trending" /></picture></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
10
14
|
# e2e
|
|
11
15
|
|
|
12
16
|
[e2e](https://tester.army/e2e?utm_source=e2e&utm_medium=github&utm_campaign=readme_intro) is an end-to-end testing framework for web and mobile apps. Describe a goal in natural language and an agent drives the app to reach it. Check the result with locators and assertions in the same test.
|
|
@@ -40,6 +44,11 @@ npx e2e init
|
|
|
40
44
|
config and an example test. The
|
|
41
45
|
[quickstart](https://e2e.tester.army/docs/quickstart) covers the rest.
|
|
42
46
|
|
|
47
|
+
To see a finished setup in your stack, open
|
|
48
|
+
[`examples/`](https://github.com/tester-army/e2e/tree/main/examples):
|
|
49
|
+
Vite, Next.js, Expo, and SwiftUI, each a standalone project with a passing
|
|
50
|
+
suite.
|
|
51
|
+
|
|
43
52
|
## Packages
|
|
44
53
|
|
|
45
54
|
| Package | What it does |
|
|
@@ -50,6 +59,7 @@ config and an example test. The
|
|
|
50
59
|
| [`@e2e-dev/github`](https://www.npmjs.com/package/@e2e-dev/github) | Reporter that posts results as a pull request comment. |
|
|
51
60
|
| [`@e2e-dev/kernel`](https://www.npmjs.com/package/@e2e-dev/kernel) | Kernel hosted browsers for the web engine. |
|
|
52
61
|
| [`@e2e-dev/eas`](https://www.npmjs.com/package/@e2e-dev/eas) | EAS Simulators hosted iOS simulators and Android emulators for the mobile engine. |
|
|
62
|
+
| [`@e2e-dev/decision`](https://e2e.tester.army/docs/decision-models) | Decision-model executors for bounded semantic actions and assertions. |
|
|
53
63
|
|
|
54
64
|
## Documentation
|
|
55
65
|
|
package/dist/.build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.
|
|
1
|
+
{"version":"0.18.0"}
|
package/dist/agent/act.js
CHANGED
|
@@ -29,6 +29,7 @@ import { projectPriorSteps, serializeLedger } from './ledger.js';
|
|
|
29
29
|
import { instantiateLanguageModel } from './model/sdk.js';
|
|
30
30
|
import { ObservationFeed } from './observation-feed.js';
|
|
31
31
|
import { recordPolicyEvent } from './phases.js';
|
|
32
|
+
import { appLocation } from '../cache/route.js';
|
|
32
33
|
import { OperationQueue } from './operation-queue.js';
|
|
33
34
|
import { StepAccounting } from './step-accounting.js';
|
|
34
35
|
import { failedStepOutcome, StepTraceSession } from './step-cache.js';
|
|
@@ -199,7 +200,10 @@ class ActDispatch {
|
|
|
199
200
|
name: agent.executor.name,
|
|
200
201
|
...(agent.executor.version === undefined ? {} : { version: agent.executor.version }),
|
|
201
202
|
},
|
|
203
|
+
// Redacted already (`dispatchAgentStep`): the key digests the context the executor reads.
|
|
204
|
+
agent: { name: agent.name, context: agent.agentContext },
|
|
202
205
|
redact: runtime.redact,
|
|
206
|
+
redactCut: runtime.redactCut,
|
|
203
207
|
maxActions: this.accounting.maxActions,
|
|
204
208
|
stepIndex,
|
|
205
209
|
});
|
|
@@ -421,11 +425,10 @@ class ActDispatch {
|
|
|
421
425
|
const feed = this.feed;
|
|
422
426
|
return {
|
|
423
427
|
get traceEligible() { return feed.traceEligible; },
|
|
424
|
-
observe: async (mode) => screenOf(await this.feed.probe(mode)),
|
|
428
|
+
observe: async (mode) => screenOf(await this.feed.probe(mode), this.runtime.app.base?.origin),
|
|
425
429
|
actions: this.dispatcher.actions,
|
|
426
430
|
signal: this.accounting.signal,
|
|
427
431
|
remainingMs: () => this.accounting.remainingMs(),
|
|
428
|
-
redact: this.runtime.redact,
|
|
429
432
|
replaying: (active) => {
|
|
430
433
|
this.runtime.steps.replaying(active);
|
|
431
434
|
this.accounting.replaying(active);
|
|
@@ -474,10 +477,10 @@ class ActDispatch {
|
|
|
474
477
|
}
|
|
475
478
|
}
|
|
476
479
|
/** The replay engine's view of a capture: the nodes, and the viewport a recorded point is checked against. */
|
|
477
|
-
function screenOf(observation) {
|
|
480
|
+
function screenOf(observation, appOrigin) {
|
|
478
481
|
const metadata = {
|
|
479
482
|
viewport: { width: observation.viewport.width, height: observation.viewport.height },
|
|
480
|
-
...(observation.
|
|
483
|
+
...(observation.location === undefined ? {} : { path: appLocation(observation.location, appOrigin) }),
|
|
481
484
|
};
|
|
482
485
|
return observation.kind === 'semantic'
|
|
483
486
|
? { ...metadata, kind: 'semantic', nodes: observation.nodes }
|
|
@@ -487,10 +487,10 @@ export class ActionDispatcher {
|
|
|
487
487
|
}
|
|
488
488
|
async runActionNow(name, body) {
|
|
489
489
|
this.accounting.reserveAction();
|
|
490
|
-
const
|
|
490
|
+
const redaction = { redact: this.runtime.redact, redactCut: this.runtime.redactCut };
|
|
491
491
|
let action;
|
|
492
492
|
try {
|
|
493
|
-
action = await instrumentPhase(this.runtime, { api: this.accounting.api, kind: 'engine', phase: 'agent.action', name }, body, (committed) => ({ detail: describeAction(committed,
|
|
493
|
+
action = await instrumentPhase(this.runtime, { api: this.accounting.api, kind: 'engine', phase: 'agent.action', name }, body, (committed) => ({ detail: describeAction(committed, redaction).summary }));
|
|
494
494
|
}
|
|
495
495
|
catch (cause) {
|
|
496
496
|
this.accounting.checkpoint(cause);
|
|
@@ -579,9 +579,8 @@ export class ActionDispatcher {
|
|
|
579
579
|
* number picks the same one.
|
|
580
580
|
*/
|
|
581
581
|
placementOf({ node, observation }) {
|
|
582
|
-
const
|
|
583
|
-
const
|
|
584
|
-
const position = describePosition(node, within, observation.nodes, { redact });
|
|
582
|
+
const within = containerKey(node.ref.id, observation.nodes, observation.parents);
|
|
583
|
+
const position = describePosition(node, within, observation.nodes);
|
|
585
584
|
return {
|
|
586
585
|
...(within === undefined ? {} : { within }),
|
|
587
586
|
...(position === undefined ? {} : { position }),
|
package/dist/agent/actions.d.ts
CHANGED
|
@@ -8,51 +8,52 @@
|
|
|
8
8
|
* the same descriptor projection. One owner for all three, so a recorded
|
|
9
9
|
* summary, a live event line, and a relocation candidate can never drift.
|
|
10
10
|
*/
|
|
11
|
-
import type {
|
|
11
|
+
import type { ViewportPoint, ViewportSize } from '../engine/surface.ts';
|
|
12
|
+
import { type NodeRedaction, type RedactedNode } from './observation.ts';
|
|
12
13
|
import { type NodeActionName, type PointActionName, type TraceTargetDescriptor, type TracePosition } from '../cache/trace.ts';
|
|
13
14
|
import type { ScrollDirection } from '../types.ts';
|
|
14
15
|
/** One committed grammar action, addressed by the node it actually ran against. */
|
|
15
16
|
export type RecordableAction = ({
|
|
16
17
|
readonly name: NodeActionName;
|
|
17
|
-
readonly node:
|
|
18
|
+
readonly node: RedactedNode;
|
|
18
19
|
} & Placement) | ({
|
|
19
20
|
readonly name: 'type';
|
|
20
|
-
readonly node:
|
|
21
|
+
readonly node: RedactedNode;
|
|
21
22
|
readonly value: string;
|
|
22
23
|
} & Placement) | ({
|
|
23
24
|
readonly name: 'typeSecret';
|
|
24
|
-
readonly node:
|
|
25
|
+
readonly node: RedactedNode;
|
|
25
26
|
readonly secret: string;
|
|
26
27
|
} & Placement) | ({
|
|
27
28
|
readonly name: 'press';
|
|
28
|
-
readonly node:
|
|
29
|
+
readonly node: RedactedNode;
|
|
29
30
|
readonly key: string;
|
|
30
31
|
} & Placement) | ({
|
|
31
32
|
readonly name: 'select';
|
|
32
|
-
readonly node:
|
|
33
|
+
readonly node: RedactedNode;
|
|
33
34
|
readonly value: string;
|
|
34
35
|
} & Placement) | ({
|
|
35
36
|
readonly name: 'check';
|
|
36
|
-
readonly node:
|
|
37
|
+
readonly node: RedactedNode;
|
|
37
38
|
readonly checked: boolean;
|
|
38
39
|
} & Placement)
|
|
39
40
|
/** Project-relative paths as the executor gave them; replay authorizes them again. */
|
|
40
41
|
| ({
|
|
41
42
|
readonly name: 'upload';
|
|
42
|
-
readonly node:
|
|
43
|
+
readonly node: RedactedNode;
|
|
43
44
|
readonly paths: readonly string[];
|
|
44
45
|
} & Placement)
|
|
45
46
|
/** The dragged node with its placement, and where it was dropped, with its own. */
|
|
46
47
|
| ({
|
|
47
48
|
readonly name: 'drag';
|
|
48
|
-
readonly node:
|
|
49
|
+
readonly node: RedactedNode;
|
|
49
50
|
readonly destination: {
|
|
50
|
-
readonly node:
|
|
51
|
+
readonly node: RedactedNode;
|
|
51
52
|
} & Placement;
|
|
52
53
|
} & Placement) | ({
|
|
53
54
|
readonly name: 'scroll';
|
|
54
55
|
readonly direction: ScrollDirection;
|
|
55
|
-
readonly node?:
|
|
56
|
+
readonly node?: RedactedNode;
|
|
56
57
|
/** The share of the viewport the node covered when scrolled, 0 to 1 (`ScrollAction.spans`). */
|
|
57
58
|
readonly spans?: number;
|
|
58
59
|
} & Placement)
|
|
@@ -61,7 +62,7 @@ export type RecordableAction = ({
|
|
|
61
62
|
readonly name: 'scrollUntil';
|
|
62
63
|
readonly text: string;
|
|
63
64
|
readonly direction: ScrollDirection;
|
|
64
|
-
readonly node?:
|
|
65
|
+
readonly node?: RedactedNode;
|
|
65
66
|
readonly spans?: number;
|
|
66
67
|
/** How many pages it took; in the summary, never in the trace, since a replay pages for itself. */
|
|
67
68
|
readonly screens: number;
|
|
@@ -92,7 +93,7 @@ export type RecordableAction = ({
|
|
|
92
93
|
readonly name: PointActionName;
|
|
93
94
|
readonly point: ViewportPoint;
|
|
94
95
|
readonly viewport: ViewportSize;
|
|
95
|
-
readonly under?:
|
|
96
|
+
readonly under?: RedactedNode;
|
|
96
97
|
};
|
|
97
98
|
/** Where the node sat when it was acted on: its container's key and its place among identical twins. */
|
|
98
99
|
export interface Placement {
|
|
@@ -100,14 +101,17 @@ export interface Placement {
|
|
|
100
101
|
readonly position?: TracePosition;
|
|
101
102
|
}
|
|
102
103
|
/**
|
|
103
|
-
* The key of the nearest named container a node sits in
|
|
104
|
-
* its row or list item
|
|
105
|
-
* Ten rows each with a "Delete" button are ten
|
|
106
|
-
* in the row that starts with Budget draft" is
|
|
104
|
+
* The key of the nearest named container a node sits in, the first text of
|
|
105
|
+
* its row or list item or else the container's own label, when that key says
|
|
106
|
+
* more than the node's own name. Ten rows each with a "Delete" button are ten
|
|
107
|
+
* identical descriptors; "Delete in the row that starts with Budget draft" is
|
|
108
|
+
* one. A row whose only text is its own (`<li>Alpha <button><svg/></li>`)
|
|
109
|
+
* is keyed by it. Only the nearest container counts: the list around a row
|
|
110
|
+
* with no text names every row alike, so its key would tell no twin apart.
|
|
107
111
|
*/
|
|
108
|
-
export declare function containerKey(id: string, nodes: ReadonlyMap<string,
|
|
112
|
+
export declare function containerKey(id: string, nodes: ReadonlyMap<string, RedactedNode>, parents: ReadonlyMap<string, string>): string | undefined;
|
|
109
113
|
/** Parent id of every non-root node, derived from the tree the node map indexes. */
|
|
110
|
-
export declare function parentsOf(nodes: ReadonlyMap<string,
|
|
114
|
+
export declare function parentsOf(nodes: ReadonlyMap<string, RedactedNode>): ReadonlyMap<string, string>;
|
|
111
115
|
/** How one committed action reads back: where it acted, and what it did. */
|
|
112
116
|
export interface DescribedAction {
|
|
113
117
|
/**
|
|
@@ -124,13 +128,18 @@ export interface DescribedAction {
|
|
|
124
128
|
/** Where a drag was dropped, described like its target; only a drag has one. */
|
|
125
129
|
readonly destination?: TraceTargetDescriptor;
|
|
126
130
|
}
|
|
127
|
-
/**
|
|
128
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Describes one committed action for recording and for the live event. Its
|
|
133
|
+
* nodes pass the ledger again as it is now: a secret resolved after the
|
|
134
|
+
* capture (inside the action itself) masks what the capture still shows.
|
|
135
|
+
*/
|
|
136
|
+
export declare function describeAction(action: RecordableAction, redaction: NodeRedaction): DescribedAction;
|
|
129
137
|
/**
|
|
130
138
|
* Builds the durable descriptor for one resolved node: the semantic fields
|
|
131
139
|
* replay re-finds it by, plus the engine's structural selector hint (kept as
|
|
132
140
|
* provenance for tuned policies; the conservative relocator ignores it).
|
|
133
141
|
* Values a secure node holds are never part of it — descriptors carry how a
|
|
134
|
-
* node is named, not what it contains.
|
|
142
|
+
* node is named, not what it contains. The node was redacted with its
|
|
143
|
+
* observation (`redactNode`), each field in the one-line form read here too.
|
|
135
144
|
*/
|
|
136
|
-
export declare function describeTarget(node:
|
|
145
|
+
export declare function describeTarget(node: RedactedNode): TraceTargetDescriptor | undefined;
|
package/dist/agent/actions.js
CHANGED
|
@@ -8,32 +8,33 @@
|
|
|
8
8
|
* the same descriptor projection. One owner for all three, so a recorded
|
|
9
9
|
* summary, a live event line, and a relocation candidate can never drift.
|
|
10
10
|
*/
|
|
11
|
+
import { redactNode } from './observation.js';
|
|
11
12
|
import { bound, isNodeAction, MAX_TRACE_DESCRIPTOR_CHARS, MAX_TRACE_SUMMARY_CHARS, } from '../cache/trace.js';
|
|
12
13
|
import { sanitizeText } from '../internal/errors.js';
|
|
13
|
-
import {
|
|
14
|
+
import { collapseText } from '../internal/text.js';
|
|
14
15
|
/** Roles whose first text names the thing a control belongs to: a row's key, a list item's title. */
|
|
15
16
|
const CONTAINER_ROLES = new Set(['row', 'listitem', 'article', 'group', 'region', 'dialog', 'tabpanel']);
|
|
16
17
|
/** Longest container key kept. */
|
|
17
18
|
const MAX_WITHIN_CHARS = 80;
|
|
18
19
|
/**
|
|
19
|
-
* The key of the nearest named container a node sits in
|
|
20
|
-
* its row or list item
|
|
21
|
-
* Ten rows each with a "Delete" button are ten
|
|
22
|
-
* in the row that starts with Budget draft" is
|
|
20
|
+
* The key of the nearest named container a node sits in, the first text of
|
|
21
|
+
* its row or list item or else the container's own label, when that key says
|
|
22
|
+
* more than the node's own name. Ten rows each with a "Delete" button are ten
|
|
23
|
+
* identical descriptors; "Delete in the row that starts with Budget draft" is
|
|
24
|
+
* one. A row whose only text is its own (`<li>Alpha <button><svg/></li>`)
|
|
25
|
+
* is keyed by it. Only the nearest container counts: the list around a row
|
|
26
|
+
* with no text names every row alike, so its key would tell no twin apart.
|
|
23
27
|
*/
|
|
24
|
-
export function containerKey(id, nodes, parents
|
|
28
|
+
export function containerKey(id, nodes, parents) {
|
|
25
29
|
const node = nodes.get(id);
|
|
26
30
|
const own = node === undefined ? '' : squash(node.name ?? node.text ?? '');
|
|
27
31
|
let cursor = parents.get(id);
|
|
28
32
|
while (cursor !== undefined) {
|
|
29
33
|
const container = nodes.get(cursor);
|
|
30
34
|
if (container !== undefined && CONTAINER_ROLES.has(container.role ?? '')) {
|
|
31
|
-
const key = firstLeafText(container);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return clean === '' || squash(clean) === own ? undefined : clean;
|
|
35
|
-
}
|
|
36
|
-
return undefined;
|
|
35
|
+
const key = firstLeafText(container) ?? ownLabel(container);
|
|
36
|
+
const clean = key === undefined ? '' : bound(collapseText(key), MAX_WITHIN_CHARS);
|
|
37
|
+
return clean === '' || squash(clean) === own ? undefined : clean;
|
|
37
38
|
}
|
|
38
39
|
cursor = parents.get(cursor);
|
|
39
40
|
}
|
|
@@ -60,6 +61,11 @@ function firstLeafText(node) {
|
|
|
60
61
|
}
|
|
61
62
|
return undefined;
|
|
62
63
|
}
|
|
64
|
+
/** A container's own name or text, when it has one. */
|
|
65
|
+
function ownLabel(node) {
|
|
66
|
+
const label = (node.name ?? node.text ?? '').trim();
|
|
67
|
+
return label === '' ? undefined : label;
|
|
68
|
+
}
|
|
63
69
|
function squash(text) {
|
|
64
70
|
return text.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
65
71
|
}
|
|
@@ -74,16 +80,20 @@ const NODE_ACTION_PROSE = {
|
|
|
74
80
|
};
|
|
75
81
|
/** How each point verb reads: `tap the point (x, y)`, `hover over the point (x, y)`. */
|
|
76
82
|
const POINT_ACTION_PROSE = { tapAt: 'tap', hoverAt: 'hover over' };
|
|
77
|
-
/**
|
|
78
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Describes one committed action for recording and for the live event. Its
|
|
85
|
+
* nodes pass the ledger again as it is now: a secret resolved after the
|
|
86
|
+
* capture (inside the action itself) masks what the capture still shows.
|
|
87
|
+
*/
|
|
88
|
+
export function describeAction(action, redaction) {
|
|
79
89
|
const target = 'node' in action
|
|
80
|
-
? describePlaced(action.node, action,
|
|
90
|
+
? describePlaced(action.node, action, redaction)
|
|
81
91
|
: 'point' in action
|
|
82
|
-
? describePlaced(action.under, {},
|
|
92
|
+
? describePlaced(action.under, {}, redaction)
|
|
83
93
|
: undefined;
|
|
84
|
-
const destination = action.name === 'drag' ? describePlaced(action.destination.node, action.destination,
|
|
94
|
+
const destination = action.name === 'drag' ? describePlaced(action.destination.node, action.destination, redaction) : undefined;
|
|
85
95
|
const where = describeForSummary(target);
|
|
86
|
-
const safe = (value) => quote(redact(sanitizeText(value)));
|
|
96
|
+
const safe = (value) => quote(redaction.redact(sanitizeText(value)));
|
|
87
97
|
const prose = (() => {
|
|
88
98
|
if (isNodeAction(action))
|
|
89
99
|
return NODE_ACTION_PROSE[action.name](where);
|
|
@@ -134,13 +144,16 @@ export function describeAction(action, redact) {
|
|
|
134
144
|
};
|
|
135
145
|
}
|
|
136
146
|
/** A node's durable descriptor with the placement it was acted on in; undefined for no node or nothing durable. */
|
|
137
|
-
function describePlaced(node, placement,
|
|
138
|
-
|
|
147
|
+
function describePlaced(node, placement, redaction) {
|
|
148
|
+
if (node === undefined)
|
|
149
|
+
return undefined;
|
|
150
|
+
const { children: _children, ...fields } = node;
|
|
151
|
+
const described = describeTarget(redactNode(fields, redaction));
|
|
139
152
|
if (described === undefined)
|
|
140
153
|
return undefined;
|
|
141
154
|
return {
|
|
142
155
|
...described,
|
|
143
|
-
...(placement.within === undefined ? {} : { within: bound(placement.within, MAX_WITHIN_CHARS) }),
|
|
156
|
+
...(placement.within === undefined ? {} : { within: bound(redaction.redact(placement.within), MAX_WITHIN_CHARS) }),
|
|
144
157
|
...(placement.position === undefined ? {} : { position: placement.position }),
|
|
145
158
|
};
|
|
146
159
|
}
|
|
@@ -149,13 +162,14 @@ function describePlaced(node, placement, redact) {
|
|
|
149
162
|
* replay re-finds it by, plus the engine's structural selector hint (kept as
|
|
150
163
|
* provenance for tuned policies; the conservative relocator ignores it).
|
|
151
164
|
* Values a secure node holds are never part of it — descriptors carry how a
|
|
152
|
-
* node is named, not what it contains.
|
|
165
|
+
* node is named, not what it contains. The node was redacted with its
|
|
166
|
+
* observation (`redactNode`), each field in the one-line form read here too.
|
|
153
167
|
*/
|
|
154
|
-
export function describeTarget(node
|
|
168
|
+
export function describeTarget(node) {
|
|
155
169
|
const field = (value) => {
|
|
156
170
|
if (value === undefined)
|
|
157
171
|
return undefined;
|
|
158
|
-
const collapsed =
|
|
172
|
+
const collapsed = collapseText(value);
|
|
159
173
|
return collapsed === '' ? undefined : bound(collapsed, MAX_TRACE_DESCRIPTOR_CHARS);
|
|
160
174
|
};
|
|
161
175
|
const role = field(node.role);
|
package/dist/agent/ai-sdk.js
CHANGED
|
@@ -23,10 +23,11 @@ export function asSdkLanguageModel(instance) {
|
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* The cache lives on `globalThis`, not in module state: config and test
|
|
26
|
-
* modules
|
|
27
|
-
* code and the runner's own model plumbing would
|
|
28
|
-
* caches
|
|
29
|
-
* each other's instanceof checks. One slot, one SDK
|
|
26
|
+
* modules may import a copy of e2e other than the runner's, so an executor
|
|
27
|
+
* constructed in config code and the runner's own model plumbing would
|
|
28
|
+
* otherwise hold two separate caches, and two separate `ai` module instances
|
|
29
|
+
* whose error classes fail each other's instanceof checks. One slot, one SDK
|
|
30
|
+
* instance, every copy.
|
|
30
31
|
*/
|
|
31
32
|
const cacheSlot = realmSlot('e2e.ai-sdk.v1');
|
|
32
33
|
function cache() {
|
package/dist/agent/error.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Runner-owned agent error classification. */
|
|
2
|
-
import { classifyError, E2EError } from '../internal/errors.js';
|
|
2
|
+
import { classifyError, E2EError, isE2EError } from '../internal/errors.js';
|
|
3
3
|
/**
|
|
4
4
|
* The single source of truth for the closed agent code set: exit/result class
|
|
5
5
|
* for every code, the blocked category for codes a `blocked` verdict may
|
|
@@ -55,7 +55,7 @@ const MODEL_UNREACHABLE_CODES = new Set(Object.entries(AGENT_CODE_TABLE)
|
|
|
55
55
|
* model was shown may be what went wrong.
|
|
56
56
|
*/
|
|
57
57
|
export function isModelUnreachable(error) {
|
|
58
|
-
if (!(error
|
|
58
|
+
if (!isE2EError(error))
|
|
59
59
|
return false;
|
|
60
60
|
return MODEL_UNREACHABLE_CODES.has(error.code);
|
|
61
61
|
}
|
|
@@ -95,7 +95,7 @@ const AGENT_CODES = new Set(Object.keys(CATEGORY_BY_CODE));
|
|
|
95
95
|
export function toAgentError(cause) {
|
|
96
96
|
if (isAgentError(cause))
|
|
97
97
|
return cause;
|
|
98
|
-
const classified =
|
|
98
|
+
const classified = classifyError(cause);
|
|
99
99
|
if (AGENT_CODES.has(classified.code)) {
|
|
100
100
|
return new AgentError(classified.code, classified.message, { cause });
|
|
101
101
|
}
|
package/dist/agent/invocation.js
CHANGED
|
@@ -104,14 +104,13 @@ export class Invocation {
|
|
|
104
104
|
/** Captures one raw observation through the shared race-hardened path. */
|
|
105
105
|
captureObservation(pixels) {
|
|
106
106
|
return retryingObserve({
|
|
107
|
-
observe: (operation) => this.session.observe(operation, {
|
|
108
|
-
pixels,
|
|
109
|
-
pixelFallback: this.options.vision !== false && !this.runtime.exposure.withholdsPixels,
|
|
110
|
-
}),
|
|
107
|
+
observe: (operation, pixelFallback) => this.session.observe(operation, { pixels, pixelFallback }),
|
|
111
108
|
operation: () => this.operation(),
|
|
112
109
|
guard: (cause) => this.checkDeadline(cause),
|
|
113
110
|
signal: this.runtime.engine.signal,
|
|
114
111
|
api: this.options.api,
|
|
112
|
+
fallback: this.options.vision !== false,
|
|
113
|
+
tainted: () => this.runtime.exposure.withholdsPixels,
|
|
115
114
|
});
|
|
116
115
|
}
|
|
117
116
|
/**
|
package/dist/agent/model/sdk.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { withHint } from '../../internal/errors.js';
|
|
10
10
|
import { missingModelError } from '../../config/agent.js';
|
|
11
11
|
import { aiSdk, asSdkLanguageModel, loadAiSdk } from '../ai-sdk.js';
|
|
12
|
+
import { MODEL_REQUEST_HEADERS } from '../../internal/client-identity.js';
|
|
12
13
|
import { packageVersion } from '../../internal/package-version.js';
|
|
13
14
|
import { AgentError } from '../error.js';
|
|
14
15
|
import { isContextOverflow } from './overflow.js';
|
|
@@ -75,6 +76,7 @@ export function createModelAdapter(model, agentName = 'default') {
|
|
|
75
76
|
maxOutputTokens: call.maxOutputTokens,
|
|
76
77
|
...(providerOptions === undefined ? {} : { providerOptions: providerOptions }),
|
|
77
78
|
maxRetries: TRANSPORT_RETRIES,
|
|
79
|
+
headers: MODEL_REQUEST_HEADERS,
|
|
78
80
|
abortSignal: call.signal,
|
|
79
81
|
timeout: call.timeoutMs,
|
|
80
82
|
};
|
|
@@ -33,6 +33,15 @@ export declare function isForcedToolChoiceDowngraded(warning: {
|
|
|
33
33
|
export declare function isForcedToolCallSkipped(error: unknown): error is {
|
|
34
34
|
readonly content?: readonly unknown[];
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Whether a turn forced to call `complete_step` called other tools only. The
|
|
38
|
+
* AI SDK throws `AI_ToolChoiceViolationError` for that too, with the reply's
|
|
39
|
+
* raw content; it is the model breaking the protocol, as when it names a tool
|
|
40
|
+
* the turn does not offer, not the provider failing.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isForcedToolCallMismatched(error: unknown): error is {
|
|
43
|
+
readonly content: readonly unknown[];
|
|
44
|
+
};
|
|
36
45
|
/**
|
|
37
46
|
* Whether a provider failure says the model does not accept a forced tool
|
|
38
47
|
* choice. Reads the message and the raw response body of the error and of
|
|
@@ -42,6 +42,20 @@ export function isForcedToolCallSkipped(error) {
|
|
|
42
42
|
const record = error;
|
|
43
43
|
return record.name === 'AI_ToolChoiceViolationError' && record.finishReason === 'stop';
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether a turn forced to call `complete_step` called other tools only. The
|
|
47
|
+
* AI SDK throws `AI_ToolChoiceViolationError` for that too, with the reply's
|
|
48
|
+
* raw content; it is the model breaking the protocol, as when it names a tool
|
|
49
|
+
* the turn does not offer, not the provider failing.
|
|
50
|
+
*/
|
|
51
|
+
export function isForcedToolCallMismatched(error) {
|
|
52
|
+
if (typeof error !== 'object' || error === null)
|
|
53
|
+
return false;
|
|
54
|
+
const record = error;
|
|
55
|
+
return (record.name === 'AI_ToolChoiceViolationError' &&
|
|
56
|
+
Array.isArray(record.content) &&
|
|
57
|
+
record.content.some((part) => typeof part === 'object' && part !== null && 'type' in part && part.type === 'tool-call'));
|
|
58
|
+
}
|
|
45
59
|
/** Longest cause chain walked; a wrapped error rarely nests deeper. */
|
|
46
60
|
const MAX_CAUSE_DEPTH = 8;
|
|
47
61
|
/**
|
|
@@ -7,24 +7,23 @@
|
|
|
7
7
|
* executor's redacted view of a capture. Nothing else in the step observes
|
|
8
8
|
* the engine; the dispatcher and the pixel tier ask the feed.
|
|
9
9
|
*/
|
|
10
|
-
import type { SemanticNode } from '../engine/surface.ts';
|
|
11
10
|
import type { StepAgentDetails } from '../run/steps.ts';
|
|
12
11
|
import { AgentError } from './error.ts';
|
|
13
12
|
import type { ExecutorObservation, ExecutorObserveOptions, ExecutorTarget } from './executor.ts';
|
|
14
13
|
import type { AgentContext } from './invocation.ts';
|
|
15
|
-
import { type AgentObservation, type SemanticAgentObservation } from './observation.ts';
|
|
14
|
+
import { type AgentObservation, type RedactedNode, type SemanticAgentObservation } from './observation.ts';
|
|
16
15
|
import type { OperationQueue } from './operation-queue.ts';
|
|
17
16
|
import { type SettleMode } from './settle-policy.ts';
|
|
18
17
|
import type { StepAccounting } from './step-accounting.ts';
|
|
19
18
|
/** A target resolved to a node of the observation it was found in. */
|
|
20
19
|
export interface Resolved {
|
|
21
|
-
readonly node:
|
|
20
|
+
readonly node: RedactedNode;
|
|
22
21
|
readonly observation: SemanticAgentObservation;
|
|
23
22
|
}
|
|
24
23
|
/** What a target names on the newest screen: the id as the screen lists it, the node or nothing, and the screen looked at. */
|
|
25
24
|
export interface Lookup {
|
|
26
25
|
readonly id: string;
|
|
27
|
-
readonly node:
|
|
26
|
+
readonly node: RedactedNode | undefined;
|
|
28
27
|
readonly observation: SemanticAgentObservation;
|
|
29
28
|
}
|
|
30
29
|
export interface ObservationFeedOptions {
|
|
@@ -116,8 +115,8 @@ export declare class ObservationFeed {
|
|
|
116
115
|
* through the queued observe: this runs inside a queued action body, and a
|
|
117
116
|
* queued observation would wait on its own caller.
|
|
118
117
|
*/
|
|
119
|
-
relocate(stale:
|
|
120
|
-
node:
|
|
118
|
+
relocate(stale: RedactedNode): Promise<{
|
|
119
|
+
node: RedactedNode;
|
|
121
120
|
observation: SemanticAgentObservation;
|
|
122
121
|
} | undefined>;
|
|
123
122
|
/**
|
|
@@ -132,7 +132,7 @@ export class ObservationFeed {
|
|
|
132
132
|
...metadata,
|
|
133
133
|
text: observation.text,
|
|
134
134
|
truncated: observation.truncated,
|
|
135
|
-
...(options.tree === true ? { tree: projectTree(observation.tree, this.runtime.
|
|
135
|
+
...(options.tree === true ? { tree: projectTree(observation.tree, this.runtime.app.base?.origin) } : {}),
|
|
136
136
|
...pixels,
|
|
137
137
|
};
|
|
138
138
|
}
|
|
@@ -197,8 +197,7 @@ export class ObservationFeed {
|
|
|
197
197
|
* queued observation would wait on its own caller.
|
|
198
198
|
*/
|
|
199
199
|
async relocate(stale) {
|
|
200
|
-
const
|
|
201
|
-
const descriptor = describeTarget(stale, options.redact);
|
|
200
|
+
const descriptor = describeTarget(stale);
|
|
202
201
|
if (descriptor === undefined)
|
|
203
202
|
return undefined;
|
|
204
203
|
this.accounting.checkpoint();
|
|
@@ -206,7 +205,7 @@ export class ObservationFeed {
|
|
|
206
205
|
this.publish(observation);
|
|
207
206
|
if (observation.kind === 'pixels')
|
|
208
207
|
return undefined;
|
|
209
|
-
const relocated = relocateDescriptor(descriptor, observation.nodes
|
|
208
|
+
const relocated = relocateDescriptor(descriptor, observation.nodes);
|
|
210
209
|
if (relocated.kind !== 'found')
|
|
211
210
|
return undefined;
|
|
212
211
|
const node = observation.nodes.get(relocated.id);
|
|
@@ -307,14 +306,13 @@ export class ObservationFeed {
|
|
|
307
306
|
async capture(pixels) {
|
|
308
307
|
this.opening = undefined;
|
|
309
308
|
const raw = await retryingObserve({
|
|
310
|
-
observe: (operation) => this.runtime.engine.session.observe(operation, {
|
|
311
|
-
pixels,
|
|
312
|
-
pixelFallback: !this.runtime.exposure.withholdsPixels,
|
|
313
|
-
}),
|
|
309
|
+
observe: (operation, pixelFallback) => this.runtime.engine.session.observe(operation, { pixels, pixelFallback }),
|
|
314
310
|
operation: () => this.accounting.operation(),
|
|
315
311
|
guard: (cause) => this.accounting.checkpoint(cause),
|
|
316
312
|
signal: this.runtime.engine.signal,
|
|
317
313
|
api: this.accounting.api,
|
|
314
|
+
fallback: true,
|
|
315
|
+
tainted: () => this.runtime.exposure.withholdsPixels,
|
|
318
316
|
});
|
|
319
317
|
if (raw.kind === 'pixels') {
|
|
320
318
|
this.semanticHistory = false;
|
|
@@ -376,11 +374,10 @@ export class ObservationFeed {
|
|
|
376
374
|
const earlier = this.lastSeen(id)?.node;
|
|
377
375
|
if (earlier === undefined)
|
|
378
376
|
return undefined;
|
|
379
|
-
const
|
|
380
|
-
const descriptor = describeTarget(earlier, options.redact);
|
|
377
|
+
const descriptor = describeTarget(earlier);
|
|
381
378
|
if (descriptor === undefined)
|
|
382
379
|
return undefined;
|
|
383
|
-
const relocated = relocateDescriptor(descriptor, latest.nodes
|
|
380
|
+
const relocated = relocateDescriptor(descriptor, latest.nodes);
|
|
384
381
|
return relocated.kind === 'found' ? latest.nodes.get(relocated.id) : undefined;
|
|
385
382
|
}
|
|
386
383
|
}
|