@tnnevol/dsh-midscene 0.2.0-rc.2.5 → 0.2.0-rc.2.6

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 CHANGED
@@ -7,7 +7,7 @@
7
7
  ## 安装
8
8
 
9
9
  ```sh
10
- dsh plugin --profile web add @tnnevol/dsh-midscene@0.2.0-rc.2.5
10
+ dsh plugin --profile web add @tnnevol/dsh-midscene@0.2.0-rc.2.6
11
11
  ```
12
12
 
13
13
  安装后重启 DSH Web profile。
@@ -14,6 +14,7 @@
14
14
  "@deepseek-ai/dsh-client-ui-slots",
15
15
  "@deepseek-ai/dsh-host-webserver",
16
16
  "@deepseek-ai/dsh-llm",
17
+ "@deepseek-ai/dsh-session",
17
18
  "@deepseek-ai/dsh-settings",
18
19
  "@deepseek-ai/dsh-skill",
19
20
  "@deepseek-ai/schemastery"
package/lib/client.js CHANGED
@@ -42662,7 +42662,7 @@ onKeyDown: (e) => this.foundation._handleKeyDown(e) });
42662
42662
  sectionTitle: "Midscene 接入设置",
42663
42663
  sectionHint: "Midscene 通过本地桥接服务使用 DSH 已配置的模型,无需任何模型 API 密钥。",
42664
42664
  apiKeyLabel: "接入密钥",
42665
- apiKeyHint: "桥接服务的接入密钥,不是模型密钥。重新生成后需要同步更新 Midscene 的 .env。",
42665
+ apiKeyHint: "桥接服务的接入密钥,不是模型密钥。重新生成后需要同步更新下面的接入配置(命令行 .env 与浏览器扩展两处)。",
42666
42666
  regenerate: "随机重新生成",
42667
42667
  regenerating: "生成中…",
42668
42668
  customKey: "自定义密钥",
@@ -42682,9 +42682,9 @@ onKeyDown: (e) => this.foundation._handleKeyDown(e) });
42682
42682
  modelsUnavailable: "DSH 当前没有可用于 Midscene 的识图模型。请先在 DSH 中安装并登录一个提供方(模型需具备图片输入能力),然后回到本页重新加载。",
42683
42683
  addModel: "添加模型",
42684
42684
  removeModel: "移除",
42685
- envLabel: "Midscene .env",
42686
- envHint: "在运行 Midscene 的工作目录保存为 .env 即可;换模型不需要修改这里。",
42687
- envCopy: "复制 .env 内容",
42685
+ envLabel: "接入配置(命令行 .env 或 Midscene 浏览器扩展)",
42686
+ envHint: "两条路都用这四行:跑命令行时保存为工作目录的 .env;用 Chrome 里的 Midscene 扩展时,打开它的侧边栏设置把这段粘进去。模型跟随当前 DSH 会话,不需要任何第三方模型密钥。",
42687
+ envCopy: "复制接入配置",
42688
42688
  loading: "正在读取当前设置…",
42689
42689
  loadFailed: "读取设置失败",
42690
42690
  saveFailed: "保存失败",
@@ -42695,7 +42695,7 @@ onKeyDown: (e) => this.foundation._handleKeyDown(e) });
42695
42695
  sectionTitle: "Midscene bridge settings",
42696
42696
  sectionHint: "Midscene drives DSH-configured models through the local bridge — no model API key needed.",
42697
42697
  apiKeyLabel: "Bridge access key",
42698
- apiKeyHint: "Access key for the bridge, not a model key. Regenerating requires updating the Midscene .env.",
42698
+ apiKeyHint: "Access key for the bridge, not a model key. Regenerating requires updating the access config below (both the CLI .env and the browser extension).",
42699
42699
  regenerate: "Regenerate",
42700
42700
  regenerating: "Generating…",
42701
42701
  customKey: "Custom key",
@@ -42715,9 +42715,9 @@ onKeyDown: (e) => this.foundation._handleKeyDown(e) });
42715
42715
  modelsUnavailable: "DSH has no vision-capable model available for Midscene. Install and sign in to a provider in DSH first (the model needs image input), then reload this page.",
42716
42716
  addModel: "Add model",
42717
42717
  removeModel: "Remove",
42718
- envLabel: "Midscene .env",
42719
- envHint: "Save as .env in the directory where you run Midscene; changing models does not require editing it.",
42720
- envCopy: "Copy .env contents",
42718
+ envLabel: "Access config (CLI .env or Midscene browser extension)",
42719
+ envHint: "Both paths use these four lines: save them as .env in the directory where you run the CLI, or open the Midscene browser extension sidebar settings and paste them there. The model follows the current DSH session — no third-party model key is needed.",
42720
+ envCopy: "Copy access config",
42721
42721
  loading: "Reading current settings…",
