e2e 0.21.1 → 0.21.2
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 +2 -0
- package/dist/.build.json +1 -1
- package/dist/agent/act-validation.js +1 -0
- package/dist/agent/act.js +7 -3
- package/dist/agent/action-dispatcher.js +30 -8
- package/dist/agent/actions.d.ts +2 -0
- package/dist/agent/actions.js +5 -2
- package/dist/agent/error.js +1 -0
- package/dist/agent/executor.d.ts +14 -6
- package/dist/agent/index.js +5 -1
- package/dist/agent/invocation.d.ts +3 -0
- package/dist/agent/model/sdk.d.ts +5 -0
- package/dist/agent/model/sdk.js +18 -1
- package/dist/agent/observation.js +1 -0
- package/dist/agent/primitives.js +14 -3
- package/dist/agent/replay.js +9 -3
- package/dist/agent/step-cache.d.ts +3 -2
- package/dist/agent/step-cache.js +7 -4
- package/dist/agent/tool-loop.js +4 -1
- package/dist/cache/context.js +1 -1
- package/dist/cache/recorder.d.ts +2 -1
- package/dist/cache/recorder.js +11 -2
- package/dist/cache/trace-fields.d.ts +10 -0
- package/dist/cache/trace-fields.js +17 -0
- package/dist/cache/trace.d.ts +9 -1
- package/dist/cache/trace.js +26 -7
- package/dist/cli/feedback.js +2 -1
- package/dist/cli/init/sibling-versions.json +1 -1
- package/dist/collect/select.js +12 -2
- package/dist/config/agent.d.ts +1 -1
- package/dist/config/diagnose.js +9 -3
- package/dist/config/esm-hooks.js +3 -2
- package/dist/config/resolve.js +3 -2
- package/dist/config/typescript.js +52 -2
- package/dist/engine/contract.d.ts +3 -2
- package/dist/engine/manifest.js +2 -2
- package/dist/internal/errors.js +5 -2
- package/dist/internal/source.js +2 -0
- package/dist/internal/unserved-model.d.ts +5 -0
- package/dist/internal/unserved-model.js +22 -0
- package/dist/locator/screen.js +1 -2
- package/dist/oauth/copilot.js +23 -5
- package/dist/oauth/device-code.js +35 -19
- package/dist/oauth/errors.d.ts +3 -0
- package/dist/oauth/errors.js +26 -11
- package/dist/oauth/providers/github-copilot.d.ts +4 -0
- package/dist/oauth/providers/github-copilot.js +64 -16
- package/dist/oauth/providers/openai.d.ts +2 -2
- package/dist/oauth/providers/openai.js +47 -19
- package/dist/oauth/providers/opencode-console.js +36 -15
- package/dist/oauth/providers/xai.js +18 -9
- package/dist/oauth/token-endpoint.d.ts +15 -6
- package/dist/oauth/token-endpoint.js +18 -6
- package/dist/report/code-frame.js +9 -3
- package/dist/report/failure-text.d.ts +10 -2
- package/dist/report/failure-text.js +24 -8
- package/dist/report/list-model.d.ts +13 -0
- package/dist/report/list-steps.d.ts +6 -1
- package/dist/report/list-steps.js +29 -0
- package/dist/report/list.d.ts +1 -0
- package/dist/report/list.js +16 -7
- package/dist/report/live-window.js +2 -1
- package/dist/report/markdown.js +1 -1
- package/dist/run/fixtures.js +2 -0
- package/dist/run/host-trace.d.ts +3 -2
- package/dist/run/host-trace.js +2 -1
- package/dist/run/last-run.d.ts +9 -0
- package/dist/run/last-run.js +28 -0
- package/dist/run/open-session.d.ts +3 -1
- package/dist/run/open-session.js +1 -1
- package/dist/run/runner.js +6 -2
- package/dist/run/sessions.js +3 -3
- package/dist/run/standalone-attempt.d.ts +2 -1
- package/dist/run/standalone-attempt.js +3 -1
- package/dist/run/steps.d.ts +3 -1
- package/dist/run/units.d.ts +8 -0
- package/dist/run/units.js +14 -0
- package/dist/run/worker-models.d.ts +2 -0
- package/dist/run/worker-models.js +7 -0
- package/dist/types.d.ts +2 -2
- package/docs/authentication.mdx +26 -23
- package/docs/browser.mdx +7 -4
- package/docs/bug-bash.mdx +2 -2
- package/docs/cache.mdx +6 -1
- package/docs/ci/bitrise.mdx +1 -1
- package/docs/ci/codemagic.mdx +2 -2
- package/docs/ci/eas-workflows.mdx +1 -1
- package/docs/coding-agents.mdx +7 -35
- package/docs/debugging.mdx +24 -36
- package/docs/executors.mdx +1 -1
- package/docs/github.mdx +2 -1
- package/docs/integrations/eas.mdx +1 -1
- package/docs/integrations/index.mdx +6 -0
- package/docs/integrations/lightpanda.mdx +192 -0
- package/docs/integrations/testmu.mdx +152 -0
- package/docs/mcp.mdx +123 -0
- package/docs/migrate/detox.mdx +1 -1
- package/docs/migrate/maestro.mdx +101 -105
- package/docs/migrate/playwright.mdx +65 -70
- package/docs/mobile.mdx +18 -3
- package/docs/models.mdx +4 -2
- package/docs/quickstart.mdx +7 -0
- package/docs/reference/agent.mdx +369 -277
- package/docs/reference/app.mdx +141 -32
- package/docs/reference/cli.mdx +674 -558
- package/docs/reference/config.mdx +1406 -752
- package/docs/reference/engine.mdx +1062 -256
- package/docs/reference/environment.mdx +156 -84
- package/docs/reference/errors.mdx +106 -72
- package/docs/reference/expect.mdx +752 -372
- package/docs/reference/mcp.mdx +366 -141
- package/docs/reference/mobile.mdx +844 -403
- package/docs/reference/reporters.mdx +312 -106
- package/docs/reference/runner.mdx +373 -174
- package/docs/reference/screen.mdx +931 -398
- package/docs/reference/security.mdx +106 -40
- package/docs/reference/test.mdx +626 -273
- package/docs/reference/web.mdx +956 -533
- package/docs/security.mdx +6 -5
- package/docs/snippets/model-provider.jsx +763 -0
- package/docs/snippets/setup-prompt-card.jsx +22 -0
- package/docs/snippets/show-more.jsx +22 -0
- package/docs/snippets/video-card.jsx +34 -0
- package/docs/snippets/video.jsx +25 -0
- package/docs/subscriptions.mdx +10 -3
- package/docs/telemetry.mdx +39 -39
- package/package.json +3 -2
- package/schema/report-v1.schema.json +6 -0
- package/skills/e2e/references/agent.md +8 -3
- package/skills/e2e/references/debugging.md +1 -1
- package/skills/e2e/references/mcp.md +1 -1
- package/skills/e2e/references/setup.md +12 -3
- package/skills/e2e/references/writing-tests.md +1 -1
package/dist/cache/trace.d.ts
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
* and a document this runner cannot replay verbatim comes back as
|
|
16
16
|
* `undefined`, never as a repaired or partially trusted entry.
|
|
17
17
|
*/
|
|
18
|
+
import { type KeyModifier } from '../engine/contract.ts';
|
|
18
19
|
import type { ScrollDirection } from '../types.ts';
|
|
20
|
+
import { type RecordedModel } from './trace-fields.ts';
|
|
21
|
+
export type { RecordedModel } from './trace-fields.ts';
|
|
19
22
|
export declare const TRACE_SCHEMA_VERSION = "trace-1";
|
|
20
23
|
/** Mirrors the default step action budget; overflow marks the trace truncated. */
|
|
21
24
|
export declare const MAX_TRACE_ACTIONS = 50;
|
|
@@ -121,6 +124,8 @@ interface ActionBase {
|
|
|
121
124
|
/** The verbs that act on one node and carry no input: tap and its variants, hover, scroll into view. */
|
|
122
125
|
declare const NODE_ACTION_NAMES: readonly ['tap', 'doubleTap', 'longPress', 'secondaryTap', 'hover', 'scrollTo'];
|
|
123
126
|
export type NodeActionName = (typeof NODE_ACTION_NAMES)[number];
|
|
127
|
+
/** Whether a node verb clicks, so it may carry `modifiers`. */
|
|
128
|
+
export declare const isTapActionName: (value: unknown) => value is "doubleTap" | "secondaryTap" | "tap";
|
|
124
129
|
/**
|
|
125
130
|
* Whether an action is one of the node verbs. The every-layer switches over
|
|
126
131
|
* an action union (prose, recording, templating, replay) take these six
|
|
@@ -134,6 +139,8 @@ export declare function isNodeAction<A extends {
|
|
|
134
139
|
export interface NodeAction extends ActionBase {
|
|
135
140
|
readonly name: NodeActionName;
|
|
136
141
|
readonly target: TraceTargetDescriptor;
|
|
142
|
+
/** Keys a tap-family action held for the click; replayed with the action. */
|
|
143
|
+
readonly modifiers?: readonly KeyModifier[];
|
|
137
144
|
}
|
|
138
145
|
export interface TypeAction extends ActionBase {
|
|
139
146
|
readonly name: 'type';
|
|
@@ -283,6 +290,8 @@ export interface ActionTrace {
|
|
|
283
290
|
};
|
|
284
291
|
/** Which test, target, and instruction recorded it; absent on older entries. */
|
|
285
292
|
readonly recordedFor?: TraceProvenance;
|
|
293
|
+
/** Configured model when recorded; absent on older or unattributed entries. */
|
|
294
|
+
readonly recordedBy?: RecordedModel;
|
|
286
295
|
/** The recorded run's verdict summary. */
|
|
287
296
|
readonly summary: string;
|
|
288
297
|
/**
|
|
@@ -372,4 +381,3 @@ export type TraceTextMap = (text: string) => string | undefined;
|
|
|
372
381
|
* `undefined` from `map` makes the whole result `undefined`.
|
|
373
382
|
*/
|
|
374
383
|
export declare function mapTraceText(trace: ActionTrace, map: TraceTextMap): ActionTrace | undefined;
|
|
375
|
-
export {};
|
package/dist/cache/trace.js
CHANGED
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* and a document this runner cannot replay verbatim comes back as
|
|
16
16
|
* `undefined`, never as a repaired or partially trusted entry.
|
|
17
17
|
*/
|
|
18
|
+
import { isKeyModifier, KEY_MODIFIERS } from '../engine/contract.js';
|
|
18
19
|
import { timestamp } from '../internal/ids.js';
|
|
19
20
|
import { bound } from '../internal/text.js';
|
|
21
|
+
import { readBoundedText, readRecordedModel } from './trace-fields.js';
|
|
20
22
|
export const TRACE_SCHEMA_VERSION = 'trace-1';
|
|
21
23
|
/** Mirrors the default step action budget; overflow marks the trace truncated. */
|
|
22
24
|
export const MAX_TRACE_ACTIONS = 50;
|
|
@@ -81,6 +83,10 @@ const isTraceAnchorState = oneOf(TRACE_ANCHOR_STATES);
|
|
|
81
83
|
/** The verbs that act on one node and carry no input: tap and its variants, hover, scroll into view. */
|
|
82
84
|
const NODE_ACTION_NAMES = ['tap', 'doubleTap', 'longPress', 'secondaryTap', 'hover', 'scrollTo'];
|
|
83
85
|
const isNodeActionName = oneOf(NODE_ACTION_NAMES);
|
|
86
|
+
/** The node verbs that click: the only ones that may hold key modifiers. */
|
|
87
|
+
const TAP_ACTION_NAMES = ['tap', 'doubleTap', 'secondaryTap'];
|
|
88
|
+
/** Whether a node verb clicks, so it may carry `modifiers`. */
|
|
89
|
+
export const isTapActionName = oneOf(TAP_ACTION_NAMES);
|
|
84
90
|
/**
|
|
85
91
|
* Whether an action is one of the node verbs. The every-layer switches over
|
|
86
92
|
* an action union (prose, recording, templating, replay) take these six
|
|
@@ -172,6 +178,7 @@ function readActionTrace(document) {
|
|
|
172
178
|
const recordedFor = recordedForRaw === undefined ? undefined : readProvenance(recordedForRaw);
|
|
173
179
|
if (recordedForRaw !== undefined && recordedFor === undefined)
|
|
174
180
|
return undefined;
|
|
181
|
+
const recordedBy = readRecordedModel(raw['recordedBy'], MAX_TRACE_DESCRIPTOR_CHARS);
|
|
175
182
|
const summary = readBoundedText(raw['summary'], MAX_TRACE_SUMMARY_CHARS);
|
|
176
183
|
if (summary === undefined)
|
|
177
184
|
return undefined;
|
|
@@ -211,6 +218,7 @@ function readActionTrace(document) {
|
|
|
211
218
|
...(executorVersion === undefined ? {} : { version: executorVersion }),
|
|
212
219
|
},
|
|
213
220
|
...(recordedFor === undefined ? {} : { recordedFor }),
|
|
221
|
+
...(recordedBy === undefined ? {} : { recordedBy }),
|
|
214
222
|
summary,
|
|
215
223
|
...(startPath === undefined ? {} : { startPath }),
|
|
216
224
|
...(endPath === undefined ? {} : { endPath }),
|
|
@@ -229,6 +237,18 @@ function readAnchors(document) {
|
|
|
229
237
|
return undefined;
|
|
230
238
|
return each(document, readDescriptor);
|
|
231
239
|
}
|
|
240
|
+
/** The `modifiers` of a recorded tap-family action: a non-empty list of distinct key modifiers, or undefined when malformed. */
|
|
241
|
+
function readModifiers(document) {
|
|
242
|
+
if (!Array.isArray(document) || document.length === 0 || document.length > KEY_MODIFIERS.length)
|
|
243
|
+
return undefined;
|
|
244
|
+
const modifiers = [];
|
|
245
|
+
for (const value of document) {
|
|
246
|
+
if (!isKeyModifier(value) || modifiers.includes(value))
|
|
247
|
+
return undefined;
|
|
248
|
+
modifiers.push(value);
|
|
249
|
+
}
|
|
250
|
+
return modifiers;
|
|
251
|
+
}
|
|
232
252
|
function readRecordedAction(document) {
|
|
233
253
|
if (typeof document !== 'object' || document === null || Array.isArray(document)) {
|
|
234
254
|
return undefined;
|
|
@@ -240,7 +260,12 @@ function readRecordedAction(document) {
|
|
|
240
260
|
const name = raw['name'];
|
|
241
261
|
if (isNodeActionName(name)) {
|
|
242
262
|
const target = readDescriptor(raw['target']);
|
|
243
|
-
|
|
263
|
+
if (target === undefined)
|
|
264
|
+
return undefined;
|
|
265
|
+
if (raw['modifiers'] === undefined)
|
|
266
|
+
return { name, summary, target };
|
|
267
|
+
const modifiers = isTapActionName(name) ? readModifiers(raw['modifiers']) : undefined;
|
|
268
|
+
return modifiers === undefined ? undefined : { name, summary, target, modifiers };
|
|
244
269
|
}
|
|
245
270
|
switch (name) {
|
|
246
271
|
case 'type': {
|
|
@@ -635,12 +660,6 @@ function readProvenance(document) {
|
|
|
635
660
|
...(agent === undefined ? {} : { agent }),
|
|
636
661
|
};
|
|
637
662
|
}
|
|
638
|
-
function readBoundedText(value, maxChars) {
|
|
639
|
-
if (typeof value !== 'string' || value.trim() === '' || value.length > maxChars) {
|
|
640
|
-
return undefined;
|
|
641
|
-
}
|
|
642
|
-
return value;
|
|
643
|
-
}
|
|
644
663
|
/** One to `MAX_TRACE_UPLOAD_PATHS` verbatim paths, each a replay input like a typed value. */
|
|
645
664
|
function readInputPaths(value) {
|
|
646
665
|
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_TRACE_UPLOAD_PATHS)
|
package/dist/cli/feedback.js
CHANGED
|
@@ -107,7 +107,8 @@ async function latestRelease(fetchImpl) {
|
|
|
107
107
|
const response = await fetchImpl(LATEST_URL, { signal: AbortSignal.timeout(LATEST_TIMEOUT_MS) });
|
|
108
108
|
if (!response.ok)
|
|
109
109
|
return undefined;
|
|
110
|
-
const
|
|
110
|
+
const body = await response.json();
|
|
111
|
+
const version = typeof body === 'object' && body !== null && 'version' in body ? body.version : undefined;
|
|
111
112
|
return typeof version === 'string' && RELEASE.test(version) ? version : undefined;
|
|
112
113
|
}
|
|
113
114
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"@e2e-dev/web":"0.15.
|
|
1
|
+
{"@e2e-dev/web":"0.15.2","@e2e-dev/mobile":"0.12.3"}
|
package/dist/collect/select.js
CHANGED
|
@@ -592,7 +592,9 @@ function shardItem(pair) {
|
|
|
592
592
|
* come from as few files as possible; the slices differ in size by at most
|
|
593
593
|
* one item, the first shards taking the remainder. Applied after every other
|
|
594
594
|
* filter and the serial closure, before the setup tests are chosen, so each
|
|
595
|
-
* shard brings only the setups it needs.
|
|
595
|
+
* shard brings only the setups it needs. A skipped test is not an item: the
|
|
596
|
+
* shard holding the item before it in report order reports it, the first
|
|
597
|
+
* shard when none comes before, so the shards together report it once.
|
|
596
598
|
*/
|
|
597
599
|
function applyShard(pairs, shard) {
|
|
598
600
|
const items = shardItems(pairs);
|
|
@@ -601,8 +603,16 @@ function applyShard(pairs, shard) {
|
|
|
601
603
|
const from = (shard.index - 1) * size + Math.min(shard.index - 1, remainder);
|
|
602
604
|
const to = from + size + (shard.index - 1 < remainder ? 1 : 0);
|
|
603
605
|
const kept = new Set(items.slice(from, to));
|
|
606
|
+
let previousKept = shard.index === 1;
|
|
604
607
|
return pairs.map((pair) => {
|
|
605
|
-
if (pair.
|
|
608
|
+
if (pair.test.kind !== 'test')
|
|
609
|
+
return pair;
|
|
610
|
+
if (pair.disposition === 'filtered')
|
|
611
|
+
return pair;
|
|
612
|
+
if (pair.disposition === 'skip')
|
|
613
|
+
return previousKept ? pair : { ...pair, disposition: 'filtered' };
|
|
614
|
+
previousKept = kept.has(shardItem(pair));
|
|
615
|
+
if (previousKept)
|
|
606
616
|
return pair;
|
|
607
617
|
return { ...pair, disposition: 'filtered', skip: { cause: 'filtered', reason: FILTERED_REASON.shard } };
|
|
608
618
|
});
|
package/dist/config/agent.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export interface ResolvedModel {
|
|
|
22
22
|
readonly model: ModelInstance;
|
|
23
23
|
}
|
|
24
24
|
/** A resolved model as reports and diagnostics name it: the instance's `provider/model-id`. */
|
|
25
|
-
export declare function modelLabel(model: ResolvedModel): string;
|
|
25
|
+
export declare function modelLabel(model: Pick<ResolvedModel, 'provider' | 'id'>): string;
|
|
26
26
|
export interface ResolvedAgentConfig {
|
|
27
27
|
/**
|
|
28
28
|
* The custom brain `agent.act()` dispatches to (`agents.<name>.executor`);
|
package/dist/config/diagnose.js
CHANGED
|
@@ -8,6 +8,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { withHint } from '../internal/errors.js';
|
|
11
|
+
import { isPlainObject } from '../internal/options.js';
|
|
11
12
|
import { addDevDependencyCommand, detectPackageManager } from '../internal/package-manager.js';
|
|
12
13
|
import { didYouMean, suggest } from '../internal/suggest.js';
|
|
13
14
|
/** The runtime exports of `e2e`; a unit test keeps this list equal to the real module. */
|
|
@@ -104,9 +105,12 @@ function subpathHint(subpath, manifestPath) {
|
|
|
104
105
|
catch {
|
|
105
106
|
return '';
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
if (!isPlainObject(parsed))
|
|
109
|
+
return '';
|
|
110
|
+
const name = typeof parsed['name'] === 'string' ? parsed['name'] : path.basename(path.dirname(manifestPath));
|
|
111
|
+
const exported = parsed['exports'];
|
|
112
|
+
const entries = typeof exported === 'object' && exported !== null && !Array.isArray(exported)
|
|
113
|
+
? Object.keys(exported).filter((key) => key.startsWith('.'))
|
|
110
114
|
: ['.'];
|
|
111
115
|
const specifiers = entries.map((entry) => (entry === '.' ? name : `${name}/${entry.slice(2)}`));
|
|
112
116
|
const attempted = `${name}/${subpath.replace(/^\.\//, '')}`;
|
|
@@ -136,6 +140,8 @@ function nearestManifest(dir) {
|
|
|
136
140
|
if (existsSync(manifestPath)) {
|
|
137
141
|
try {
|
|
138
142
|
const parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
143
|
+
if (!isPlainObject(parsed))
|
|
144
|
+
return undefined;
|
|
139
145
|
const dependencies = new Set();
|
|
140
146
|
for (const block of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
141
147
|
const section = parsed[block];
|
package/dist/config/esm-hooks.js
CHANGED
|
@@ -89,13 +89,14 @@ function directoryMain(directory, view) {
|
|
|
89
89
|
const manifest = path.join(directory, 'package.json');
|
|
90
90
|
if (!view.isFile(manifest))
|
|
91
91
|
return undefined;
|
|
92
|
-
let
|
|
92
|
+
let parsed;
|
|
93
93
|
try {
|
|
94
|
-
|
|
94
|
+
parsed = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
95
95
|
}
|
|
96
96
|
catch {
|
|
97
97
|
return undefined;
|
|
98
98
|
}
|
|
99
|
+
const main = typeof parsed === 'object' && parsed !== null && 'main' in parsed ? parsed.main : undefined;
|
|
99
100
|
return typeof main === 'string' && main !== '' ? path.resolve(directory, main) : undefined;
|
|
100
101
|
}
|
|
101
102
|
/**
|
package/dist/config/resolve.js
CHANGED
|
@@ -555,8 +555,9 @@ function resolveProjectId(explicit, projectRoot) {
|
|
|
555
555
|
if (existsSync(packageJsonPath)) {
|
|
556
556
|
try {
|
|
557
557
|
const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
558
|
-
|
|
559
|
-
|
|
558
|
+
const name = isRecord(parsed) ? parsed['name'] : undefined;
|
|
559
|
+
if (typeof name === 'string' && name.length > 0)
|
|
560
|
+
return name;
|
|
560
561
|
}
|
|
561
562
|
catch {
|
|
562
563
|
// fall through to hashed identity
|
|
@@ -11,6 +11,8 @@ import remapping, {} from '@jridgewell/remapping';
|
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
13
|
import { InfrastructureError } from '../internal/errors.js';
|
|
14
|
+
import { obj } from '../internal/objects.js';
|
|
15
|
+
import { isPlainObject } from '../internal/options.js';
|
|
14
16
|
import { HOOKED_REQUIRE_KEY } from './compiled-files.js';
|
|
15
17
|
const ownRequire = createRequire(import.meta.url);
|
|
16
18
|
/** oxc's transformer and parser, loaded on first use. */
|
|
@@ -220,6 +222,53 @@ function withOwnHelpers(code, helpers, format) {
|
|
|
220
222
|
}
|
|
221
223
|
/** An inline source map at the end of `source`, as another loader earlier in the chain leaves one. */
|
|
222
224
|
const INLINE_MAP = /\n\/\/# sourceMappingURL=data:application\/json(?:;charset=utf-8)?;base64,([A-Za-z0-9+/=]+)\s*$/;
|
|
225
|
+
/** Whether `value` is an array whose every entry passes `is`. */
|
|
226
|
+
function isArrayOf(value, is) {
|
|
227
|
+
return Array.isArray(value) && value.every(is);
|
|
228
|
+
}
|
|
229
|
+
/** Whether `entry` is a string. */
|
|
230
|
+
function isString(entry) {
|
|
231
|
+
return typeof entry === 'string';
|
|
232
|
+
}
|
|
233
|
+
/** Whether `entry` is a string or null, as a source map's `sources` and `sourcesContent` hold. */
|
|
234
|
+
function isStringOrNull(entry) {
|
|
235
|
+
return entry === null || typeof entry === 'string';
|
|
236
|
+
}
|
|
237
|
+
/** Whether `entry` is an integer, as a source map's `ignoreList` holds. */
|
|
238
|
+
function isInteger(entry) {
|
|
239
|
+
return Number.isInteger(entry);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The source map an earlier loader inlined as base64 JSON, rebuilt from the
|
|
243
|
+
* fields composing it reads, or undefined when it is malformed. A malformed
|
|
244
|
+
* map is left out rather than failing the import, so positions lead to the
|
|
245
|
+
* code that loader handed over.
|
|
246
|
+
*/
|
|
247
|
+
function inlineSourceMap(base64) {
|
|
248
|
+
let parsed;
|
|
249
|
+
try {
|
|
250
|
+
parsed = JSON.parse(Buffer.from(base64, 'base64').toString('utf8'));
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
if (!isPlainObject(parsed))
|
|
256
|
+
return undefined;
|
|
257
|
+
const { mappings, sources } = parsed;
|
|
258
|
+
const names = parsed['names'] ?? [];
|
|
259
|
+
const sourceRoot = parsed['sourceRoot'] ?? undefined;
|
|
260
|
+
const sourcesContent = parsed['sourcesContent'] ?? undefined;
|
|
261
|
+
const ignoreList = parsed['ignoreList'] ?? parsed['x_google_ignoreList'] ?? undefined;
|
|
262
|
+
if (typeof mappings !== 'string' ||
|
|
263
|
+
!isArrayOf(sources, isStringOrNull) ||
|
|
264
|
+
!isArrayOf(names, isString) ||
|
|
265
|
+
(sourceRoot !== undefined && typeof sourceRoot !== 'string') ||
|
|
266
|
+
(sourcesContent !== undefined && !isArrayOf(sourcesContent, isStringOrNull)) ||
|
|
267
|
+
(ignoreList !== undefined && !isArrayOf(ignoreList, isInteger))) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
return { version: 3, mappings, sources, names, ...obj({ sourceRoot, sourcesContent, ignoreList }) };
|
|
271
|
+
}
|
|
223
272
|
/**
|
|
224
273
|
* `source`, the content of `file` as the next loader handed it over,
|
|
225
274
|
* compiled to JavaScript for its kind with `compilerOptions`, with an
|
|
@@ -247,7 +296,8 @@ export function compileTypeScript(file, source, kind, compilerOptions) {
|
|
|
247
296
|
({ code, mappings } = runnableCommonJs(file, code, mappings));
|
|
248
297
|
// Absolute, so a frame names the file without the loader's query, whatever characters its name holds.
|
|
249
298
|
const own = { version: 3, mappings, names: result.map?.names ?? [], sources: [pathToFileURL(file).href] };
|
|
250
|
-
const
|
|
251
|
-
const
|
|
299
|
+
const encoded = INLINE_MAP.exec(source)?.[1];
|
|
300
|
+
const incoming = encoded === undefined ? undefined : inlineSourceMap(encoded);
|
|
301
|
+
const map = incoming === undefined ? own : remapping([own, incoming], () => null);
|
|
252
302
|
return `${code}\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(map)).toString('base64')}\n`;
|
|
253
303
|
}
|
|
@@ -148,8 +148,9 @@ export interface SemanticNode {
|
|
|
148
148
|
* The node's test id, when the platform gives it one: the value of the
|
|
149
149
|
* project's test-id attribute on a document platform, an accessibility
|
|
150
150
|
* identifier on iOS, a resource id on Android, an automation id on a
|
|
151
|
-
* desktop. The `testId` query resolves against
|
|
152
|
-
*
|
|
151
|
+
* desktop. The `testId` query resolves against this field, so the harness
|
|
152
|
+
* never knows where a platform keeps it. An engine may also match the id
|
|
153
|
+
* without its platform's namespacing.
|
|
153
154
|
*/
|
|
154
155
|
readonly testId?: string;
|
|
155
156
|
/** What a field takes, deciding which secrets may fill it. */
|
package/dist/engine/manifest.js
CHANGED
|
@@ -155,7 +155,7 @@ export function defineEngine(spec) {
|
|
|
155
155
|
}
|
|
156
156
|
const name = spec.name;
|
|
157
157
|
if (typeof spec.version !== 'string' || spec.version.trim() === '') {
|
|
158
|
-
throw invalid(name, 'version must be a non-empty string; it is
|
|
158
|
+
throw invalid(name, 'version must be a non-empty string; it is recorded in reports and keys saved sessions');
|
|
159
159
|
}
|
|
160
160
|
if (spec.spiVersion !== ENGINE_SPI_VERSION) {
|
|
161
161
|
throw invalid(name, `declares spiVersion ${String(spec.spiVersion)}; this runner supports ${ENGINE_SPI_VERSION}`);
|
|
@@ -285,7 +285,7 @@ export function defineEngine(spec) {
|
|
|
285
285
|
handle['secrets'] = declaredSecrets(name, spec.secrets);
|
|
286
286
|
if (spec.session !== undefined)
|
|
287
287
|
handle['session'] = hookManifest(name, 'session', spec.session);
|
|
288
|
-
//
|
|
288
|
+
// oxlint-disable-next-line e2e/no-chained-type-assertions -- assembled key by key above, so the record is an Engine by construction
|
|
289
289
|
return Object.freeze({
|
|
290
290
|
...handle,
|
|
291
291
|
[engineBrand]: true,
|
package/dist/internal/errors.js
CHANGED
|
@@ -136,10 +136,10 @@ export function asEngineError(value) {
|
|
|
136
136
|
return value;
|
|
137
137
|
if (!(value instanceof Error) || value.name !== 'EngineError')
|
|
138
138
|
return undefined;
|
|
139
|
-
const code = value.code;
|
|
139
|
+
const code = 'code' in value ? value.code : undefined;
|
|
140
140
|
if (!isEngineErrorCode(code))
|
|
141
141
|
return undefined;
|
|
142
|
-
const retryable = value.retryable === true;
|
|
142
|
+
const retryable = 'retryable' in value && value.retryable === true;
|
|
143
143
|
if (retryable && !RETRYABLE_ENGINE_ERROR_CODES.has(code)) {
|
|
144
144
|
return { code: 'ENGINE_FAILURE', message: value.message, retryable: false };
|
|
145
145
|
}
|
|
@@ -178,8 +178,11 @@ const CATEGORIES = new Set([
|
|
|
178
178
|
*/
|
|
179
179
|
export function isE2EError(value) {
|
|
180
180
|
return (value instanceof Error &&
|
|
181
|
+
E2E_ERROR_MARKER in value &&
|
|
181
182
|
value[E2E_ERROR_MARKER] === true &&
|
|
183
|
+
'category' in value &&
|
|
182
184
|
CATEGORIES.has(value.category) &&
|
|
185
|
+
'code' in value &&
|
|
183
186
|
typeof value.code === 'string');
|
|
184
187
|
}
|
|
185
188
|
export class ConfigurationError extends E2EError {
|
package/dist/internal/source.js
CHANGED
|
@@ -35,6 +35,8 @@ export function userFrame(stack, projectRoot) {
|
|
|
35
35
|
if (match === null)
|
|
36
36
|
continue;
|
|
37
37
|
const [, location, line, column] = match;
|
|
38
|
+
if (location === undefined || line === undefined || column === undefined)
|
|
39
|
+
continue;
|
|
38
40
|
const withoutQuery = location.split('?')[0];
|
|
39
41
|
let file;
|
|
40
42
|
try {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** A model constructor's mark on a provider failure meaning the account cannot use the model, with the explanation a user reads; a well-known symbol, so it survives two loaded copies of the package. */
|
|
2
|
+
/** Marks `failure` as a model the account cannot use, with the explanation a user reads, and returns it. */
|
|
3
|
+
export declare function markUnservedModel<Failure extends Error>(failure: Failure, explanation: string): Failure;
|
|
4
|
+
/** The explanation `failure`, or the last attempt of its spent retry chain, was marked with; read by shape, before any SDK loaded. */
|
|
5
|
+
export declare function unservedModelExplanation(failure: unknown): string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** A model constructor's mark on a provider failure meaning the account cannot use the model, with the explanation a user reads; a well-known symbol, so it survives two loaded copies of the package. */
|
|
2
|
+
const UNSERVED_MODEL = Symbol.for('e2e.unserved-model.v1');
|
|
3
|
+
/** Marks `failure` as a model the account cannot use, with the explanation a user reads, and returns it. */
|
|
4
|
+
export function markUnservedModel(failure, explanation) {
|
|
5
|
+
Object.defineProperty(failure, UNSERVED_MODEL, { value: explanation });
|
|
6
|
+
return failure;
|
|
7
|
+
}
|
|
8
|
+
/** The explanation `failure`, or the last attempt of its spent retry chain, was marked with; read by shape, before any SDK loaded. */
|
|
9
|
+
export function unservedModelExplanation(failure) {
|
|
10
|
+
const attempt = lastAttempt(failure);
|
|
11
|
+
if (!(attempt instanceof Error))
|
|
12
|
+
return undefined;
|
|
13
|
+
const explanation = Reflect.get(attempt, UNSERVED_MODEL);
|
|
14
|
+
return typeof explanation === 'string' ? explanation : undefined;
|
|
15
|
+
}
|
|
16
|
+
/** The last attempt of a spent retry chain, else `failure` itself; a chain the deadline cut short stays whole. */
|
|
17
|
+
function lastAttempt(failure) {
|
|
18
|
+
if (!(failure instanceof Error) || failure.name !== 'AI_RetryError')
|
|
19
|
+
return failure;
|
|
20
|
+
const chain = failure;
|
|
21
|
+
return chain.reason !== 'abort' && chain.lastError !== undefined ? chain.lastError : failure;
|
|
22
|
+
}
|
package/dist/locator/screen.js
CHANGED
|
@@ -12,7 +12,7 @@ import { obj } from '../internal/objects.js';
|
|
|
12
12
|
import { isTextMatch, normalizeText } from '../internal/text.js';
|
|
13
13
|
import { attributeOf, denySecureRead, isNodeVisible, locatorDetails } from './engine.js';
|
|
14
14
|
import { describeExpression, filterExpression, indexExpression, roleQuery, testIdQuery, textQuery, } from './expression.js';
|
|
15
|
-
import { cutOffAtDeadline, isOperationTimeout, POLL_INTERVAL_MS, pollCondition
|
|
15
|
+
import { cutOffAtDeadline, isOperationTimeout, POLL_INTERVAL_MS, pollCondition } from '../internal/time.js';
|
|
16
16
|
import { SampleHistory } from '../expect/samples.js';
|
|
17
17
|
/**
|
|
18
18
|
* Internals hang off the locator object itself under a global symbol so that
|
|
@@ -194,7 +194,6 @@ class ScreenImpl {
|
|
|
194
194
|
}
|
|
195
195
|
throw cause;
|
|
196
196
|
}
|
|
197
|
-
await sleep(Math.min(POLL_INTERVAL_MS, deadline.remaining()), engine.signal);
|
|
198
197
|
}
|
|
199
198
|
});
|
|
200
199
|
}
|
package/dist/oauth/copilot.js
CHANGED
|
@@ -10,8 +10,9 @@ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
|
10
10
|
import { OAuthError } from './errors.js';
|
|
11
11
|
import { createOAuthFetch } from './fetch.js';
|
|
12
12
|
import { USER_AGENT } from '../internal/client-identity.js';
|
|
13
|
+
import { markUnservedModel } from '../internal/unserved-model.js';
|
|
13
14
|
import { loginHint } from './providers.js';
|
|
14
|
-
import { COPILOT_API_URL, copilotProtocolFor, createCopilotProvider } from './providers/github-copilot.js';
|
|
15
|
+
import { COPILOT_API_URL, copilotProtocolFor, copilotRefusal, createCopilotProvider, isCopilotRefusal } from './providers/github-copilot.js';
|
|
15
16
|
import { withoutServerStorage } from './responses.js';
|
|
16
17
|
import { defaultCredentialStore } from './store.js';
|
|
17
18
|
/** How long the first call waits on the model listing before it calls over chat and asks again next time. */
|
|
@@ -30,7 +31,7 @@ export function copilot(modelId) {
|
|
|
30
31
|
fetch,
|
|
31
32
|
includeUsage: true,
|
|
32
33
|
}).chatModel(modelId);
|
|
33
|
-
return protocolDelegate(chat, () => responsesModel(modelId, fetch), () => copilotProtocolFor(modelId, fetch, AbortSignal.timeout(LISTING_TIMEOUT_MS)));
|
|
34
|
+
return protocolDelegate(chat, () => responsesModel(modelId, fetch), () => copilotProtocolFor(modelId, fetch, AbortSignal.timeout(LISTING_TIMEOUT_MS)), modelId);
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* The Responses model for `modelId`. `@ai-sdk/openai` is imported only here,
|
|
@@ -52,8 +53,9 @@ async function responsesModel(modelId, fetch) {
|
|
|
52
53
|
* own timeout, so a caller that gives up never fails the others. Only a
|
|
53
54
|
* listing that was read is remembered: when it could not be read, that call
|
|
54
55
|
* goes over chat, the endpoint most models use, and the next call asks again.
|
|
56
|
+
* A call Copilot refuses with `model_not_supported` is marked for the runner.
|
|
55
57
|
*/
|
|
56
|
-
function protocolDelegate(chat, responses, lookup) {
|
|
58
|
+
function protocolDelegate(chat, responses, lookup, modelId) {
|
|
57
59
|
let resolved;
|
|
58
60
|
let pending;
|
|
59
61
|
const settle = () => {
|
|
@@ -70,6 +72,8 @@ function protocolDelegate(chat, responses, lookup) {
|
|
|
70
72
|
});
|
|
71
73
|
return pending;
|
|
72
74
|
};
|
|
75
|
+
/** The failure as the runner reads it: a refusal of the model to this account carries the explanation. */
|
|
76
|
+
const refused = (cause) => (isCopilotRefusal(cause) ? markUnservedModel(cause, copilotRefusal(modelId)) : cause);
|
|
73
77
|
return {
|
|
74
78
|
specificationVersion: chat.specificationVersion,
|
|
75
79
|
get provider() {
|
|
@@ -81,8 +85,22 @@ function protocolDelegate(chat, responses, lookup) {
|
|
|
81
85
|
get supportedUrls() {
|
|
82
86
|
return resolved?.supportedUrls ?? chat.supportedUrls;
|
|
83
87
|
},
|
|
84
|
-
doGenerate: async (options) =>
|
|
85
|
-
|
|
88
|
+
doGenerate: async (options) => {
|
|
89
|
+
try {
|
|
90
|
+
return await (await untilAborted(settle(), options.abortSignal)).doGenerate(options);
|
|
91
|
+
}
|
|
92
|
+
catch (cause) {
|
|
93
|
+
throw refused(cause);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
doStream: async (options) => {
|
|
97
|
+
try {
|
|
98
|
+
return await (await untilAborted(settle(), options.abortSignal)).doStream(options);
|
|
99
|
+
}
|
|
100
|
+
catch (cause) {
|
|
101
|
+
throw refused(cause);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
86
104
|
};
|
|
87
105
|
}
|
|
88
106
|
/** Waits for `promise` unless `signal` aborts first, leaving the shared promise running for other callers. */
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* `rfc8628Flow` is the standard shape itself, which GitHub and SpaceXAI both
|
|
6
6
|
* speak apart from the status code they answer "pending" with.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { OAuthError, describeResponse, readJsonBody } from './errors.js';
|
|
10
|
+
import { parseTokens, positiveSeconds, postForm } from './token-endpoint.js';
|
|
10
11
|
const DEFAULT_INTERVAL_S = 5;
|
|
11
12
|
const MIN_INTERVAL_MS = 1_000;
|
|
12
13
|
const SLOW_DOWN_INCREMENT_MS = 5_000;
|
|
@@ -69,6 +70,23 @@ function throwIfCancelled(signal) {
|
|
|
69
70
|
if (signal?.aborted)
|
|
70
71
|
throw new OAuthError('CANCELLED', 'the login was cancelled');
|
|
71
72
|
}
|
|
73
|
+
/** The device authorization response, as far as the flow reads it; the timing values stay raw for `positiveSeconds`. */
|
|
74
|
+
const DeviceCodeSchema = z.object({
|
|
75
|
+
device_code: z.string(),
|
|
76
|
+
user_code: z.string(),
|
|
77
|
+
verification_uri: z.string(),
|
|
78
|
+
verification_uri_complete: z.string().optional().catch(undefined),
|
|
79
|
+
expires_in: z.unknown().optional(),
|
|
80
|
+
interval: z.unknown().optional(),
|
|
81
|
+
});
|
|
82
|
+
/** The error fields of a poll that granted nothing; a field of another type, or a body that is not an object, names nothing. */
|
|
83
|
+
const DevicePollErrorSchema = z
|
|
84
|
+
.object({
|
|
85
|
+
error: z.string().optional().catch(undefined),
|
|
86
|
+
error_description: z.string().optional().catch(undefined),
|
|
87
|
+
interval: z.number().optional().catch(undefined),
|
|
88
|
+
})
|
|
89
|
+
.catch({});
|
|
72
90
|
/** The standard device flow; the poll reads the body before the status, since vendors disagree on the status for "pending". */
|
|
73
91
|
export function rfc8628Flow(options) {
|
|
74
92
|
const { vendor, clientId, callbacks } = options;
|
|
@@ -78,37 +96,35 @@ export function rfc8628Flow(options) {
|
|
|
78
96
|
const response = await postForm(options.deviceCodeUrl, { client_id: clientId, ...options.request }, signal);
|
|
79
97
|
if (!response.ok)
|
|
80
98
|
throw new OAuthError('FLOW_FAILED', `${vendor} device login could not start: ${await describeResponse(response)}`);
|
|
81
|
-
const
|
|
82
|
-
const { device_code, user_code, verification_uri, verification_uri_complete } = json;
|
|
83
|
-
if (typeof device_code !== 'string' || typeof user_code !== 'string' || typeof verification_uri !== 'string') {
|
|
84
|
-
throw new OAuthError('FLOW_FAILED', `the ${vendor} device code response is missing fields`);
|
|
85
|
-
}
|
|
99
|
+
const device = await readJsonBody(response, DeviceCodeSchema, `the ${vendor} device code response`, 'is missing fields');
|
|
86
100
|
return {
|
|
87
|
-
deviceCode: device_code,
|
|
88
|
-
userCode: user_code,
|
|
89
|
-
verificationUri: verification_uri,
|
|
90
|
-
...(
|
|
91
|
-
expiresIn: Number(
|
|
92
|
-
interval: Number(
|
|
101
|
+
deviceCode: device.device_code,
|
|
102
|
+
userCode: device.user_code,
|
|
103
|
+
verificationUri: device.verification_uri,
|
|
104
|
+
...(device.verification_uri_complete === undefined ? {} : { verificationUriComplete: device.verification_uri_complete }),
|
|
105
|
+
expiresIn: Number(device.expires_in),
|
|
106
|
+
interval: Number(device.interval),
|
|
93
107
|
};
|
|
94
108
|
},
|
|
95
109
|
async poll(authorization, signal) {
|
|
96
110
|
const response = await postForm(options.tokenUrl, { grant_type: 'urn:ietf:params:oauth:grant-type:device_code', client_id: clientId, device_code: authorization.deviceCode }, signal);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
const body = await response.json().catch(() => undefined);
|
|
112
|
+
const tokens = parseTokens(body);
|
|
113
|
+
if (tokens !== undefined)
|
|
114
|
+
return { status: 'granted', value: tokens };
|
|
115
|
+
const { error, error_description, interval } = DevicePollErrorSchema.parse(body);
|
|
116
|
+
switch (error) {
|
|
101
117
|
case 'authorization_pending':
|
|
102
118
|
return { status: 'pending' };
|
|
103
119
|
case 'slow_down':
|
|
104
|
-
return
|
|
120
|
+
return interval === undefined ? { status: 'slow_down' } : { status: 'slow_down', intervalSeconds: interval };
|
|
105
121
|
case 'access_denied':
|
|
106
122
|
case 'authorization_denied':
|
|
107
123
|
return { status: 'denied' };
|
|
108
124
|
case 'expired_token':
|
|
109
125
|
return { status: 'expired' };
|
|
110
126
|
default:
|
|
111
|
-
throw new OAuthError('FLOW_FAILED', `${vendor} device login failed: ${
|
|
127
|
+
throw new OAuthError('FLOW_FAILED', `${vendor} device login failed: ${error_description ?? error ?? response.status}`);
|
|
112
128
|
}
|
|
113
129
|
},
|
|
114
130
|
});
|
package/dist/oauth/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
export type OAuthErrorCode =
|
|
2
3
|
/** No stored credentials for the provider. */
|
|
3
4
|
'NOT_LOGGED_IN'
|
|
@@ -20,3 +21,5 @@ export declare class OAuthError extends Error {
|
|
|
20
21
|
}
|
|
21
22
|
/** Reads an error body for a message without assuming JSON. */
|
|
22
23
|
export declare function describeResponse(response: Response): Promise<string>;
|
|
24
|
+
/** A response's or request's JSON body checked against `schema`; FLOW_FAILED as `<subject> is not JSON` or `<subject> <invalid>` otherwise. */
|
|
25
|
+
export declare function readJsonBody<T>(source: Pick<Response, 'text'>, schema: z.ZodType<T>, subject: string, invalid?: string): Promise<T>;
|