dsh-browser-playwright-codex 0.6.6 → 0.7.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 +39 -0
- package/README.md +70 -57
- package/SECURITY.md +6 -3
- package/cordis.patch.yml +76 -68
- package/lib/client.js +604 -0
- package/lib/contract.d.ts +5 -2
- package/lib/contract.js +12 -3
- package/lib/gates-patch.d.ts +49 -0
- package/lib/gates-patch.js +168 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +6 -0
- package/lib/page-actions.d.ts +16 -0
- package/lib/page-actions.js +43 -1
- package/lib/plugin.d.ts +56 -0
- package/lib/plugin.js +31 -0
- package/lib/runtime-state.d.ts +2 -0
- package/lib/runtime-state.js +5 -1
- package/lib/settings-card.d.ts +9 -0
- package/lib/settings-card.js +275 -0
- package/lib/snapshot-render.d.ts +12 -0
- package/lib/snapshot-render.js +99 -1
- package/lib/tool.d.ts +6 -0
- package/lib/tool.js +63 -51
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +27 -15
package/lib/contract.js
CHANGED
|
@@ -52,8 +52,6 @@ export const ALWAYS_ON_SUFFIXES = [
|
|
|
52
52
|
'open_tab',
|
|
53
53
|
'close_tab',
|
|
54
54
|
'screenshot',
|
|
55
|
-
'console_messages',
|
|
56
|
-
'network_requests',
|
|
57
55
|
'close',
|
|
58
56
|
];
|
|
59
57
|
/**
|
|
@@ -61,7 +59,13 @@ export const ALWAYS_ON_SUFFIXES = [
|
|
|
61
59
|
* a tool the deployment disabled can only error, so shipping its schema would
|
|
62
60
|
* spend prompt tokens on a capability that cannot run.
|
|
63
61
|
*/
|
|
64
|
-
export const GATED_SUFFIXES = [
|
|
62
|
+
export const GATED_SUFFIXES = [
|
|
63
|
+
'extract',
|
|
64
|
+
'evaluate',
|
|
65
|
+
'cdp',
|
|
66
|
+
'console_messages',
|
|
67
|
+
'network_requests',
|
|
68
|
+
];
|
|
65
69
|
/** Full wire names for every tool, in registration order. */
|
|
66
70
|
export function toolNames(prefix) {
|
|
67
71
|
return TOOL_SUFFIXES.map((suffix) => prefix + suffix);
|
|
@@ -75,6 +79,10 @@ export function registeredSuffixes(config) {
|
|
|
75
79
|
live.add('cdp');
|
|
76
80
|
if (config.extractConfigured || config.registerDisabledTools)
|
|
77
81
|
live.add('extract');
|
|
82
|
+
if (config.diagnostics || config.registerDisabledTools) {
|
|
83
|
+
live.add('console_messages');
|
|
84
|
+
live.add('network_requests');
|
|
85
|
+
}
|
|
78
86
|
return TOOL_SUFFIXES.filter((suffix) => live.has(suffix));
|
|
79
87
|
}
|
|
80
88
|
/** Full wire names this configuration registers, in registration order. */
|
|
@@ -103,6 +111,7 @@ export const ERROR_CODES = {
|
|
|
103
111
|
PARAM_MISSING: 'PARAM_MISSING',
|
|
104
112
|
ELEMENT_NOT_ACTIONABLE: 'ELEMENT_NOT_ACTIONABLE',
|
|
105
113
|
VALUE_NOT_SET: 'VALUE_NOT_SET',
|
|
114
|
+
CREDENTIAL_FIELD: 'CREDENTIAL_FIELD',
|
|
106
115
|
CLICK_TARGET_NOT_FOUND: 'CLICK_TARGET_NOT_FOUND',
|
|
107
116
|
HOVER_TARGET_NOT_FOUND: 'HOVER_TARGET_NOT_FOUND',
|
|
108
117
|
DIALOG_PENDING: 'DIALOG_PENDING',
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Managed block markers: everything between them belongs to this plugin. */
|
|
2
|
+
export declare const BEGIN = "# >>> dsh-browser: \u6D4F\u89C8\u5668\u63D2\u4EF6\u914D\u7F6E\uFF08\u672C\u5757\u7531\u8BBE\u7F6E\u9875\u7EF4\u62A4\uFF0C\u4E5F\u53EF\u624B\u5DE5\u7F16\u8F91\uFF09>>>";
|
|
3
|
+
export declare const END = "# <<< dsh-browser <<<";
|
|
4
|
+
/**
|
|
5
|
+
* The pre-merge markers, written by dsh-browser-toggle 0.3.0. Accepted so an
|
|
6
|
+
* existing block is replaced in place instead of accumulating a second row that
|
|
7
|
+
* would patch the same entry twice.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LEGACY_BEGIN = "# >>> dsh-browser-toggle: \u5F3A\u529B\u5DE5\u5177\u95F8\u95E8\uFF08\u672C\u5757\u7531\u8BBE\u7F6E\u9875\u7EF4\u62A4\uFF0C\u4E5F\u53EF\u624B\u5DE5\u7F16\u8F91\uFF09>>>";
|
|
10
|
+
export declare const LEGACY_END = "# <<< dsh-browser-toggle <<<";
|
|
11
|
+
/** Path of the home-level patch layer (highest priority among patch files). */
|
|
12
|
+
export declare function homePatchPath(env?: NodeJS.ProcessEnv): string;
|
|
13
|
+
/**
|
|
14
|
+
* The managed block: one loader row for the whole plugin, with the shipped
|
|
15
|
+
* launch/provider/snapshot values repeated and the two gates filled in.
|
|
16
|
+
*
|
|
17
|
+
* @param allowEvaluate - register browser_evaluate
|
|
18
|
+
* @param allowCdp - register browser_cdp
|
|
19
|
+
* @param diagnostics - register the two diagnostics tools
|
|
20
|
+
* @returns the YAML block, markers included
|
|
21
|
+
*/
|
|
22
|
+
export declare function renderBrowserBlock(allowEvaluate: boolean, allowCdp: boolean, diagnostics: boolean): string;
|
|
23
|
+
/** Merge the managed block into `original`, replacing an existing one in place. */
|
|
24
|
+
export declare function mergeBrowserBlock(original: string, allowEvaluate: boolean, allowCdp: boolean, diagnostics: boolean): string;
|
|
25
|
+
/** Read the current managed block, or undefined when there is none. */
|
|
26
|
+
export declare function parseBrowserBlock(text: string): {
|
|
27
|
+
allowEvaluate: boolean;
|
|
28
|
+
allowCdp: boolean;
|
|
29
|
+
diagnostics: boolean;
|
|
30
|
+
} | undefined;
|
|
31
|
+
/** Back-compat aliases used by the absorbed settings-card host half. */
|
|
32
|
+
export declare const parseGatesBlock: typeof parseBrowserBlock;
|
|
33
|
+
/** Back-compat alias (see {@link mergeBrowserBlock}). */
|
|
34
|
+
export declare const mergeGatesBlock: typeof mergeBrowserBlock;
|
|
35
|
+
/**
|
|
36
|
+
* Write both gates into the home-level patch. Backup first, atomic rename, then
|
|
37
|
+
* read back and check; on a failed check the previous bytes are restored.
|
|
38
|
+
*
|
|
39
|
+
* @param allowEvaluate - register browser_evaluate
|
|
40
|
+
* @param allowCdp - register browser_cdp
|
|
41
|
+
* @param env - environment (DSH_HOME override for tests)
|
|
42
|
+
* @returns the file written and the values it now carries
|
|
43
|
+
*/
|
|
44
|
+
export declare function writeGates(allowEvaluate: boolean, allowCdp: boolean, diagnostics: boolean, env?: NodeJS.ProcessEnv): Promise<{
|
|
45
|
+
patch: string;
|
|
46
|
+
allowEvaluate: boolean;
|
|
47
|
+
allowCdp: boolean;
|
|
48
|
+
diagnostics: boolean;
|
|
49
|
+
}>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Home-level patch writer for the merged single-row plugin (2026-10-19).
|
|
3
|
+
*
|
|
4
|
+
* Reads and rewrites ONE managed block inside the home-level cordis.patch.yml
|
|
5
|
+
* (the patch layer with the highest priority, so the user's own choice wins over
|
|
6
|
+
* any profile layer and over the package default). Everything outside the block
|
|
7
|
+
* is preserved byte for byte.
|
|
8
|
+
*
|
|
9
|
+
* Why the block carries the WHOLE row: the loader replaces an entry's `config`
|
|
10
|
+
* wholesale, so a block that lists only the two gates would silently reset the
|
|
11
|
+
* launch options, the idle timeout and the snapshot engine to their defaults.
|
|
12
|
+
* The template below therefore mirrors cordis.patch.yml's shipped values and
|
|
13
|
+
* differs only in the two gate booleans.
|
|
14
|
+
*
|
|
15
|
+
* Dependency-free on purpose: a half-written patch layer breaks every plugin
|
|
16
|
+
* load in the profile, so this file only uses node builtins and stays testable
|
|
17
|
+
* on its own.
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-browser-playwright-codex/gates-patch
|
|
20
|
+
*/
|
|
21
|
+
import { readFile, writeFile, rename, copyFile } from 'node:fs/promises';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
/** Managed block markers: everything between them belongs to this plugin. */
|
|
25
|
+
export const BEGIN = '# >>> dsh-browser: 浏览器插件配置(本块由设置页维护,也可手工编辑)>>>';
|
|
26
|
+
export const END = '# <<< dsh-browser <<<';
|
|
27
|
+
/**
|
|
28
|
+
* The pre-merge markers, written by dsh-browser-toggle 0.3.0. Accepted so an
|
|
29
|
+
* existing block is replaced in place instead of accumulating a second row that
|
|
30
|
+
* would patch the same entry twice.
|
|
31
|
+
*/
|
|
32
|
+
export const LEGACY_BEGIN = '# >>> dsh-browser-toggle: 强力工具闸门(本块由设置页维护,也可手工编辑)>>>';
|
|
33
|
+
export const LEGACY_END = '# <<< dsh-browser-toggle <<<';
|
|
34
|
+
/** Path of the home-level patch layer (highest priority among patch files). */
|
|
35
|
+
export function homePatchPath(env = process.env) {
|
|
36
|
+
return join(env.DSH_HOME || join(homedir(), '.dsh'), 'cordis.patch.yml');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The managed block: one loader row for the whole plugin, with the shipped
|
|
40
|
+
* launch/provider/snapshot values repeated and the two gates filled in.
|
|
41
|
+
*
|
|
42
|
+
* @param allowEvaluate - register browser_evaluate
|
|
43
|
+
* @param allowCdp - register browser_cdp
|
|
44
|
+
* @param diagnostics - register the two diagnostics tools
|
|
45
|
+
* @returns the YAML block, markers included
|
|
46
|
+
*/
|
|
47
|
+
export function renderBrowserBlock(allowEvaluate, allowCdp, diagnostics) {
|
|
48
|
+
return [
|
|
49
|
+
BEGIN,
|
|
50
|
+
'# allowEvaluate: 注册 browser_evaluate(页面任意 JS,能用你的登录态操作网站)',
|
|
51
|
+
'# allowCdp: 注册 browser_cdp(原始 CDP,仅放行检查与输入模拟)',
|
|
52
|
+
'# 包内默认关闭;这里的值就是用户自己的选择。改完由热重载生效。',
|
|
53
|
+
'# 整行 config 会被整体替换,所以这里写全 —— 只写两个闸门会把其它设置重置成默认值。',
|
|
54
|
+
'- id: browser',
|
|
55
|
+
" name: 'dsh-browser-playwright-codex'",
|
|
56
|
+
' config:',
|
|
57
|
+
' service:',
|
|
58
|
+
' provider: playwright',
|
|
59
|
+
' playwright:',
|
|
60
|
+
' launch:',
|
|
61
|
+
' headless: false',
|
|
62
|
+
' persistent: true',
|
|
63
|
+
' viewport:',
|
|
64
|
+
' width: 1280',
|
|
65
|
+
' height: 800',
|
|
66
|
+
' navigationTimeoutMs: 30000',
|
|
67
|
+
' ignoreHTTPSErrors: false',
|
|
68
|
+
' idleTimeoutMs: 0',
|
|
69
|
+
' maxSessions: 8',
|
|
70
|
+
' snapshot:',
|
|
71
|
+
' diff: false',
|
|
72
|
+
' tool:',
|
|
73
|
+
' toolPrefix: "browser_"',
|
|
74
|
+
' allowEvaluate: ' + (allowEvaluate ? 'true' : 'false'),
|
|
75
|
+
' allowCdp: ' + (allowCdp ? 'true' : 'false'),
|
|
76
|
+
' diagnostics: ' + (diagnostics ? 'true' : 'false'),
|
|
77
|
+
' registerDisabledTools: false',
|
|
78
|
+
' diagnostics: false',
|
|
79
|
+
' maxWaitMs: 60000',
|
|
80
|
+
END,
|
|
81
|
+
'',
|
|
82
|
+
].join('\n');
|
|
83
|
+
}
|
|
84
|
+
/** Locate the managed block, preferring the current markers over the legacy ones. */
|
|
85
|
+
function findBlock(original) {
|
|
86
|
+
const markers = [
|
|
87
|
+
[BEGIN, END],
|
|
88
|
+
[LEGACY_BEGIN, LEGACY_END],
|
|
89
|
+
];
|
|
90
|
+
for (const [begin, end] of markers) {
|
|
91
|
+
const beginIdx = original.indexOf(begin);
|
|
92
|
+
const endIdx = original.indexOf(end);
|
|
93
|
+
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
94
|
+
let after = endIdx + end.length;
|
|
95
|
+
if (original[after] === '\n')
|
|
96
|
+
after += 1;
|
|
97
|
+
return { start: beginIdx, after };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
/** Merge the managed block into `original`, replacing an existing one in place. */
|
|
103
|
+
export function mergeBrowserBlock(original, allowEvaluate, allowCdp, diagnostics) {
|
|
104
|
+
const block = renderBrowserBlock(allowEvaluate, allowCdp, diagnostics);
|
|
105
|
+
const found = findBlock(original);
|
|
106
|
+
if (found !== undefined)
|
|
107
|
+
return original.slice(0, found.start) + block + original.slice(found.after);
|
|
108
|
+
return (original === '' || original.endsWith('\n') ? original : original + '\n') + '\n' + block;
|
|
109
|
+
}
|
|
110
|
+
/** Read the current managed block, or undefined when there is none. */
|
|
111
|
+
export function parseBrowserBlock(text) {
|
|
112
|
+
const found = findBlock(text);
|
|
113
|
+
if (found === undefined)
|
|
114
|
+
return undefined;
|
|
115
|
+
const body = text.slice(found.start, found.after);
|
|
116
|
+
const evaluate = /allowEvaluate:\s*(true|false)/.exec(body);
|
|
117
|
+
const cdp = /allowCdp:\s*(true|false)/.exec(body);
|
|
118
|
+
if (evaluate === null || cdp === null)
|
|
119
|
+
return undefined;
|
|
120
|
+
const diag = /diagnostics:\s*(true|false)/.exec(body);
|
|
121
|
+
return {
|
|
122
|
+
allowEvaluate: evaluate[1] === 'true',
|
|
123
|
+
allowCdp: cdp[1] === 'true',
|
|
124
|
+
diagnostics: diag === null ? false : diag[1] === 'true',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Back-compat aliases used by the absorbed settings-card host half. */
|
|
128
|
+
export const parseGatesBlock = parseBrowserBlock;
|
|
129
|
+
/** Back-compat alias (see {@link mergeBrowserBlock}). */
|
|
130
|
+
export const mergeGatesBlock = mergeBrowserBlock;
|
|
131
|
+
/**
|
|
132
|
+
* Write both gates into the home-level patch. Backup first, atomic rename, then
|
|
133
|
+
* read back and check; on a failed check the previous bytes are restored.
|
|
134
|
+
*
|
|
135
|
+
* @param allowEvaluate - register browser_evaluate
|
|
136
|
+
* @param allowCdp - register browser_cdp
|
|
137
|
+
* @param env - environment (DSH_HOME override for tests)
|
|
138
|
+
* @returns the file written and the values it now carries
|
|
139
|
+
*/
|
|
140
|
+
export async function writeGates(allowEvaluate, allowCdp, diagnostics, env = process.env) {
|
|
141
|
+
const file = homePatchPath(env);
|
|
142
|
+
let original = '';
|
|
143
|
+
try {
|
|
144
|
+
original = await readFile(file, 'utf8');
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
original = '';
|
|
148
|
+
}
|
|
149
|
+
const next = mergeBrowserBlock(original, allowEvaluate, allowCdp, diagnostics);
|
|
150
|
+
if (original !== '')
|
|
151
|
+
await copyFile(file, file + '.bak-browser-toggle').catch(() => { });
|
|
152
|
+
const tmp = file + '.tmp-browser-toggle';
|
|
153
|
+
await writeFile(tmp, next, 'utf8');
|
|
154
|
+
await rename(tmp, file);
|
|
155
|
+
const check = await readFile(file, 'utf8');
|
|
156
|
+
const parsed = parseBrowserBlock(check);
|
|
157
|
+
if (parsed === undefined ||
|
|
158
|
+
parsed.allowEvaluate !== allowEvaluate ||
|
|
159
|
+
parsed.allowCdp !== allowCdp ||
|
|
160
|
+
parsed.diagnostics !== diagnostics) {
|
|
161
|
+
if (original !== '') {
|
|
162
|
+
await writeFile(file, original, 'utf8');
|
|
163
|
+
throw new Error('patch self-check failed after write; previous content restored');
|
|
164
|
+
}
|
|
165
|
+
throw new Error('patch self-check failed after write');
|
|
166
|
+
}
|
|
167
|
+
return { patch: file, allowEvaluate, allowCdp, diagnostics };
|
|
168
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -20,3 +20,8 @@ export { Config as PlaywrightConfigSchema } from './config.ts';
|
|
|
20
20
|
export type { PlaywrightConfig, PageData } from './config.ts';
|
|
21
21
|
export type { BrowserNode, BrowserProvider, BrowserSession, BrowserSnapshot, LoadState, ScreenshotCapture, TabInfo, } from './types.ts';
|
|
22
22
|
export type { ToolConfig } from './tool.ts';
|
|
23
|
+
export { Config as BrowserPluginConfigSchema, apply, name } from './plugin.ts';
|
|
24
|
+
export type { BrowserPluginConfig } from './plugin.ts';
|
|
25
|
+
export { apply as applyPlaywright, name as playwrightEntry, Config as PlaywrightEntryConfig } from './playwright.ts';
|
|
26
|
+
export { apply as applyTools, name as toolEntry, Config as ToolEntryConfig } from './tool.ts';
|
|
27
|
+
export { Config } from './plugin.ts';
|
package/lib/index.js
CHANGED
|
@@ -12,3 +12,9 @@ export { default as BrowserRuntime } from "./service.js";
|
|
|
12
12
|
export { PlaywrightProvider } from "./provider.js";
|
|
13
13
|
export { assertAllowedUrl } from "./url-policy.js";
|
|
14
14
|
export { Config as PlaywrightConfigSchema } from "./config.js";
|
|
15
|
+
// Single loader row (2026-10-19): the package root is the plugin. The three
|
|
16
|
+
// sub-entries below stay importable, but the loader mounts this one.
|
|
17
|
+
export { Config as BrowserPluginConfigSchema, apply, name } from "./plugin.js";
|
|
18
|
+
export { apply as applyPlaywright, name as playwrightEntry, Config as PlaywrightEntryConfig } from "./playwright.js";
|
|
19
|
+
export { apply as applyTools, name as toolEntry, Config as ToolEntryConfig } from "./tool.js";
|
|
20
|
+
export { Config } from "./plugin.js";
|
package/lib/page-actions.d.ts
CHANGED
|
@@ -68,6 +68,22 @@ export declare function clickTargetHref(locator: Locator): Promise<string | null
|
|
|
68
68
|
export declare function clickLocator(session: PlaywrightSession, locator: Locator, signal: AbortSignal | undefined, label: string, shared: number): Promise<import("./types.ts").BrowserSnapshot>;
|
|
69
69
|
export declare function clickAt(session: PlaywrightSession, x: number, y: number, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
|
|
70
70
|
export declare function clickAtRef(session: PlaywrightSession, ref: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
|
|
71
|
+
/**
|
|
72
|
+
* Does this field hold a secret (password, one-time code, card number)?
|
|
73
|
+
*
|
|
74
|
+
* Mechanism-level counterpart to the old promise "it never types your
|
|
75
|
+
* passwords": `browser_fill` refuses these fields so the promise is true by
|
|
76
|
+
* construction, not by the model remembering to behave. The visible window is
|
|
77
|
+
* where the human types them.
|
|
78
|
+
*
|
|
79
|
+
* @param spec - tag / type / autocomplete read from the live element
|
|
80
|
+
* @returns true when the field must be filled by the human
|
|
81
|
+
*/
|
|
82
|
+
export declare function isCredentialField(spec: {
|
|
83
|
+
tag: string;
|
|
84
|
+
type?: string;
|
|
85
|
+
autocomplete?: string;
|
|
86
|
+
}): boolean;
|
|
71
87
|
export declare function fill(session: PlaywrightSession, ref: string, text: string, signal: AbortSignal | undefined): Promise<import("./types.ts").BrowserSnapshot>;
|
|
72
88
|
/**
|
|
73
89
|
* Normalize a field value before comparing. Two levels of forgiveness, both of them
|
package/lib/page-actions.js
CHANGED
|
@@ -480,13 +480,55 @@ export async function clickAtRef(session, ref, signal) {
|
|
|
480
480
|
}
|
|
481
481
|
});
|
|
482
482
|
}
|
|
483
|
+
/** Autocomplete tokens whose value is a secret the human should type in the window. */
|
|
484
|
+
const CREDENTIAL_AUTOCOMPLETE = new Set([
|
|
485
|
+
'current-password',
|
|
486
|
+
'new-password',
|
|
487
|
+
'one-time-code',
|
|
488
|
+
'cc-number',
|
|
489
|
+
'cc-csc',
|
|
490
|
+
'cc-exp',
|
|
491
|
+
'cc-exp-month',
|
|
492
|
+
'cc-exp-year',
|
|
493
|
+
]);
|
|
494
|
+
/**
|
|
495
|
+
* Does this field hold a secret (password, one-time code, card number)?
|
|
496
|
+
*
|
|
497
|
+
* Mechanism-level counterpart to the old promise "it never types your
|
|
498
|
+
* passwords": `browser_fill` refuses these fields so the promise is true by
|
|
499
|
+
* construction, not by the model remembering to behave. The visible window is
|
|
500
|
+
* where the human types them.
|
|
501
|
+
*
|
|
502
|
+
* @param spec - tag / type / autocomplete read from the live element
|
|
503
|
+
* @returns true when the field must be filled by the human
|
|
504
|
+
*/
|
|
505
|
+
export function isCredentialField(spec) {
|
|
506
|
+
const tag = spec.tag.toLowerCase();
|
|
507
|
+
if (tag !== 'input' && tag !== 'textarea')
|
|
508
|
+
return false;
|
|
509
|
+
if ((spec.type ?? '').toLowerCase() === 'password')
|
|
510
|
+
return true;
|
|
511
|
+
const tokens = (spec.autocomplete ?? '').toLowerCase().trim().split(/\s+/);
|
|
512
|
+
const token = tokens[tokens.length - 1] ?? '';
|
|
513
|
+
return CREDENTIAL_AUTOCOMPLETE.has(token);
|
|
514
|
+
}
|
|
483
515
|
export async function fill(session, ref, text, signal) {
|
|
484
516
|
return session.run(async () => {
|
|
485
517
|
const locator = await session.refLocator(ref);
|
|
486
518
|
const page = session.currentPageSync();
|
|
487
519
|
await session.waitForElementStable(locator);
|
|
488
520
|
await session.assertActionable(locator, { editable: true });
|
|
489
|
-
const
|
|
521
|
+
const spec = await locator
|
|
522
|
+
.evaluate((el) => {
|
|
523
|
+
const tag = el.tagName.toLowerCase();
|
|
524
|
+
const type = tag === 'input' ? String(el.type || '').toLowerCase() : '';
|
|
525
|
+
return { tag, type, autocomplete: el.getAttribute('autocomplete') ?? '' };
|
|
526
|
+
})
|
|
527
|
+
.catch(() => ({ tag: 'input', type: '', autocomplete: '' }));
|
|
528
|
+
if (isCredentialField(spec)) {
|
|
529
|
+
throw new BrowserError('CREDENTIAL_FIELD', 'browser_fill refuses credential fields (password, one-time code, card number): a human types those in the visible window. Ask the user to type it there, then take a fresh snapshot and continue with the rest.');
|
|
530
|
+
}
|
|
531
|
+
const tag = spec.tag;
|
|
490
532
|
let lastError;
|
|
491
533
|
let written = false;
|
|
492
534
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
package/lib/plugin.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single loader entry: one package = one plugin (2026-10-19).
|
|
3
|
+
*
|
|
4
|
+
* Why this file exists: `dsh-client-modules` rejects distinct active Loader
|
|
5
|
+
* sources that resolve to the same package name, and a Settings card needs a
|
|
6
|
+
* client module. The browser capability used to be mounted as three rows
|
|
7
|
+
* (`/service`, `/playwright`, `/tool`) because it has three configurable
|
|
8
|
+
* concerns, which forced the card into a second package (dsh-browser-toggle).
|
|
9
|
+
* One row per package makes the card legal inside this package, so the whole
|
|
10
|
+
* product is now one plugin: this entry mounts the three concerns as child
|
|
11
|
+
* plugins and keeps their own config schemas untouched.
|
|
12
|
+
*
|
|
13
|
+
* Config shape (see cordis.patch.yml):
|
|
14
|
+
*
|
|
15
|
+
* service: { provider } browser seam
|
|
16
|
+
* playwright: { launch, idleTimeoutMs, ... } provider / window / snapshot
|
|
17
|
+
* tool: { toolPrefix, allowEvaluate, ... } the model-facing tools
|
|
18
|
+
*
|
|
19
|
+
* Each child still exports its original schema and apply(), so the subpath
|
|
20
|
+
* entries (`./service`, `./playwright`, `./tool`) keep working for tests, the
|
|
21
|
+
* verify script and anyone importing the package directly.
|
|
22
|
+
*
|
|
23
|
+
* @module dsh-browser-playwright-codex/plugin
|
|
24
|
+
*/
|
|
25
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
26
|
+
import z from '@deepseek-ai/schemastery';
|
|
27
|
+
import { type BrowserRuntimeConfig } from './service.ts';
|
|
28
|
+
import { type PlaywrightConfig } from './config.ts';
|
|
29
|
+
import { type ToolConfig } from './tool.ts';
|
|
30
|
+
/** Config accepted by the single loader row. */
|
|
31
|
+
export interface BrowserPluginConfig {
|
|
32
|
+
/** Browser seam: which registered provider to use. */
|
|
33
|
+
service?: BrowserRuntimeConfig;
|
|
34
|
+
/** Provider: window, launch and snapshot settings. */
|
|
35
|
+
playwright?: PlaywrightConfig;
|
|
36
|
+
/** Model-facing tools: prefix, capability gates, wait caps. */
|
|
37
|
+
tool?: ToolConfig;
|
|
38
|
+
}
|
|
39
|
+
/** Schemastery validation for {@link BrowserPluginConfig}. */
|
|
40
|
+
export declare const Config: z<BrowserPluginConfig>;
|
|
41
|
+
/** Cordis plugin name used by loader diagnostics (this is the row id in patches). */
|
|
42
|
+
export declare const name = "browser";
|
|
43
|
+
/**
|
|
44
|
+
* Mount the whole capability: the seam, the Playwright provider, the tool family,
|
|
45
|
+
* and — when the deployment has a web server — the Settings card.
|
|
46
|
+
*
|
|
47
|
+
* @param ctx - plugin context
|
|
48
|
+
* @param config - nested config for each concern; the loader fills defaults
|
|
49
|
+
*/
|
|
50
|
+
export declare function apply(ctx: Context, config: BrowserPluginConfig): void;
|
|
51
|
+
declare const _default: {
|
|
52
|
+
name: string;
|
|
53
|
+
Config: z<BrowserPluginConfig>;
|
|
54
|
+
apply: typeof apply;
|
|
55
|
+
};
|
|
56
|
+
export default _default;
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import z from '@deepseek-ai/schemastery';
|
|
2
|
+
import BrowserRuntime, { Config as ServiceConfig } from "./service.js";
|
|
3
|
+
import * as playwright from "./playwright.js";
|
|
4
|
+
import * as tool from "./tool.js";
|
|
5
|
+
import * as settingsCard from "./settings-card.js";
|
|
6
|
+
import { Config as PlaywrightConfigSchema } from "./config.js";
|
|
7
|
+
import { Config as ToolConfigSchema } from "./tool.js";
|
|
8
|
+
/** Schemastery validation for {@link BrowserPluginConfig}. */
|
|
9
|
+
export const Config = z.object({
|
|
10
|
+
service: ServiceConfig,
|
|
11
|
+
playwright: PlaywrightConfigSchema,
|
|
12
|
+
tool: ToolConfigSchema,
|
|
13
|
+
});
|
|
14
|
+
/** Cordis plugin name used by loader diagnostics (this is the row id in patches). */
|
|
15
|
+
export const name = 'browser';
|
|
16
|
+
/**
|
|
17
|
+
* Mount the whole capability: the seam, the Playwright provider, the tool family,
|
|
18
|
+
* and — when the deployment has a web server — the Settings card.
|
|
19
|
+
*
|
|
20
|
+
* @param ctx - plugin context
|
|
21
|
+
* @param config - nested config for each concern; the loader fills defaults
|
|
22
|
+
*/
|
|
23
|
+
export function apply(ctx, config) {
|
|
24
|
+
ctx.plugin(BrowserRuntime, config.service ?? {});
|
|
25
|
+
ctx.plugin(playwright, config.playwright);
|
|
26
|
+
ctx.plugin(tool, config.tool);
|
|
27
|
+
// The card's host half injects `webServer`, so in a CLI deployment this child
|
|
28
|
+
// simply stays pending instead of failing the whole plugin.
|
|
29
|
+
ctx.plugin(settingsCard);
|
|
30
|
+
}
|
|
31
|
+
export default { name, Config, apply };
|
package/lib/runtime-state.d.ts
CHANGED
|
@@ -64,11 +64,13 @@ export declare function isEngineUserChosen(): boolean;
|
|
|
64
64
|
export declare function publishGates(gates: {
|
|
65
65
|
allowEvaluate: boolean;
|
|
66
66
|
allowCdp: boolean;
|
|
67
|
+
diagnostics: boolean;
|
|
67
68
|
}): void;
|
|
68
69
|
/** The published gates, or undefined while the tool family has not been applied. */
|
|
69
70
|
export declare function getGates(): {
|
|
70
71
|
allowEvaluate: boolean;
|
|
71
72
|
allowCdp: boolean;
|
|
73
|
+
diagnostics: boolean;
|
|
72
74
|
} | undefined;
|
|
73
75
|
/**
|
|
74
76
|
* The last computed "which sites look logged in" summary, or undefined before anything
|
package/lib/runtime-state.js
CHANGED
|
@@ -149,7 +149,11 @@ export function isEngineUserChosen() {
|
|
|
149
149
|
* @param gates - whether browser_evaluate / browser_cdp are registered.
|
|
150
150
|
*/
|
|
151
151
|
export function publishGates(gates) {
|
|
152
|
-
_gates = {
|
|
152
|
+
_gates = {
|
|
153
|
+
allowEvaluate: gates.allowEvaluate === true,
|
|
154
|
+
allowCdp: gates.allowCdp === true,
|
|
155
|
+
diagnostics: gates.diagnostics === true,
|
|
156
|
+
};
|
|
153
157
|
}
|
|
154
158
|
/** The published gates, or undefined while the tool family has not been applied. */
|
|
155
159
|
export function getGates() {
|