42722
42722
  loadFailed: "Failed to read settings",
42723
42723
  saveFailed: "Failed to save",
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { randomBytes } from "node:crypto";
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { createServer } from "node:http";
4
+ import { SessionId } from "@deepseek-ai/dsh-session";
4
5
  import { existsSync } from "node:fs";
5
6
  import { readFile, readdir, stat } from "node:fs/promises";
6
7
  import { basename, join } from "node:path";
@@ -1296,6 +1297,18 @@ var BridgeServer = class {
1296
1297
  #server;
1297
1298
  /** 实际监听端口;启动成功前为 undefined。 */
1298
1299
  #port;
1300
+ /**
1301
+ * 桥自身的会话标识,随每次请求下发给模型层。
1302
+ *
1303
+ * 部分提供方要求「按会话路由」的请求头(如 opencode 的 `x-opencode-session`),
1304
+ * 而 DSH 正是从 `GenerateOptions.sessionId` 生成该头。桥原先不传这个字段,
1305
+ * 于是**只要会话模型属于这类提供方,桥就整体不可用**(502 `MissingSessionID`)——
1306
+ * 与「DSH 会话能正常请求的模型,桥也应该能正常发送」直接冲突。
1307
+ *
1308
+ * 取值稳定(每个桥实例一个):既满足按会话路由的要求,也让支持提示缓存的
1309
+ * 提供方拿到稳定的缓存键;不支持的提供方会忽略它,因此对所有提供方都安全。
1310
+ */
1311
+ #sessionId = SessionId(randomUUID());
1299
1312
  catalog;
1300
1313
  /**
1301
1314
  * @param deps - 运行依赖。
@@ -1594,6 +1607,7 @@ var BridgeServer = class {
1594
1607
  const timer = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
1595
1608
  const stream = this.deps.llm.stream({
1596
1609
  ...options,
1610
+ sessionId: this.#sessionId,
1597
1611
  signal: controller.signal
1598
1612
  });
1599
1613
  try {
@@ -1690,9 +1704,20 @@ var BridgeServer = class {
1690
1704
  * Midscene 技能,无需另行执行技能安装命令。注册随 Cordis 效果注销:插件卸载
1691
1705
  * 或 HMR 时技能同步消失(AC-03)。
1692
1706
  *
1693
- * **不写入用户技能目录**(需求行为约束):技能只在注册表里存在,宿主自己的
1694
- * 发现优先级负责共存——项目目录 > 用户目录 > 插件注册,因此用户自行安装的
1695
- * 同名技能天然胜出(AC-04),插件不覆盖也不重复提示。
1707
+ * **不写入用户技能目录**(需求行为约束):技能只在注册表里存在,由宿主自己的
1708
+ * 发现优先级负责共存,插件不往用户目录里写文件、也不重复提示。
1709
+ *
1710
+ * **同名冲突时插件保持最高优先级**(AC-04):插件分发的是**本环境的权威版本**
1711
+ * ——包内技能带环境说明(模型由桥提供、不索要密钥),而用户目录里的同名副本
1712
+ * 通常是没有这段说明的上游原文。一旦被后者遮蔽,agent 就会不知道插件的作用,
1713
+ * 转而向用户索要模型密钥(实测事故:会话里 agent 翻了 9 步环境、最后仍让用户
1714
+ * 提供 `MIDSCENE_MODEL_API_KEY`)。因此候选统一用 `rank: 0`(数值最小者胜)。
1715
+ *
1716
+ * ⚠️ **层序优先于 rank**:宿主按 `[global, ...作用域链]` 逐层合并同名技能,
1717
+ * **后面的层直接覆盖前面的层**,rank 只在同一层内决定胜负。插件在 profile 启动时
1718
+ * 注册(global 层),而 `~/.agents/skills` 落在作用域链里(更靠后)——**这种情况
1719
+ * 下 rank 再小也赢不了**。所以另有一条兜底:插件自有的入口技能 `dsh-midscene`
1720
+ * 用**独占名字**,不参与任何同名冲突。
1696
1721
  */
1697
1722
  /** SkillProvider 在注册表里的名字。 */
1698
1723
  const SKILL_PROVIDER_NAME = "dsh-midscene";
@@ -1717,6 +1742,11 @@ const BRIDGE_PRELUDE = `> **本环境说明(由 DSH Midscene 插件注入,
1717
1742
  > - **不要向用户索要模型 API key,也不要引导用户去申请第三方模型。** 本文档中出现的服务商、
1718
1743
  > 模型与 key 示例仅作背景知识;实际使用哪个模型由桥决定。
1719
1744
  > - 需要浏览器桥接模式时同样走桥:Midscene 侧的模型配置始终是上面这四行。
1745
+ > - **浏览器交互用用户的主 Chrome**:走扩展桥接(\`--bridge\` / \`AgentOverChromeBridge\`),
1746
+ > 复用用户真实浏览器的登录态,而不是另起一个干净 profile。用户只需**一次性**把上面四行
1747
+ > 粘进 **Midscene 扩展的侧边栏设置**(扩展的 Playground 靠它接上 DSH 的模型)。
1748
+ > **要主动把这一步讲给用户** —— 少了它扩展用不上 DSH 的模型,用户就会被引向申请第三方
1749
+ > key,而那正是本插件要消除的成本。不要反过来让用户「先配好模型再来」。
1720
1750
  >
1721
1751
  > **静默操作(不打断用户)**:用 Bridge 模式驱动用户浏览器时,连接标签页应传
1722
1752
  > \`activate: false\`,否则会把用户正在看的页面切走、让浏览器变成测试画布。JS SDK 路径:
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tnnevol/dsh-midscene",
3
3
  "displayName": "Midscene Bridge",
4
4
  "type": "module",
5
- "version": "0.2.0-rc.2.5",
5
+ "version": "0.2.0-rc.2.6",
6
6
  "description": "Hosts a loopback OpenAI-compatible bridge that exposes every model configured in DSH (official routes, plugin routes, and user-declared providers) so Midscene browser automation can drive pages with natural language without holding any credential itself.",
7
7
  "author": "tnnevol",
8
8
  "license": "Apache-2.0",
@@ -68,6 +68,7 @@
68
68
  "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
69
69
  "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
70
70
  "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
71
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
71
72
  "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
72
73
  "@deepseek-ai/dsh-skill": "0.2.0-rc.2",
73
74
  "@deepseek-ai/schemastery": "3.18.4",
@@ -83,6 +84,7 @@
83
84
  "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
84
85
  "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
85
86
  "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
87
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
86
88
  "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
87
89
  "@deepseek-ai/dsh-skill": "0.2.0-rc.2",
88
90
  "@deepseek-ai/schemastery": "3.18.4",
@@ -94,8 +96,8 @@
94
96
  "tsdown": "^0.22.2",
95
97
  "typescript": "^5.9.3",
96
98
  "vitest": "^4.1.8",
97
- "@tnnevol/dsh-e2e-kit": "5.7.6",
98
- "@tnnevol/dsh-semi-ui": "5.7.6"
99
+ "@tnnevol/dsh-e2e-kit": "5.7.7",
100
+ "@tnnevol/dsh-semi-ui": "5.7.7"
99
101
  },
100
102
  "scripts": {
101
103
  "build": "tsdown",
package/skills/SOURCE.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # 内置技能来源
2
2
 
3
- 本目录内置 **7 个技能**,来自两个上游。**所有技能文件都与上游逐字节一致,不含任何本地补丁。**
3
+ 本目录内置 **8 个技能**:**7 个来自上游**(与上游逐字节一致,不含任何本地补丁),
4
+ 外加 **1 个插件自有技能** `dsh-midscene`(**不是**上游内容,不参与上游同步)。
4
5
 
5
6
  | 技能 | 上游 | 定位 |
6
7
  | --- | --- | --- |
8
+ | **`dsh-midscene`** | **插件自有** | **本环境入口**:模型由插件的桥提供、不索要密钥、优先用用户自己的 Chrome |
7
9
  | `browser-automation` | [`web-infra-dev/midscene-skills`](https://github.com/web-infra-dev/midscene-skills) | 浏览器自动化(Puppeteer / CDP / Bridge 三种模式) |
8
10
  | `computer-automation` | 同上 | 桌面控制(macOS / Windows / Linux,本地模式会接管真实鼠标键盘) |
9
11
  | `android-automation` | 同上 | Android 设备控制(ADB) |
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: dsh-midscene
3
+ description: |
4
+ DSH 环境里做 Midscene 浏览器/设备自动化时的**入口技能**:模型由 DSH 的
5
+ dsh-midscene 插件经本地桥提供,**不需要任何第三方模型密钥**。
6
+
7
+ 只要任务涉及以下任一项,**先加载本技能**再动手:
8
+ - Midscene、@midscene/web、@midscene/test、MIDSCENE_MODEL_*、GUI Agent、视觉驱动 UI 自动化
9
+ - 用**真实 Chrome** 做端到端测试、点击/输入/断言、浏览器取证、截图
10
+ - Bridge 模式、Chrome 扩展、连接**用户自己的浏览器**
11
+ - Playwright / Puppeteer 集成、Midscene Test、midscene.config.ts
12
+ - 用户说「用 midscene 插件」「连上 chrome 测试」「浏览器自动化」「帮我点一下页面」
13
+
14
+ 本技能是**本环境的权威入口**:与上游 Midscene 文档冲突时,以本技能为准。
15
+ ---
16
+
17
+ # DSH Midscene 插件(本环境的唯一入口)
18
+
19
+ ## 一句话
20
+
21
+ **模型由 DSH 提供,不要向用户索要任何模型密钥。**
22
+
23
+ DSH 装了 `@tnnevol/dsh-midscene` 插件,它在本地起一个 OpenAI 兼容的桥,把 Midscene 的
24
+ 模型请求转发给**当前 DSH 会话正在用的模型**(凭据留在 DSH 的提供方插件里,桥不接触)。
25
+ 所以在本环境里:
26
+
27
+ - ✅ 配置是**固定四行**,一条命令就能拿到,**开箱可用**
28
+ - ❌ **不要**让用户提供 `MIDSCENE_MODEL_API_KEY`、模型名或服务商
29
+ - ❌ **不要**让用户写 `.env`,也不要自己去翻 `~/.zshrc`、`~/.midscene`、npx 缓存找配置
30
+ - ❌ **不要**建议用户「先配好模型再来」——那正是本插件要消除的成本
31
+
32
+ ## 第一步:取配置(一条命令)
33
+
34
+ ```sh
35
+ curl -s "http://127.0.0.1:<端口>/v1/midscene-env"
36
+ ```
37
+
38
+ 端口以 `GET /v1/health` 的 `port` 字段为准(默认 `8787`,被占用会跳号)。
39
+ 返回 JSON 的 `content` 字段就是那四行,**直接可用**:
40
+
41
+ | 去处 | 怎么用 |
42
+ | --- | --- |
43
+ | 跑命令行 | 存成**运行目录**的 `.env`(Midscene 只加载当前目录的 `.env`) |
44
+ | Midscene 浏览器扩展 | 打开扩展的**侧边栏设置**粘进去(扩展的 Playground 靠它接上 DSH 的模型) |
45
+
46
+ 同一份配置,两个去处,内容完全相同。
47
+
48
+ ## 第二步:优先用用户自己的 Chrome(Bridge 模式)
49
+
50
+ 用户要的是**他自己的浏览器**——真实登录态、已装扩展、可见窗口,**不是**另起的 headless 实例:
51
+
52
+ ```sh
53
+ npx -y @midscene/web@1 --bridge connect --url "<目标地址>"
54
+ ```
55
+
56
+ - 需要用户已安装 **Midscene 浏览器扩展**;首次连接时在扩展弹窗点 **Allow**
57
+ - **静默操作**:连接标签页传 `activate: false`,否则会把用户正在看的页面切走
58
+ - 只有用户明确要「无头跑」「隔离环境」时才用 CDP / Puppeteer 路径
59
+
60
+ ## 第三步:按需操作并给出证据
61
+
62
+ 需要「看」→ `take_screenshot`;需要「动手」→ `act` / `assert`(或测试框架里的 `aiAct` / `aiAssert`)。
63
+ 报告结果时**附证据**(截图路径、断言输出、接口返回),不要只说「已完成」。
64
+
65
+ ## 常见弯路(都不要走)
66
+
67
+ | 弯路 | 为什么错 |
68
+ | --- | --- |
69
+ | 向用户索要模型 key | 桥已经提供;模型就是当前 DSH 会话的模型 |
70
+ | 翻 `~/.zshrc` / `~/.midscene` / npx 缓存找配置 | 那里不该有配置,翻也翻不到 |
71
+ | 自己起 Puppeteer/Chrome 实例 | 用户要的是**他本人的**浏览器与登录态 |
72
+ | 引用上游文档断言「插件不提供 env」 | 上游文档不知道本插件的桥存在,**以本技能为准** |
73
+ | 先花十几步探索环境再动手 | 第一步就是上面那条 `curl`,配置立刻到手 |
74
+
75
+ ## 端口与多实例
76
+
77
+ 同一台机器可能有多个 DSH 实例,各自一个桥、端口不同(`8787`、`8788`…)。
78
+ **以当前会话所属实例的桥为准**:从该实例的设置页或 `/v1/health` 确认端口,不要默认套用别的实例。