dsh-browser-playwright-codex 0.5.1
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/CHANGELOG.md +486 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/SECURITY.md +114 -0
- package/cordis.patch.yml +66 -0
- package/lib/cdp-policy.d.ts +69 -0
- package/lib/cdp-policy.js +105 -0
- package/lib/compat.d.ts +37 -0
- package/lib/compat.js +31 -0
- package/lib/contract.d.ts +74 -0
- package/lib/contract.js +117 -0
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +21 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +12 -0
- package/lib/injected.d.ts +35 -0
- package/lib/injected.js +350 -0
- package/lib/playwright.d.ts +647 -0
- package/lib/playwright.js +2515 -0
- package/lib/runtime-state.d.ts +45 -0
- package/lib/runtime-state.js +181 -0
- package/lib/service.d.ts +56 -0
- package/lib/service.js +96 -0
- package/lib/snapshot-aria.d.ts +121 -0
- package/lib/snapshot-aria.js +485 -0
- package/lib/snapshot-render.d.ts +28 -0
- package/lib/snapshot-render.js +100 -0
- package/lib/tool.d.ts +77 -0
- package/lib/tool.js +907 -0
- package/lib/types.d.ts +242 -0
- package/lib/types.js +5 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +108 -0
- package/scripts/analysis/hl-compare.mjs +149 -0
- package/scripts/analysis/live-handoff-probe.mjs +97 -0
- package/scripts/analysis/needs-human-probe.mjs +83 -0
- package/scripts/analysis/repro-minimize.mjs +112 -0
- package/scripts/autocommit.ps1 +23 -0
- package/scripts/check-doc-links.py +112 -0
- package/scripts/doctor.mjs +144 -0
- package/scripts/dsh-browser-verify.mjs +475 -0
- package/scripts/measure-prompt-cost.mjs +57 -0
- package/scripts/prompt-cost.mjs +73 -0
- package/scripts/shipped-gates.mjs +43 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP allow-list policy for browser_cdp.
|
|
3
|
+
*
|
|
4
|
+
* Raw CDP is a kernel-level remote control: some commands are read-only
|
|
5
|
+
* inspection / input simulation (safe), while others mutate browser or
|
|
6
|
+
* network state (cookies, request interception, storage, security) — far
|
|
7
|
+
* beyond what an agent should reach through a web tool. This module answers
|
|
8
|
+
* one question: may an agent send this CDP method?
|
|
9
|
+
*
|
|
10
|
+
* Default-deny, by exact name. Every allowed entry is a full method name and
|
|
11
|
+
* matching is plain equality, so a method this list has never heard of — a
|
|
12
|
+
* future Chromium command, a forged suffix, a whole-domain prefix — is denied
|
|
13
|
+
* without anyone having to notice it was added. The earlier policy matched
|
|
14
|
+
* `DOM.get` / `DOM.query` as families, which quietly authorised nine more DOM
|
|
15
|
+
* calls than the reviewed list and would have authorised every future member of
|
|
16
|
+
* those families as well.
|
|
17
|
+
*
|
|
18
|
+
* What is allowed, and why:
|
|
19
|
+
* - DOM reads: geometry (getBoxModel, getContentQuads, getNodeForLocation),
|
|
20
|
+
* node lookup (querySelector[All], describeNode, resolveNode), markup and
|
|
21
|
+
* attribute reads (getOuterHTML, getAttributes) and the root tree.
|
|
22
|
+
* - One DOM action: focus. It moves focus, the same class of effect as the
|
|
23
|
+
* input events below, so it is a deliberate allow rather than a read.
|
|
24
|
+
* - Input simulation, four methods only: Input.dispatchMouseEvent,
|
|
25
|
+
* Input.dispatchKeyEvent, Input.dispatchTouchEvent, Input.insertText —
|
|
26
|
+
* CDP-native trusted events. The Input domain as a whole is NOT allowed:
|
|
27
|
+
* no family prefix, so Input.setIgnoreInputEvents, the gesture
|
|
28
|
+
* synthesizers and any future Input.* mutation stay denied by default.
|
|
29
|
+
* - Read-only helpers: Page.getLayoutMetrics, Page.getNavigationHistory,
|
|
30
|
+
* Page.getFrameTree, CSS.getComputedStyleForNode.
|
|
31
|
+
*
|
|
32
|
+
* Everything else — Network.*, Storage.*, Security.*, Fetch.*, Target.*,
|
|
33
|
+
* Browser.*, Emulation.*, Runtime.evaluate, Page.navigate,
|
|
34
|
+
* Page.getResourceContent (it reads any frame's resource body),
|
|
35
|
+
* DOM.setFileInputFiles, the debugger-internal DOM calls (getDetachedDomNodes,
|
|
36
|
+
* getNodeStackTraces, getRelayoutBoundary, the container-query and anchor
|
|
37
|
+
* helpers …) — is rejected with CDP_DENIED. Growing this list is meant to be
|
|
38
|
+
* work: add the name here, add it to the reviewed list in
|
|
39
|
+
* tests/cdp-policy.test.ts (which asserts the two are equal), and say why.
|
|
40
|
+
* @module dsh-browser-playwright-codex/cdp-policy
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* The allowed CDP methods, as exact names. Keep this explicit and reviewable:
|
|
44
|
+
* nothing here may be a family or domain prefix, and every entry must also
|
|
45
|
+
* appear in the reviewed list in tests/cdp-policy.test.ts.
|
|
46
|
+
*
|
|
47
|
+
* Frozen as a speed bump against accidental in-place edits at runtime. It is
|
|
48
|
+
* not a security boundary — the boundary is the matching rule below plus the
|
|
49
|
+
* tests that pin it; freezing only stops a typo from widening the list.
|
|
50
|
+
*/
|
|
51
|
+
export const CDP_ALLOWED_METHODS = Object.freeze([
|
|
52
|
+
// DOM reads.
|
|
53
|
+
'DOM.getDocument',
|
|
54
|
+
'DOM.getBoxModel',
|
|
55
|
+
'DOM.getNodeForLocation',
|
|
56
|
+
'DOM.getContentQuads',
|
|
57
|
+
'DOM.getOuterHTML',
|
|
58
|
+
'DOM.getAttributes',
|
|
59
|
+
'DOM.querySelector',
|
|
60
|
+
'DOM.querySelectorAll',
|
|
61
|
+
'DOM.describeNode',
|
|
62
|
+
'DOM.resolveNode',
|
|
63
|
+
// The one allowed DOM action (moves focus).
|
|
64
|
+
'DOM.focus',
|
|
65
|
+
// Input simulation: method-explicit, no family prefix.
|
|
66
|
+
'Input.dispatchMouseEvent',
|
|
67
|
+
'Input.dispatchKeyEvent',
|
|
68
|
+
'Input.dispatchTouchEvent',
|
|
69
|
+
'Input.insertText',
|
|
70
|
+
// Read-only page layout / history helpers and computed style.
|
|
71
|
+
'Page.getLayoutMetrics',
|
|
72
|
+
'Page.getNavigationHistory',
|
|
73
|
+
'Page.getFrameTree',
|
|
74
|
+
'CSS.getComputedStyleForNode',
|
|
75
|
+
]);
|
|
76
|
+
/** Human summary for the tool description and denial diagnostics. */
|
|
77
|
+
export const CDP_ALLOW_SUMMARY = 'DOM inspection (geometry / node lookup / attributes / markup) plus the four trusted input events (Input.dispatchMouseEvent / Input.dispatchKeyEvent / Input.dispatchTouchEvent / Input.insertText) and read-only Page / CSS helpers, matched by exact name with no family prefixes';
|
|
78
|
+
/**
|
|
79
|
+
* Decide whether an agent may send the given CDP method.
|
|
80
|
+
*
|
|
81
|
+
* Exact equality only. The shape check comes first, so a non-string (an object
|
|
82
|
+
* with a toString, a number, a null) is denied without ever being evaluated.
|
|
83
|
+
* There is deliberately no prefix, family or suffix rule: a name that is not
|
|
84
|
+
* written out above does not pass, no matter who adds it to Chromium.
|
|
85
|
+
* @param method - CDP method name, e.g. "DOM.getNodeForLocation".
|
|
86
|
+
* @returns true when the method is exactly one of the allowed names.
|
|
87
|
+
*/
|
|
88
|
+
export function isCdpMethodAllowed(method) {
|
|
89
|
+
if (typeof method !== 'string' || method.length === 0)
|
|
90
|
+
return false;
|
|
91
|
+
return CDP_ALLOWED_METHODS.includes(method);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Describe a denied method for the error message: name the allow-list family
|
|
95
|
+
* so the model learns the boundary instead of guessing.
|
|
96
|
+
*/
|
|
97
|
+
export function cdpDenialMessage(method) {
|
|
98
|
+
return ('browser_cdp method ' +
|
|
99
|
+
JSON.stringify(method) +
|
|
100
|
+
' is not in the allow-list (' +
|
|
101
|
+
CDP_ALLOW_SUMMARY +
|
|
102
|
+
'). ' +
|
|
103
|
+
'If you need it, it is intentionally blocked: raw CDP network/storage/security commands can mutate cookies, ' +
|
|
104
|
+
'intercept traffic or read stored data, which the agent must not do through this tool.');
|
|
105
|
+
}
|
package/lib/compat.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boundary adapter for the branded identifiers this plugin borrows from the
|
|
3
|
+
* DeepSeek Harness. The harness ships a release every few days and renames
|
|
4
|
+
* freely — `CallId` became `ToolCallId`, brand constructors moved to
|
|
5
|
+
* `@deepseek-ai/dsh-brand` — and a renamed identifier used to break an import
|
|
6
|
+
* in the middle of the tests.
|
|
7
|
+
*
|
|
8
|
+
* Scope, stated honestly: this file owns the *identifier brands* (below).
|
|
9
|
+
* The harness API surface this plugin uses on purpose (cordis `Context`,
|
|
10
|
+
* `defineTool` / `ToolExecution`, `BlockAssembler`, `createUserMessage`) is
|
|
11
|
+
* still imported directly at its call site — a rename there is a load-time
|
|
12
|
+
* failure that `npm run doctor` reports as a missing export, which is the
|
|
13
|
+
* signal we want rather than a silent `undefined`.
|
|
14
|
+
*
|
|
15
|
+
* `CallId` / `ToolCallId` are nominal wrappers over a string whose runtime body
|
|
16
|
+
* is the identity function, so this plugin defines its own instead of importing
|
|
17
|
+
* theirs — a harness rename cannot break it, and no version range has to be
|
|
18
|
+
* chased.
|
|
19
|
+
* @module dsh-browser-playwright-codex/compat
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Correlates one model-issued tool call with its result. Structurally
|
|
23
|
+
* compatible with the harness brand, owned by this plugin so the harness
|
|
24
|
+
* cannot rename it out from under the tests.
|
|
25
|
+
*/
|
|
26
|
+
export type ToolCallId = string & {
|
|
27
|
+
readonly __brand?: 'ToolCallId';
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Brand a string as a {@link ToolCallId}. Runtime identity, exactly like the
|
|
31
|
+
* harness brand constructor it replaces.
|
|
32
|
+
* @param id - the provider-issued or synthesized call id.
|
|
33
|
+
* @returns the same string, branded.
|
|
34
|
+
*/
|
|
35
|
+
export declare function toolCallId(id: string): ToolCallId;
|
|
36
|
+
/** Historical alias: the 0.1.6-and-earlier name, kept so call sites read the same. */
|
|
37
|
+
export declare const CallId: typeof toolCallId;
|
package/lib/compat.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boundary adapter for the branded identifiers this plugin borrows from the
|
|
3
|
+
* DeepSeek Harness. The harness ships a release every few days and renames
|
|
4
|
+
* freely — `CallId` became `ToolCallId`, brand constructors moved to
|
|
5
|
+
* `@deepseek-ai/dsh-brand` — and a renamed identifier used to break an import
|
|
6
|
+
* in the middle of the tests.
|
|
7
|
+
*
|
|
8
|
+
* Scope, stated honestly: this file owns the *identifier brands* (below).
|
|
9
|
+
* The harness API surface this plugin uses on purpose (cordis `Context`,
|
|
10
|
+
* `defineTool` / `ToolExecution`, `BlockAssembler`, `createUserMessage`) is
|
|
11
|
+
* still imported directly at its call site — a rename there is a load-time
|
|
12
|
+
* failure that `npm run doctor` reports as a missing export, which is the
|
|
13
|
+
* signal we want rather than a silent `undefined`.
|
|
14
|
+
*
|
|
15
|
+
* `CallId` / `ToolCallId` are nominal wrappers over a string whose runtime body
|
|
16
|
+
* is the identity function, so this plugin defines its own instead of importing
|
|
17
|
+
* theirs — a harness rename cannot break it, and no version range has to be
|
|
18
|
+
* chased.
|
|
19
|
+
* @module dsh-browser-playwright-codex/compat
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Brand a string as a {@link ToolCallId}. Runtime identity, exactly like the
|
|
23
|
+
* harness brand constructor it replaces.
|
|
24
|
+
* @param id - the provider-issued or synthesized call id.
|
|
25
|
+
* @returns the same string, branded.
|
|
26
|
+
*/
|
|
27
|
+
export function toolCallId(id) {
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
/** Historical alias: the 0.1.6-and-earlier name, kept so call sites read the same. */
|
|
31
|
+
export const CallId = toolCallId;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser capability's wire contract in one place: the tool names it
|
|
3
|
+
* registers and the failure codes it can raise. Registration, the load-time
|
|
4
|
+
* guard, the regression suites and the standalone verify script all read this
|
|
5
|
+
* module, so a renamed tool or a changed code cannot drift apart silently.
|
|
6
|
+
* @module dsh-browser-playwright-codex/contract
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Tool name suffixes in registration order; a wire name is `prefix + suffix`.
|
|
10
|
+
* Gated suffixes are kept in the same list so the full surface stays visible.
|
|
11
|
+
*/
|
|
12
|
+
export declare const TOOL_SUFFIXES: readonly ["navigate", "snapshot", "click", "dialog", "click_at", "hover", "fill", "press", "scroll", "back", "forward", "wait", "tabs", "switch_tab", "open_tab", "close_tab", "screenshot", "extract", "evaluate", "cdp", "console_messages", "network_requests", "close"];
|
|
13
|
+
/** One tool name suffix. */
|
|
14
|
+
export type ToolSuffix = (typeof TOOL_SUFFIXES)[number];
|
|
15
|
+
/**
|
|
16
|
+
* Suffixes registered unconditionally. They are always in the system prompt,
|
|
17
|
+
* so this list is also the resident schema cost of the plugin.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ALWAYS_ON_SUFFIXES: readonly ["navigate", "snapshot", "click", "dialog", "click_at", "hover", "fill", "press", "scroll", "back", "forward", "wait", "tabs", "switch_tab", "open_tab", "close_tab", "screenshot", "console_messages", "network_requests", "close"];
|
|
20
|
+
/**
|
|
21
|
+
* Suffixes registered only when their capability is enabled (or configured):
|
|
22
|
+
* a tool the deployment disabled can only error, so shipping its schema would
|
|
23
|
+
* spend prompt tokens on a capability that cannot run.
|
|
24
|
+
*/
|
|
25
|
+
export declare const GATED_SUFFIXES: readonly ["extract", "evaluate", "cdp"];
|
|
26
|
+
/** Which gated capabilities are live for one configuration. */
|
|
27
|
+
export interface ToolSurfaceConfig {
|
|
28
|
+
/** allowEvaluate: registers browser_evaluate when true. */
|
|
29
|
+
allowEvaluate: boolean;
|
|
30
|
+
/** allowCdp: registers browser_cdp when true. */
|
|
31
|
+
allowCdp: boolean;
|
|
32
|
+
/** extract.provider and extract.model both set: registers browser_extract. */
|
|
33
|
+
extractConfigured: boolean;
|
|
34
|
+
/** Force every gated tool to register even when its capability is off. */
|
|
35
|
+
registerDisabledTools: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Full wire names for every tool, in registration order. */
|
|
38
|
+
export declare function toolNames(prefix: string): string[];
|
|
39
|
+
/** Suffixes this configuration actually registers, in registration order. */
|
|
40
|
+
export declare function registeredSuffixes(config: ToolSurfaceConfig): ToolSuffix[];
|
|
41
|
+
/** Full wire names this configuration registers, in registration order. */
|
|
42
|
+
export declare function registeredToolNames(prefix: string, config: ToolSurfaceConfig): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Stable machine-readable browser failure codes. Model-visible text never
|
|
45
|
+
* keys off these strings, so tests and tools can rely on them as an API.
|
|
46
|
+
*/
|
|
47
|
+
export declare const ERROR_CODES: {
|
|
48
|
+
readonly NO_PROVIDER: "NO_PROVIDER";
|
|
49
|
+
readonly AMBIGUOUS_PROVIDER: "AMBIGUOUS_PROVIDER";
|
|
50
|
+
readonly CONFIGURED_PROVIDER_MISSING: "CONFIGURED_PROVIDER_MISSING";
|
|
51
|
+
readonly CONFIGURED_PROVIDER_UNAVAILABLE: "CONFIGURED_PROVIDER_UNAVAILABLE";
|
|
52
|
+
readonly NO_BROWSER: "NO_BROWSER";
|
|
53
|
+
readonly BROWSER_LAUNCH_FAILED: "BROWSER_LAUNCH_FAILED";
|
|
54
|
+
readonly NAVIGATION_TIMEOUT: "NAVIGATION_TIMEOUT";
|
|
55
|
+
readonly ACTION_TIMEOUT: "ACTION_TIMEOUT";
|
|
56
|
+
readonly REF_NOT_FOUND: "REF_NOT_FOUND";
|
|
57
|
+
readonly URL_NOT_ALLOWED: "URL_NOT_ALLOWED";
|
|
58
|
+
readonly EVALUATE_DISABLED: "EVALUATE_DISABLED";
|
|
59
|
+
readonly SESSION_CLOSED: "SESSION_CLOSED";
|
|
60
|
+
readonly DISABLED: "DISABLED";
|
|
61
|
+
readonly BROWSER_CRASHED: "BROWSER_CRASHED";
|
|
62
|
+
readonly PARAM_MISSING: "PARAM_MISSING";
|
|
63
|
+
readonly ELEMENT_NOT_ACTIONABLE: "ELEMENT_NOT_ACTIONABLE";
|
|
64
|
+
readonly VALUE_NOT_SET: "VALUE_NOT_SET";
|
|
65
|
+
readonly CLICK_TARGET_NOT_FOUND: "CLICK_TARGET_NOT_FOUND";
|
|
66
|
+
readonly HOVER_TARGET_NOT_FOUND: "HOVER_TARGET_NOT_FOUND";
|
|
67
|
+
readonly DIALOG_PENDING: "DIALOG_PENDING";
|
|
68
|
+
readonly NO_DIALOG: "NO_DIALOG";
|
|
69
|
+
readonly CDP_DISABLED: "CDP_DISABLED";
|
|
70
|
+
readonly CDP_DENIED: "CDP_DENIED";
|
|
71
|
+
readonly CDP_ERROR: "CDP_ERROR";
|
|
72
|
+
};
|
|
73
|
+
/** One machine-readable failure code. */
|
|
74
|
+
export type BrowserErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
package/lib/contract.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser capability's wire contract in one place: the tool names it
|
|
3
|
+
* registers and the failure codes it can raise. Registration, the load-time
|
|
4
|
+
* guard, the regression suites and the standalone verify script all read this
|
|
5
|
+
* module, so a renamed tool or a changed code cannot drift apart silently.
|
|
6
|
+
* @module dsh-browser-playwright-codex/contract
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Tool name suffixes in registration order; a wire name is `prefix + suffix`.
|
|
10
|
+
* Gated suffixes are kept in the same list so the full surface stays visible.
|
|
11
|
+
*/
|
|
12
|
+
export const TOOL_SUFFIXES = [
|
|
13
|
+
'navigate',
|
|
14
|
+
'snapshot',
|
|
15
|
+
'click',
|
|
16
|
+
'dialog',
|
|
17
|
+
'click_at',
|
|
18
|
+
'hover',
|
|
19
|
+
'fill',
|
|
20
|
+
'press',
|
|
21
|
+
'scroll',
|
|
22
|
+
'back',
|
|
23
|
+
'forward',
|
|
24
|
+
'wait',
|
|
25
|
+
'tabs',
|
|
26
|
+
'switch_tab',
|
|
27
|
+
'open_tab',
|
|
28
|
+
'close_tab',
|
|
29
|
+
'screenshot',
|
|
30
|
+
'extract',
|
|
31
|
+
'evaluate',
|
|
32
|
+
'cdp',
|
|
33
|
+
'console_messages',
|
|
34
|
+
'network_requests',
|
|
35
|
+
'close',
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Suffixes registered unconditionally. They are always in the system prompt,
|
|
39
|
+
* so this list is also the resident schema cost of the plugin.
|
|
40
|
+
*/
|
|
41
|
+
export const ALWAYS_ON_SUFFIXES = [
|
|
42
|
+
'navigate',
|
|
43
|
+
'snapshot',
|
|
44
|
+
'click',
|
|
45
|
+
'dialog',
|
|
46
|
+
'click_at',
|
|
47
|
+
'hover',
|
|
48
|
+
'fill',
|
|
49
|
+
'press',
|
|
50
|
+
'scroll',
|
|
51
|
+
'back',
|
|
52
|
+
'forward',
|
|
53
|
+
'wait',
|
|
54
|
+
'tabs',
|
|
55
|
+
'switch_tab',
|
|
56
|
+
'open_tab',
|
|
57
|
+
'close_tab',
|
|
58
|
+
'screenshot',
|
|
59
|
+
'console_messages',
|
|
60
|
+
'network_requests',
|
|
61
|
+
'close',
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* Suffixes registered only when their capability is enabled (or configured):
|
|
65
|
+
* a tool the deployment disabled can only error, so shipping its schema would
|
|
66
|
+
* spend prompt tokens on a capability that cannot run.
|
|
67
|
+
*/
|
|
68
|
+
export const GATED_SUFFIXES = ['extract', 'evaluate', 'cdp'];
|
|
69
|
+
/** Full wire names for every tool, in registration order. */
|
|
70
|
+
export function toolNames(prefix) {
|
|
71
|
+
return TOOL_SUFFIXES.map((suffix) => prefix + suffix);
|
|
72
|
+
}
|
|
73
|
+
/** Suffixes this configuration actually registers, in registration order. */
|
|
74
|
+
export function registeredSuffixes(config) {
|
|
75
|
+
const live = new Set(ALWAYS_ON_SUFFIXES);
|
|
76
|
+
if (config.allowEvaluate || config.registerDisabledTools)
|
|
77
|
+
live.add('evaluate');
|
|
78
|
+
if (config.allowCdp || config.registerDisabledTools)
|
|
79
|
+
live.add('cdp');
|
|
80
|
+
if (config.extractConfigured || config.registerDisabledTools)
|
|
81
|
+
live.add('extract');
|
|
82
|
+
return TOOL_SUFFIXES.filter((suffix) => live.has(suffix));
|
|
83
|
+
}
|
|
84
|
+
/** Full wire names this configuration registers, in registration order. */
|
|
85
|
+
export function registeredToolNames(prefix, config) {
|
|
86
|
+
return registeredSuffixes(config).map((suffix) => prefix + suffix);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Stable machine-readable browser failure codes. Model-visible text never
|
|
90
|
+
* keys off these strings, so tests and tools can rely on them as an API.
|
|
91
|
+
*/
|
|
92
|
+
export const ERROR_CODES = {
|
|
93
|
+
NO_PROVIDER: 'NO_PROVIDER',
|
|
94
|
+
AMBIGUOUS_PROVIDER: 'AMBIGUOUS_PROVIDER',
|
|
95
|
+
CONFIGURED_PROVIDER_MISSING: 'CONFIGURED_PROVIDER_MISSING',
|
|
96
|
+
CONFIGURED_PROVIDER_UNAVAILABLE: 'CONFIGURED_PROVIDER_UNAVAILABLE',
|
|
97
|
+
NO_BROWSER: 'NO_BROWSER',
|
|
98
|
+
BROWSER_LAUNCH_FAILED: 'BROWSER_LAUNCH_FAILED',
|
|
99
|
+
NAVIGATION_TIMEOUT: 'NAVIGATION_TIMEOUT',
|
|
100
|
+
ACTION_TIMEOUT: 'ACTION_TIMEOUT',
|
|
101
|
+
REF_NOT_FOUND: 'REF_NOT_FOUND',
|
|
102
|
+
URL_NOT_ALLOWED: 'URL_NOT_ALLOWED',
|
|
103
|
+
EVALUATE_DISABLED: 'EVALUATE_DISABLED',
|
|
104
|
+
SESSION_CLOSED: 'SESSION_CLOSED',
|
|
105
|
+
DISABLED: 'DISABLED',
|
|
106
|
+
BROWSER_CRASHED: 'BROWSER_CRASHED',
|
|
107
|
+
PARAM_MISSING: 'PARAM_MISSING',
|
|
108
|
+
ELEMENT_NOT_ACTIONABLE: 'ELEMENT_NOT_ACTIONABLE',
|
|
109
|
+
VALUE_NOT_SET: 'VALUE_NOT_SET',
|
|
110
|
+
CLICK_TARGET_NOT_FOUND: 'CLICK_TARGET_NOT_FOUND',
|
|
111
|
+
HOVER_TARGET_NOT_FOUND: 'HOVER_TARGET_NOT_FOUND',
|
|
112
|
+
DIALOG_PENDING: 'DIALOG_PENDING',
|
|
113
|
+
NO_DIALOG: 'NO_DIALOG',
|
|
114
|
+
CDP_DISABLED: 'CDP_DISABLED',
|
|
115
|
+
CDP_DENIED: 'CDP_DENIED',
|
|
116
|
+
CDP_ERROR: 'CDP_ERROR',
|
|
117
|
+
};
|
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy for the browser capability. Every failure the model can
|
|
3
|
+
* encounter carries a stable, provider-neutral code plus a human-readable
|
|
4
|
+
* message; tools translate these onto the wire without exposing internals.
|
|
5
|
+
* The code list lives in `contract.ts` so registration, tests and the verify
|
|
6
|
+
* script share one source of truth.
|
|
7
|
+
* @module dsh-browser-playwright-codex/errors
|
|
8
|
+
*/
|
|
9
|
+
import type { BrowserErrorCode } from './contract.ts';
|
|
10
|
+
/** Stable machine-readable browser failure codes. */
|
|
11
|
+
export type { BrowserErrorCode };
|
|
12
|
+
/**
|
|
13
|
+
* Error thrown by every browser-capability operation. Tools surface the
|
|
14
|
+
* code as structured diagnostics; the message stays model-visible prose.
|
|
15
|
+
*/
|
|
16
|
+
export declare class BrowserError extends Error {
|
|
17
|
+
readonly code: BrowserErrorCode;
|
|
18
|
+
/**
|
|
19
|
+
* @param code - stable machine-readable failure code.
|
|
20
|
+
* @param message - non-empty human-readable failure summary.
|
|
21
|
+
*/
|
|
22
|
+
constructor(code: BrowserErrorCode, message: string);
|
|
23
|
+
}
|
|
24
|
+
/** Wrap an unknown cause into a launch failure with the code preserved. */
|
|
25
|
+
export declare function launchFailed(cause: unknown): BrowserError;
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown by every browser-capability operation. Tools surface the
|
|
3
|
+
* code as structured diagnostics; the message stays model-visible prose.
|
|
4
|
+
*/
|
|
5
|
+
export class BrowserError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
/**
|
|
8
|
+
* @param code - stable machine-readable failure code.
|
|
9
|
+
* @param message - non-empty human-readable failure summary.
|
|
10
|
+
*/
|
|
11
|
+
constructor(code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'BrowserError';
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Wrap an unknown cause into a launch failure with the code preserved. */
|
|
18
|
+
export function launchFailed(cause) {
|
|
19
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
20
|
+
return new BrowserError('BROWSER_LAUNCH_FAILED', 'failed to launch the browser: ' + detail);
|
|
21
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-browser-playwright-codex: Playwright-powered browser capability for
|
|
3
|
+
* DeepSeek Harness. Snapshot-first interaction with stable element refs,
|
|
4
|
+
* session-scoped browser contexts, screenshots as attachments.
|
|
5
|
+
* @module dsh-browser-playwright-codex
|
|
6
|
+
*/
|
|
7
|
+
export { BrowserError, launchFailed } from './errors.ts';
|
|
8
|
+
export type { BrowserErrorCode } from './errors.ts';
|
|
9
|
+
export { ALWAYS_ON_SUFFIXES, ERROR_CODES, GATED_SUFFIXES, TOOL_SUFFIXES, registeredSuffixes, registeredToolNames, toolNames, } from './contract.ts';
|
|
10
|
+
export type { ToolSuffix, ToolSurfaceConfig } from './contract.ts';
|
|
11
|
+
export { CallId, toolCallId } from './compat.ts';
|
|
12
|
+
export type { ToolCallId } from './compat.ts';
|
|
13
|
+
export { SNAPSHOT_SCRIPT } from './injected.ts';
|
|
14
|
+
export type { SnapshotOptions, PageDataOptions } from './injected.ts';
|
|
15
|
+
export { default as BrowserRuntime } from './service.ts';
|
|
16
|
+
export type { BrowserRuntimeConfig } from './service.ts';
|
|
17
|
+
export { PlaywrightProvider, assertAllowedUrl, Config as PlaywrightConfigSchema } from './playwright.ts';
|
|
18
|
+
export type { PlaywrightConfig, PageData } from './playwright.ts';
|
|
19
|
+
export type { BrowserNode, BrowserProvider, BrowserSession, BrowserSnapshot, LoadState, ScreenshotCapture, TabInfo, } from './types.ts';
|
|
20
|
+
export type { ToolConfig } from './tool.ts';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-browser-playwright-codex: Playwright-powered browser capability for
|
|
3
|
+
* DeepSeek Harness. Snapshot-first interaction with stable element refs,
|
|
4
|
+
* session-scoped browser contexts, screenshots as attachments.
|
|
5
|
+
* @module dsh-browser-playwright-codex
|
|
6
|
+
*/
|
|
7
|
+
export { BrowserError, launchFailed } from "./errors.js";
|
|
8
|
+
export { ALWAYS_ON_SUFFIXES, ERROR_CODES, GATED_SUFFIXES, TOOL_SUFFIXES, registeredSuffixes, registeredToolNames, toolNames, } from "./contract.js";
|
|
9
|
+
export { CallId, toolCallId } from "./compat.js";
|
|
10
|
+
export { SNAPSHOT_SCRIPT } from "./injected.js";
|
|
11
|
+
export { default as BrowserRuntime } from "./service.js";
|
|
12
|
+
export { PlaywrightProvider, assertAllowedUrl, Config as PlaywrightConfigSchema } from "./playwright.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected page-context snapshot engine, installed through addInitScript.
|
|
3
|
+
* It is plain JavaScript: it runs inside the page, never in Node. Each
|
|
4
|
+
* snapshot call assigns stable data-dsh-ref attributes to actionable
|
|
5
|
+
* elements in document order and returns a pruned role/name tree. Names come
|
|
6
|
+
* from the element itself first, then from its descendants for actionable
|
|
7
|
+
* elements whose own text is empty (so a label one level down still lands on
|
|
8
|
+
* the line that carries the ref). A child that merely repeats an ancestor
|
|
9
|
+
* name is folded, and container roles (select options, table rows) are never
|
|
10
|
+
* named from their content.
|
|
11
|
+
* @module dsh-browser-playwright-codex/injected
|
|
12
|
+
*/
|
|
13
|
+
/** Options passed from the provider into one snapshot call. */
|
|
14
|
+
export interface SnapshotOptions {
|
|
15
|
+
/** Include only actionable (ref-carrying) elements. */
|
|
16
|
+
readonly interactiveOnly?: boolean;
|
|
17
|
+
/** Maximum nodes in the returned tree. */
|
|
18
|
+
readonly maxNodes?: number;
|
|
19
|
+
/** Maximum characters kept per accessible name. */
|
|
20
|
+
readonly maxNameLength?: number;
|
|
21
|
+
/** Maximum characters kept per text block. */
|
|
22
|
+
readonly maxTextLength?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Options for the bounded page-data extraction. */
|
|
25
|
+
export interface PageDataOptions {
|
|
26
|
+
readonly maxTextChars?: number;
|
|
27
|
+
readonly maxLinks?: number;
|
|
28
|
+
readonly maxInputs?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The browser-side snapshot script. Installed with page.addInitScript and
|
|
32
|
+
* invoked as window.__dshSnapshot(opts). The returned value crosses the
|
|
33
|
+
* evaluate boundary as plain JSON.
|
|
34
|
+
*/
|
|
35
|
+
export declare const SNAPSHOT_SCRIPT: string;
|