dsh-browser-view 0.3.1 → 0.3.3

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/lib/index.js CHANGED
@@ -2035,9 +2035,28 @@ async function selectTool(config, args) {
2035
2035
  ...await pageIdentity(page)
2036
2036
  };
2037
2037
  }
2038
+ /**
2039
+ * 防御性解码 eval 脚本:模型/人工偶发把脚本 JSON.stringify 一次再传
2040
+ * (脚本以 \" 开头、换行变字面 \\n),回放/录制链路不纠错会导致语法错误。
2041
+ * 识别条件:字符串以引号包裹且含反斜杠转义,且 JSON.parse 结果仍是字符串。
2042
+ * 无法解析或语义不变场景原样返回(裸字符串字面量如 "hello" 不含转义,不受影响)。
2043
+ */
2044
+ function unwrapDoubleEncodedScript(script) {
2045
+ if (script.length >= 2 && script.startsWith("\"") && script.endsWith("\"") && script.includes("\\")) try {
2046
+ const parsed = JSON.parse(script);
2047
+ if (typeof parsed === "string" && parsed !== script) return {
2048
+ script: parsed,
2049
+ unwrapped: true
2050
+ };
2051
+ } catch {}
2052
+ return {
2053
+ script,
2054
+ unwrapped: false
2055
+ };
2056
+ }
2038
2057
  async function evalTool(config, args) {
2039
2058
  const page = await getPage(config);
2040
- const script = String(args.script ?? "");
2059
+ const { script, unwrapped } = unwrapDoubleEncodedScript(String(args.script ?? ""));
2041
2060
  const wrapped = /[\n;]|^\s*return\b/.test(script) ? `(async () => {\n${script}\n})()` : `(${script})`;
2042
2061
  const raw = await page.evaluate(wrapped);
2043
2062
  let result;
@@ -2046,7 +2065,10 @@ async function evalTool(config, args) {
2046
2065
  } catch {
2047
2066
  result = raw === void 0 ? null : String(raw);
2048
2067
  }
2049
- return { result };
2068
+ return {
2069
+ result,
2070
+ ...unwrapped ? { scriptUnwrapped: true } : {}
2071
+ };
2050
2072
  }
