vcorp-chrome 1.8.1 → 2.2.0
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 +20 -1
- package/docs//345/215/217/350/256/256.md +53 -0
- package/docs//345/257/271/346/257/224.md +55 -53
- package/docs//346/241/214/351/235/242/345/256/214/345/205/250/346/250/241/345/274/217-/344/272/244/346/216/245.md +128 -0
- package/docs//350/203/275/345/212/233/345/214/205/344/270/216eda-/344/272/244/346/216/245.md +288 -0
- package/extension/background.js +20 -2
- package/extension/cloud-policy.js +23 -0
- package/extension/cloud.js +315 -6
- package/extension/desktop-policy.js +406 -0
- package/extension/desktop-render.js +81 -0
- package/extension/device.js +144 -0
- package/extension/manifest.json +41 -38
- package/extension/panel.html +114 -45
- package/extension/panel.js +469 -427
- package/native-host/full.js +217 -0
- package/native-host/host.js +155 -0
- package/package.json +53 -49
- package/src/bridge.js +31 -2
- package/src/cli.js +50 -1
- package/src/lib/desktop.js +171 -0
- package/src/lib/native-install.js +139 -0
- package/src/lib/paths.js +35 -0
- package/src/lib/rpc.js +7 -2
- package/src/mcp-server.js +135 -10
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# 能力包(load/call)与嘉立创EDA:交接文档
|
|
2
|
+
|
|
3
|
+
2026-10-30 完成,跨两个仓库:
|
|
4
|
+
|
|
5
|
+
- **vcorp-chrome**(本仓库,分支 `my-dev`):扩展里的能力包机制,以及 `eda` 能力包。
|
|
6
|
+
- **opus-video-maker**(分支 `main`):`opus-eda` 沙箱镜像、`easyeda` skill,以及网页表单里的「专用工具」。
|
|
7
|
+
|
|
8
|
+
第 4 节写怎么升级上游 easyeda-agent,平时最常用的是这一节。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. 现状
|
|
13
|
+
|
|
14
|
+
| 东西 | 位置 / 版本 | 状态 |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| 扩展 | `my-dev` @ `d8d8ba8`(Release 1.8.1) | 已推送 |
|
|
17
|
+
| eda 能力包 | `1.15.0+7a73a68.f00c96`(上游 easyeda-agent 1.15.0 @ `7a73a68`) | 随扩展发布 |
|
|
18
|
+
| 网页 | opus-video-maker `main` @ `a08d3aa` | Coolify 已部署,任务表单有「专用工具:嘉立创EDA(原理图、PCB)」 |
|
|
19
|
+
| 镜像 | `opus-eda`,宿主 192.168.68.2 | 2026-10-30 22:09 上线,1.21 GB,default_model=`openai/gpt-6.1-terra` |
|
|
20
|
+
|
|
21
|
+
已经实测通过:
|
|
22
|
+
|
|
23
|
+
- 只读任务:在测试工程上跑原理图检查,用时 3 分 45 秒,花费 $0.15,出了报告。
|
|
24
|
+
- 真实工程上的布线任务:1.8.1 修好 `project.find` 之后能跑通。
|
|
25
|
+
- 云端调用删除类 action 时的确认弹窗,三种结果都试过:同意、拒绝/超时、全局免确认。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. 整体怎么连起来
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
网页任务(kind=browse,load=eda)
|
|
33
|
+
→ gen.js selectImage 选 opus-eda 镜像(GBX_EDA_IMAGE,默认 opus-eda)
|
|
34
|
+
→ 沙箱里的 agent 读 skills/easyeda,用 browser.mjs 发 load / call
|
|
35
|
+
→ extlink.js(云端通道)→ 用户 Chrome 里的扩展 cloud.js
|
|
36
|
+
→ cloud-policy.js 查白名单 / 站点 / 标签页归属;confirm 清单里的 action 先弹窗
|
|
37
|
+
→ background.js:load 注入 extension/loads/eda/agent.js(MAIN world),call 用结构化克隆调用
|
|
38
|
+
→ 页面里的 window.__vcorpLoads.eda.call(action, payload) → 上游 runAction → 编辑器的 _EXTAPI_ROOT_
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### vcorp-chrome 里的文件
|
|
42
|
+
|
|
43
|
+
| 文件 | 作用 |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `loads/eda/load.json` | 清单。包括 hosts、waitFor、limits,`vendor`(上游仓库 + **固定 40 位 commit**),`actions.read` / `actions.write`(白名单,现在 63 / 90),`confirm`(14 个,云端调用前要用户点头),`denied`(15 个,带不开放的理由) |
|
|
46
|
+
| `loads/eda/setup.js` | 跑在上游代码之前,补全局变量:`eda = _EXTAPI_ROOT_`,再把 `E*_` / `I*_` 枚举从 `_EXTAPI_SCRIPT_SPACES_` 搬到 window |
|
|
47
|
+
| `loads/eda/entry.js` | 页面侧入口,注册 `__vcorpLoads.eda = { version, ready(), call() }`。<br>`ready()`:地址里带工程 id、但工程还没打开时回「没准备好」。<br>`call()`:再查一次白名单。<br>**`project.find` 在这里被覆盖了**,见 6.4 |
|
|
48
|
+
| `loads/eda/actions-md.py` | 生成 opus 那边的 `skills/easyeda/references/actions.md` |
|
|
49
|
+
| `scripts/build-loads.mjs` | `npm run build:loads [eda]`。按 commit 把上游 clone 到 `.cache/loads/eda@<12位>/`,用 esbuild 打成 `extension/loads/eda/agent.js`(不压缩),再写注册表 `extension/loads/index.js` |
|
|
50
|
+
| `extension/loads/**` | **构建产物,入库**。MV3 不允许远程代码,能力包只能是扩展包里的文件。`.gitattributes` 设了 `-text`,保证 sha256 在 Windows 上也一致 |
|
|
51
|
+
| `extension/load.js` | 校验:`loadDef`、`checkLoadParams`、`checkCallParams`、`actionKind`、`confirmText`、`loadHostAllowed` |
|
|
52
|
+
| `extension/background.js` | `load` / `call` 两个命令处理器,以及 `ensureLoad`:查 host,等 waitFor,版本不对就重新注入,再轮询 `ready()`,最长 30 秒 |
|
|
53
|
+
| `extension/cloud-policy.js`、`cloud.js` | 云端可以用 load / call。confirm 标记**只从注册表来**,服务器发来的 `confirm` 字段一律丢掉;`confirmCall()` 负责弹窗 |
|
|
54
|
+
| `src/mcp-server.js` | 本地 MCP 的 `load` / `call` 工具 |
|
|
55
|
+
| `test/loads.test.js` | 检查注册表和 load.json 是否同步(含版本指纹)、sha256、白名单里的 action 上游是否都有、confirm 是否都在 write 里、hosts 判定 |
|
|
56
|
+
|
|
57
|
+
### opus-video-maker 里的文件
|
|
58
|
+
|
|
59
|
+
| 文件 | 作用 |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `gooseboxd-image-eda/` | 镜像定义:image.json、Dockerfile(带自检)、CLAUDE.md、managed-settings.json |
|
|
62
|
+
| `skills/easyeda/SKILL.md` | 工作流程:读现状 → 出计划 → ask 确认 → 小批写入并读回 → 保存 → 写报告 |
|
|
63
|
+
| `skills/easyeda/references/actions.md` | **生成的**,不要手改 |
|
|
64
|
+
| `skills/easyeda/references/pitfalls.md` | 手写的踩坑记录 |
|
|
65
|
+
| `server.js` / `gen.js` / `images.js` / `public/index.html` | `load` 字段,以及按它选镜像 |
|
|
66
|
+
| `extlink.js`、`skills/user-browser/.../browser.mjs` | 云端命令清单里加了 load、call |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 3. 安全设计(改动前先看)
|
|
71
|
+
|
|
72
|
+
- **云端只能按名字点能力包、按白名单调 action,不能下发任何 JS。** 云端拿不到 `eval`。
|
|
73
|
+
- **白名单查三遍**:cloud-policy.js(云端),background.js(扩展),entry.js(页面)。
|
|
74
|
+
- **会删东西或整片重做的 action 由扩展自己弹确认**,不靠 agent 记得先 ask。全局免确认和「本任务内都同意」可以盖过它。拒绝或 5 分钟没人点,都回 `CONFIRM_DECLINED`。
|
|
75
|
+
- **能力包运行在用户已登录的编辑器页面里**,权限和用户一样,能读写他所有工程。所以每次升级上游都要审代码,见 4.2。
|
|
76
|
+
- 用户的真实设计上不做试验。测试工程:
|
|
77
|
+
- 名称:`API-Test-20261030`
|
|
78
|
+
- uuid:`da5c2483a06745698a2acd1085e5aebe`
|
|
79
|
+
- 原理图页:`34f3d48784a1ed0b`
|
|
80
|
+
- `HS-Toy-4G-Lierda`(`507f709b…`)是用户的真实设计。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 4. 上游 easyeda-agent 怎么更新
|
|
85
|
+
|
|
86
|
+
上游是 https://github.com/zhoushoujianwork/easyeda-agent (MIT AND Apache-2.0)。我们只用它的 `extension/src/actions.ts` 这一层(`runAction` 和 HANDLERS),**不用** `transport.ts`:那是它连自己本地服务的通道。
|
|
87
|
+
|
|
88
|
+
选它而不选 easyeda-api-skill,是因为后者的提交历史是假的:commit 时间全挤在半年前。
|
|
89
|
+
|
|
90
|
+
### 4.1 什么时候升级
|
|
91
|
+
|
|
92
|
+
- 上游修了我们碰到的问题,或者加了我们要的 action。
|
|
93
|
+
- 嘉立创EDA 编辑器大版本更新后,有 action 坏了。
|
|
94
|
+
- **不需要追着上游升级**。commit 是钉死的,不升级就不会变。
|
|
95
|
+
|
|
96
|
+
### 4.2 步骤
|
|
97
|
+
|
|
98
|
+
以下命令都在 vcorp-chrome 仓库根目录、Git Bash 里跑。`OLD` 是 `load.json` 里现在的 commit,`NEW` 是要升到的 commit。
|
|
99
|
+
|
|
100
|
+
**1. 拿到新 commit,看上游改了什么。** 缓存目录本身是完整的 clone,可以直接 fetch:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
C=.cache/loads/eda@7a73a6834d8d # 现在的缓存目录;没有就先 npm run build:loads eda
|
|
104
|
+
git -C $C fetch -q origin
|
|
105
|
+
git -C $C log --oneline origin/HEAD -5 # 选一个 NEW,记下完整 40 位
|
|
106
|
+
OLD=7a73a6834d8d76fd9765dd06f9d4e4bdd6b0556a; NEW=<40位>
|
|
107
|
+
git -C $C log --oneline $OLD..$NEW
|
|
108
|
+
git -C $C diff --stat $OLD $NEW -- extension/src extension/package.json internal/protocol/actions.go
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**2. 审代码。不能跳过。** 新代码会以用户身份在他的编辑器里运行。
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git -C $C diff $OLD $NEW -- extension/src | grep -nE '^\+.*(fetch\(|XMLHttpRequest|WebSocket|sendBeacon|new Function|eval\(|importScripts|https?://)'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
逐条看命中的地方,确认没有把数据发到编辑器以外的地方,也没有执行字符串代码。可疑就不升级。同时看一眼提交作者和时间是否正常。
|
|
118
|
+
|
|
119
|
+
**3. 对比 action 目录,决定白名单。**
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
for c in $OLD $NEW; do git -C $C show $c:internal/protocol/actions.go | sed -nE 's/.*Name:[[:space:]]*"([^"]+)".*/\1/p' | sort > /tmp/act-${c:0:7}.txt; done
|
|
123
|
+
comm -3 /tmp/act-${OLD:0:7}.txt /tmp/act-${NEW:0:7}.txt # 左列:上游删了的;右列:新加的
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- **上游删掉或改名的 action**:如果在我们的白名单里,就从 `load.json` 去掉或改名。不处理的话,`test/loads.test.js` 会报「上游没有 action X」。
|
|
127
|
+
- **新加的 action 默认不开放。** 白名单之外的 action 一律拒绝,所以新 action 不加进 `load.json` 就什么都不会发生。要开放时,按上游的 `Mutates` 决定放进 `read` 还是 `write`:
|
|
128
|
+
- 会删东西、整片重做或清空的,同时加进 `confirm`,值是给用户看的中文说明,格式是「要……」后面接的那半句。上游的 `NeedsConfirm` 可以参考,但 confirm 清单是我们自己定的。
|
|
129
|
+
- 会删整页、删板子、删库、导入导出工程文件、碰账号的,放进 `denied`,写明理由。
|
|
130
|
+
- 上游改了某个 action 的参数或语义,就去 opus 的 `pitfalls.md` 里查有没有相关条目,过时的删掉。
|
|
131
|
+
|
|
132
|
+
**4. 看依赖和全局变量有没有变。**
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
git -C $C diff $OLD $NEW -- extension/package.json
|
|
136
|
+
git -C $C diff $OLD $NEW -- extension/src | grep -nE "^\+import .* from '[^.]" # 新的第三方依赖
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- 上游的第三方依赖要装在**本仓库**的 devDependencies 里,现在只有 `jszip`。esbuild 通过 `nodePaths` 从本仓库的 `node_modules` 找依赖。没装的话构建会报 Could not resolve。
|
|
140
|
+
- 上游开始用新的全局变量(不在 `E*_` / `I*_` 枚举里)时,在 `setup.js` 里补上。
|
|
141
|
+
- `entry.js` 用的是 `import { runAction } from '@upstream/extension/src/actions'`。上游改了文件路径或导出名,这里跟着改,同时改 `load.json` 的 `vendor.entry`。
|
|
142
|
+
|
|
143
|
+
**5. 改 `load.json`,重新构建,跑测试。**
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# load.json: vendor.commit = NEW(完整 40 位);vendor.version = 上游 extension/package.json 的 version
|
|
147
|
+
git -C $C show $NEW:extension/package.json | grep '"version"'
|
|
148
|
+
npm run build:loads eda # 会新 clone 一份到 .cache/loads/eda@<NEW前12位>/
|
|
149
|
+
npm test
|
|
150
|
+
git status --short # 应该是 load.json、extension/loads/eda/agent.js、extension/loads/index.js,可能还有 LICENSE/NOTICE
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
能力包的版本号由构建脚本自动生成,格式是 `上游版本+commit前7位.适配层指纹`,不用手改。
|
|
154
|
+
|
|
155
|
+
**6. 检查 `project.find` 的覆盖还要不要(见 6.4)。** 如果上游修好了不带 `teamUuid` 时清单为空的问题,就删掉 `entry.js` 里的 `findAcrossTeams`,改回直接走 `runAction`。
|
|
156
|
+
|
|
157
|
+
**7. 在真实浏览器里验证。** 先在 `chrome://extensions` 重新加载 unpacked 扩展,然后在**测试工程**上跑:
|
|
158
|
+
|
|
159
|
+
- `load eda`:回执里的版本号应该是新的。
|
|
160
|
+
- 只读 action:`project.current`、`schematic.components.list`、`schematic.check`;有 PCB 时再跑 `pcb.drc.check`。
|
|
161
|
+
- 本次改动涉及的 action。
|
|
162
|
+
- 放一个元件再删掉,然后 `schematic.save`。
|
|
163
|
+
|
|
164
|
+
放元件可以用的器件:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{"libraryUuid":"0819f05c4eef4c71ace90d822a990e87","uuid":"b948db94476e4027ac8953235755ec96","x":900,"y":500}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
注意:编辑器加载后的前 20 秒左右,调用可能卡到超时,先等一会儿再验证(6.2)。
|
|
171
|
+
|
|
172
|
+
**8. 重新生成 opus 那边的 actions.md。**
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
python loads/eda/actions-md.py .cache/loads/eda@<NEW前12位>/internal/protocol/actions.go loads/eda/load.json ../opus-video-maker/skills/easyeda/references/actions.md
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
脚本最后会打印 `missing from catalog: [...]`,应该是空列表。不空说明白名单里有 action 不在上游目录里,回到第 3 步检查。
|
|
179
|
+
|
|
180
|
+
**9. 发布。顺序不能反。**
|
|
181
|
+
|
|
182
|
+
1. **先发扩展。**
|
|
183
|
+
- 三处版本号一起改(`package.json`、`package-lock.json`、`extension/manifest.json`),提交、打 tag、推 `my-dev`。
|
|
184
|
+
- 打商店包并提交审核,`npm publish`。
|
|
185
|
+
- 详细步骤见 opus-video-maker 的 `docs/DEPLOY.md` 第 3 节。
|
|
186
|
+
2. **再更新镜像。** 只有 actions.md 或 skill 变了才需要:opus-video-maker 提交、推 `main`,然后在宿主上 build、push(见第 5 节)。
|
|
187
|
+
|
|
188
|
+
原因:新开放的 action 在旧扩展里会被白名单拒绝(`LOAD_DENIED`)。商店审核要几小时到几天,这期间旧扩展的用户还会存在,所以 skill 里不要写「必须用某个新 action 才能完成」这类说法。
|
|
189
|
+
|
|
190
|
+
### 4.3 回滚
|
|
191
|
+
|
|
192
|
+
把 `load.json` 的 `vendor.commit` 和 `version` 改回去,重新构建,发一个新版本的扩展。`extension/loads/` 是入库的,直接 revert 那个提交也可以。
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 5. opus-eda 镜像怎么重建
|
|
197
|
+
|
|
198
|
+
宿主是 `wizard@192.168.68.2`,目录在 `~/ovm-images/`。
|
|
199
|
+
|
|
200
|
+
**宿主上的 `opus-video-maker` 不是 git 仓库**,是导出的副本,所以 `remote.sh update` 用不了。按下面的办法送代码过去,旧副本改名留作回滚:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# 本机,D:\working\opus-video-maker,main 已经推送
|
|
204
|
+
git -c core.autocrlf=false archive --format=tar main | ssh wizard@192.168.68.2 'set -e; cd ~/ovm-images; rm -rf opus-video-maker.new; mkdir opus-video-maker.new; tar -x -C opus-video-maker.new; mv opus-video-maker opus-video-maker.prev-$(date +%m%d%H%M); mv opus-video-maker.new opus-video-maker'
|
|
205
|
+
|
|
206
|
+
# 构建 + 推送(约 1–2 分钟,基础层走缓存)
|
|
207
|
+
ssh wizard@192.168.68.2 'cd ~/ovm-images && bash opus-video-maker/gooseboxd-host/remote.sh build opus-eda && bash opus-video-maker/gooseboxd-host/remote.sh push opus-eda --model openai/gpt-6.1-terra'
|
|
208
|
+
ssh wizard@192.168.68.2 'cd ~/ovm-images && bash opus-video-maker/gooseboxd-host/remote.sh status' | grep -A2 opus-eda
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- `core.autocrlf=false` 一定要带,否则脚本会变成 CRLF。
|
|
212
|
+
- 2026-10-30 的旧副本还留在 `opus-video-maker.prev-01ca40e`。
|
|
213
|
+
- `skills/user-browser` 或 `huashu-chrome-notes` 改了的话,五张浏览器类镜像都要重建(见 DEPLOY.md 2.5)。
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 6. 实测踩过的坑(都已处理)
|
|
218
|
+
|
|
219
|
+
### 6.1 嘉立创EDA 加载时会整页重写,扩展的浮层被挂丢
|
|
220
|
+
|
|
221
|
+
编辑器的 `loader.js` 用 `document.open(); document.writeln(…); document.close()` 换掉整个 `<html>`。window 和扩展 isolated world 里的状态都还在,但 `mark.js` 挂上去的浮层节点脱离了文档。
|
|
222
|
+
|
|
223
|
+
结果是:EDA 页面上从来没有出现过边框和光标,确认弹窗画进了一个看不见的节点,后台一直等到超时。
|
|
224
|
+
|
|
225
|
+
修复在 `8784cc4`:`ensureHost` 发现节点脱离了文档就重新挂回去。以后别的站点如果出现「弹窗看不见、ask 超时」,先查这一点。
|
|
226
|
+
|
|
227
|
+
### 6.2 编辑器刚打开的头 20 秒左右不稳定
|
|
228
|
+
|
|
229
|
+
这段时间 `schematic.page.open`、`schematic.component.place` 可能卡到超时,不返回也不报错。等一会儿再调就正常。
|
|
230
|
+
|
|
231
|
+
这一条已写进 `pitfalls.md`:`load` 之后先等 20 秒,前几次调用的 timeout 给 30000。
|
|
232
|
+
|
|
233
|
+
### 6.3 后台标签页里 `document.current` 会误报
|
|
234
|
+
|
|
235
|
+
后台标签页里它可能报 `No active document`,其实页面是开着的。判断页面开没开,用 `schematic.components.list` 这类读操作。已写进 `pitfalls.md`。
|
|
236
|
+
|
|
237
|
+
### 6.4 `project.find` 不给 teamUuid 时永远找不到(1.8.1 修)
|
|
238
|
+
|
|
239
|
+
上游在没有 `teamUuid` 时调 `getAllProjectsUuid()`,V4 上这个不带团队的清单是空的。能力包里也没有哪个 action 能拿到 teamUuid。
|
|
240
|
+
|
|
241
|
+
第一次真实布线任务就卡在这里:agent 正确地没去猜工程,停下来向用户要链接。
|
|
242
|
+
|
|
243
|
+
`entry.js` 里的 `findAcrossTeams` 改成遍历用户所在的每个团队,查团队根目录和每个文件夹,返回结构和判定规则照上游。
|
|
244
|
+
|
|
245
|
+
**文件夹那条路还没有用真实数据验证过**:用户的工程都不在文件夹里。
|
|
246
|
+
|
|
247
|
+
### 6.5 其他
|
|
248
|
+
|
|
249
|
+
- **刷新页面后的时序**:页面刷新后 `_EXTAPI_ROOT_` 先出现,工程过几秒才打开。由 `ready()` 回「没准备好」、`ensureLoad` 轮询来兜住。
|
|
250
|
+
- **适配层改动要能被页面感知**:只改了 entry.js 或 setup.js 时,页面上那份旧代码也要被换掉,所以版本号里带了适配层的指纹。
|
|
251
|
+
- **从没切到前台过的后台 EDA 标签页**:有一次 `load` 回了 `LOAD_NOT_READY`(工程一直没打开),之后没能复现,后台标签页 `load` 一般 8–11 秒就成功。如果再出现,先把标签页切到前台看一眼。
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 7. 开发时的验证工具
|
|
256
|
+
|
|
257
|
+
- **单元测试**:vcorp-chrome 跑 `npm test`,应该 146 个全过。opus-video-maker 跑 `npm test`,有 3 个 sandbox 归档用例在干净代码上也失败,是旧问题。
|
|
258
|
+
- **本机直连 bridge**:写一个最小 WebSocket 客户端,读 `~/.vcorp-chrome/bridge.json` 拿端口和 token,发 `hello` 再发 `cmd`。不要用 `BridgeClient.connect()`,它会杀掉版本不一致的桥。
|
|
259
|
+
- **审计日志**:`~/.vcorp-chrome/audit.jsonl` 记了每条命令和结果,时间是 UTC。排查「卡住了」时先看这里,能分清是扩展慢,还是客户端没收到回包。
|
|
260
|
+
- **在 Node 里跑真实的 cloud.js**:mock 掉 `chrome.storage`,假装一个服务器 socket,`deps.run` 转发到 bridge。这样可以不经过线上服务器测云端确认。
|
|
261
|
+
- **假服务器必须回 `ping`**,否则 cloud.js 过了 `DEAD_MS` 会主动断开,长时间等确认之后的回包会丢掉。
|
|
262
|
+
- 这套脚本是临时写的,没有入库。
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 8. 还没做的
|
|
267
|
+
|
|
268
|
+
- `project.find` 的文件夹分支,等有在文件夹里的工程时验证一次。
|
|
269
|
+
- `pro.easyeda.com`(国际版,V3)只做过早期探索,没有用能力包完整跑过。
|
|
270
|
+
- 能力包机制是通用的,加新站点照 `loads/eda/` 的结构来:
|
|
271
|
+
1. 写 `load.json`、`setup.js`、`entry.js`。
|
|
272
|
+
2. 跑 `npm run build:loads <name>`。
|
|
273
|
+
3. opus 那边在 `BROWSE_LOADS` 加名字,加镜像目录和环境变量,表单里加选项。
|
|
274
|
+
|
|
275
|
+
## 9. 相关提交
|
|
276
|
+
|
|
277
|
+
- vcorp-chrome `my-dev`:
|
|
278
|
+
- `dc87780` load/call 和 eda 能力包
|
|
279
|
+
- `e552e12` 刷新后等工程打开、版本带适配层指纹
|
|
280
|
+
- `4f57bd2` 云端确认
|
|
281
|
+
- `8784cc4` mark.js 被整页重写后的修复
|
|
282
|
+
- `07f111f` project.find 跨团队查找
|
|
283
|
+
- `d8d8ba8` Release 1.8.1
|
|
284
|
+
- opus-video-maker `main`:
|
|
285
|
+
- `f75885b` opus-eda 镜像
|
|
286
|
+
- `20e2e3e` skill:⚠ action 由扩展确认
|
|
287
|
+
- `6db8cdf` pitfalls:启动时序
|
|
288
|
+
- `a08d3aa` 合并
|
package/extension/background.js
CHANGED
|
@@ -659,6 +659,22 @@ async function runCloud(cmd, params, tabId, sid, label) {
|
|
|
659
659
|
|
|
660
660
|
const liveList = async () => (await chrome.storage.session.get(LIVE_SIDS))[LIVE_SIDS] || [];
|
|
661
661
|
|
|
662
|
+
// 桥的名单只有桥能更新,而桥一死就没人再推了——扩展不自己清,死会话会一直算在线,
|
|
663
|
+
// 它们留在页面上的标记也摘不掉。只在确认重连失败之后调(见 hc-keepalive),
|
|
664
|
+
// 瞬断时不能清,否则标记会闪一下。桥回来时会在 welcome 后补一份真名单。
|
|
665
|
+
async function dropBridgeSessions() {
|
|
666
|
+
const { [BRIDGE_SIDS]: bridge = [] } = await chrome.storage.session.get(BRIDGE_SIDS).catch(() => ({}));
|
|
667
|
+
if (bridge.length) await onBridgeEvent({ event: 'sessions', live: [] });
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// 面板显示用:没连着桥时只有云端名单可信。比 dropBridgeSessions 更早生效,
|
|
671
|
+
// 否则「未连接」旁边最多还会挂 30 秒已经过期的本机 agent。
|
|
672
|
+
async function shownSids() {
|
|
673
|
+
if (await connected()) return liveList();
|
|
674
|
+
const { [CLOUD_SIDS]: cloud = [] } = await chrome.storage.session.get(CLOUD_SIDS);
|
|
675
|
+
return cloud;
|
|
676
|
+
}
|
|
677
|
+
|
|
662
678
|
// 这个标签页此刻的主人们。只算**还连着**的会话——判据和 liveOwnersOf 一致:
|
|
663
679
|
// 已结束的会话留下的槽不算数,否则关掉一个终端窗口之后,它的标记会永远挂在那儿。
|
|
664
680
|
//
|
|
@@ -2857,7 +2873,9 @@ chrome.alarms?.onAlarm.addListener(async () => {
|
|
|
2857
2873
|
const kicked = await chrome.runtime.sendMessage({ __hcBridge: 'kick' }).catch(() => null);
|
|
2858
2874
|
if (kicked?.connected) return setBadge(true);
|
|
2859
2875
|
await directConnect();
|
|
2860
|
-
|
|
2876
|
+
const up = await connected();
|
|
2877
|
+
setBadge(up);
|
|
2878
|
+
if (!up) await dropBridgeSessions();
|
|
2861
2879
|
});
|
|
2862
2880
|
chrome.tabs.onRemoved.addListener(async (tabId) => {
|
|
2863
2881
|
// 全局槽 + 所有会话级槽一次扫清,别留指向死 tab 的槽
|
|
@@ -2968,7 +2986,7 @@ chrome.runtime.onMessage.addListener((m, sender, sendResponse) => {
|
|
|
2968
2986
|
if (m.__hcPopup === 'sessions') {
|
|
2969
2987
|
(async () => {
|
|
2970
2988
|
try {
|
|
2971
|
-
const [all, lives] = await Promise.all([chrome.storage.local.get(null),
|
|
2989
|
+
const [all, lives] = await Promise.all([chrome.storage.local.get(null), shownSids()]);
|
|
2972
2990
|
const clients = await chrome.storage.session.get(lives.map(sidClientKey));
|
|
2973
2991
|
const rows = [];
|
|
2974
2992
|
for (const sid of lives) {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// 自动允许只放行这一个任务,不往名单里加。一个任务新批的网站最多 SITES_MAX 个。
|
|
25
25
|
|
|
26
26
|
import { checkLoadParams, checkCallParams } from './load.js';
|
|
27
|
+
import { cleanApps } from './desktop-policy.js';
|
|
27
28
|
|
|
28
29
|
export const DEFAULT_ORIGIN = 'https://maker.vcorp.ai';
|
|
29
30
|
|
|
@@ -37,6 +38,11 @@ export const CLOUD_CMDS = new Set([
|
|
|
37
38
|
// 能力包:只能按名字装扩展包里自带的包、按白名单调 action,payload 是纯数据(见 load.js)。
|
|
38
39
|
// 这是云端拿到「有类型的站点能力」的唯一途径,eval 依旧没有。
|
|
39
40
|
'load', 'call',
|
|
41
|
+
// 桌面:经同机的原生宿主转给 agent-desktop。按任务批准应用(apps),规矩在 desktop-policy.js;
|
|
42
|
+
// 用户没在侧栏里启用桌面能力时宿主根本连不上,这几条只会报「没有桌面设备」。
|
|
43
|
+
'desktop', 'apps', 'devices',
|
|
44
|
+
// 桌面完全模式:跑命令、读写文件。开关在用户本机,宿主每条现查;没开时这两条只会报 FULL_OFF
|
|
45
|
+
'shell', 'file',
|
|
40
46
|
]);
|
|
41
47
|
|
|
42
48
|
// ---------- 站点申请 ----------
|
|
@@ -348,6 +354,23 @@ export function checkCommand(cmd, params, { hosts, origin }) {
|
|
|
348
354
|
if (!sites.length && !bad.length) throw err('CLOUD_DENIED', 'sites 要给 add:要访问的网站,如 ["zhipin.com"]');
|
|
349
355
|
return { add: sites, bad, reason: String(p.reason || '').replace(/\s+/g, ' ').trim().slice(0, 200) };
|
|
350
356
|
}
|
|
357
|
+
case 'apps': {
|
|
358
|
+
// add 可以是空的:那是在问「本任务能操作哪些应用」
|
|
359
|
+
const { apps, bad, overflow } = cleanApps(p.add);
|
|
360
|
+
return { add: apps, bad, overflow, reason: String(p.reason || '').replace(/\s+/g, ' ').trim().slice(0, 200) };
|
|
361
|
+
}
|
|
362
|
+
// desktop 的参数要对着本任务的应用和快照户口审,在 cloud.js 里过 checkDesktop
|
|
363
|
+
case 'desktop':
|
|
364
|
+
return p;
|
|
365
|
+
case 'devices':
|
|
366
|
+
return {};
|
|
367
|
+
// 原样交给宿主,由完全模式开关决定能不能跑
|
|
368
|
+
case 'shell':
|
|
369
|
+
if (typeof p.command !== 'string' || !p.command.trim()) throw err('CLOUD_DENIED', 'shell 要给 command');
|
|
370
|
+
return p;
|
|
371
|
+
case 'file':
|
|
372
|
+
if (typeof p.path !== 'string' || !p.path.trim()) throw err('CLOUD_DENIED', 'file 要给 path');
|
|
373
|
+
return p;
|
|
351
374
|
case 'folders': {
|
|
352
375
|
// use 可以是空的:那是在问「这个浏览器挂了哪些」,不是在申请。
|
|
353
376
|
const { names, bad } = cleanFolderNames(p.use);
|