2051
2073
  async function screenshotTool(config, args, exec) {
2052
2074
  const page = await getPage(config);
@@ -3342,15 +3364,27 @@ async function recordTool(args) {
3342
3364
  if (!name) throw new Error("save 需要 name");
3343
3365
  const recorded = lastRecordingOf();
3344
3366
  const source = recorded !== null ? "recording" : "recent-log";
3345
- const rawSteps = recorded !== null ? recorded.map((c) => ({
3346
- tool: c.tool,
3347
- args: c.args ?? {},
3348
- ...c.meta !== void 0 ? { meta: c.meta } : {}
3349
- })) : testLogOf().reverse().slice(-100).map((c) => ({
3350
- tool: c.tool,
3351
- args: c.args ?? {},
3352
- ...c.meta !== void 0 ? { meta: c.meta } : {}
3353
- }));
3367
+ const calls = recorded !== null ? recorded : testLogOf().reverse().slice(-100);
3368
+ const includeFailed = args.includeFailed === true;
3369
+ const failedCount = includeFailed ? 0 : calls.filter((c) => c.ok === false).length;
3370
+ const rawSteps = (includeFailed ? calls : calls.filter((c) => c.ok !== false)).map((c) => {
3371
+ if (c.tool === "browser_eval" && typeof c.args?.script === "string") {
3372
+ const { script } = unwrapDoubleEncodedScript(c.args.script);
3373
+ if (script !== c.args.script) return {
3374
+ tool: c.tool,
3375
+ args: {
3376
+ ...c.args,
3377
+ script
3378
+ },
3379
+ ...c.meta !== void 0 ? { meta: c.meta } : {}
3380
+ };
3381
+ }
3382
+ return {
3383
+ tool: c.tool,
3384
+ args: c.args ?? {},
3385
+ ...c.meta !== void 0 ? { meta: c.meta } : {}
3386
+ };
3387
+ });
3354
3388
  const steps = args.stripReads === true ? rawSteps.filter((s) => !isReadonlyStep(s)) : rawSteps;
3355
3389
  const stripped = rawSteps.length - steps.length;
3356
3390
  let varsApplied = 0;
@@ -3379,6 +3413,7 @@ async function recordTool(args) {
3379
3413
  source,
3380
3414
  file,
3381
3415
  ...stripped > 0 ? { stripped } : {},
3416
+ ...failedCount > 0 ? { skippedFailed: failedCount } : {},
3382
3417
  ...varsApplied > 0 ? { varsApplied } : {},
3383
3418
  ...warnings.length > 0 ? { warnings } : {},
3384
3419
  hint: "已持久化到磁盘,重启 DSH 后仍可回放"
@@ -4304,7 +4339,13 @@ function registerTools(ctx, config) {
4304
4339
  outputSchema: {
4305
4340
  type: "object",
4306
4341
  additionalProperties: false,
4307
- properties: { result: {} },
4342
+ properties: {
4343
+ result: {},
4344
+ scriptUnwrapped: {
4345
+ type: "boolean",
4346
+ description: "入参 script 疑似被 JSON 双重序列化(以引号包裹且含 \\n 字面量),已自动解码一层。"
4347
+ }
4348
+ },
4308
4349
  required: ["result"]
4309
4350
  },
4310
4351
  render: (_args, value) => `Result: ${JSON.stringify(value.result)}`,
@@ -5409,6 +5450,10 @@ function registerTools(ctx, config) {
5409
5450
  type: "boolean",
5410
5451
  description: "save 时剔除只读步骤(snapshot/elements/eval/network/status/cookies list),默认 false。"
5411
5452
  },
5453
+ includeFailed: {
5454
+ type: "boolean",
5455
+ description: "save 时保留失败的实验步骤(默认剔除:超时/无效 selector 的步骤回放必然再次失败并卡住回放)。"
5456
+ },
5412
5457
  vars: {
5413
5458
  type: "object",
5414
5459
  description: "save 时参数化:{ 变量名: \"#步骤号.args.字段\" }(步骤号 1 起),该字段值替换为 {{变量名}} 占位符;replay 时用 vars 传入真实值。"
@@ -5423,6 +5468,10 @@ function registerTools(ctx, config) {
5423
5468
  recording: { type: "boolean" },
5424
5469
  steps: { type: "integer" },
5425
5470
  stripped: { type: "integer" },
5471
+ skippedFailed: {
5472
+ type: "integer",
5473
+ description: "save 时默认剔除的失败实验步骤数。"
5474
+ },
5426
5475
  varsApplied: { type: "integer" },
5427
5476
  saved: { type: "string" },
5428
5477
  source: { type: "string" },
@@ -155,7 +155,18 @@ export declare function selectTool(config: BrowserConfig, args: Record<string, a
155
155
  selected: string;
156
156
  selector: string;
157
157
  }>;
158
+ /**
159
+ * 防御性解码 eval 脚本:模型/人工偶发把脚本 JSON.stringify 一次再传
160
+ * (脚本以 \" 开头、换行变字面 \\n),回放/录制链路不纠错会导致语法错误。
161
+ * 识别条件:字符串以引号包裹且含反斜杠转义,且 JSON.parse 结果仍是字符串。
162
+ * 无法解析或语义不变场景原样返回(裸字符串字面量如 "hello" 不含转义,不受影响)。
163
+ */
164
+ export declare function unwrapDoubleEncodedScript(script: string): {
165
+ script: string;
166
+ unwrapped: boolean;
167
+ };
158
168
  export declare function evalTool(config: BrowserConfig, args: Record<string, any>): Promise<{
169
+ scriptUnwrapped?: boolean | undefined;
159
170
  result: unknown;
160
171
  }>;
161
172
  export declare function screenshotTool(config: BrowserConfig, args: Record<string, any>, exec?: ToolRunContext): Promise<{
@@ -566,6 +577,7 @@ export declare function recordTool(args: Record<string, any>): Promise<{
566
577
  hint: string;
567
578
  warnings?: string[] | undefined;
568
579
  varsApplied?: number | undefined;
580
+ skippedFailed?: number | undefined;
569
581
  stripped?: number | undefined;
570
582
  saved: string;
571
583
  steps: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-browser-view",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "DSH 插件:共享 Playwright 浏览器自动化(36 个 browser_* 工具,含语义元素查找、批处理、视觉回归、选择器自愈、参数化回放、结构化抽取、回归报告与隔离 context)+ 人机协作(人工接管等待、敏感操作审批、人机混合录制、面板双向化:操作预告高亮/内嵌报告)+ 实时画面面板与录制管理。仅依赖 playwright-core,不依赖任何 @deepseek-ai 运行时包。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",