vcorp-chrome 2.2.1 → 2.3.5
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 +1 -0
- package/docs/macOS/346/241/214/351/235/242/350/203/275/345/212/233.md +109 -0
- package/docs//344/272/221/347/253/257/345/217/221/345/270/203.md +2 -2
- package/extension/background.js +11 -4
- package/extension/cloud-policy.js +13 -4
- package/extension/cloud.js +1385 -1323
- package/extension/content.js +41 -8
- package/extension/device.js +35 -0
- package/extension/folders.js +30 -0
- package/extension/manifest.json +1 -1
- package/extension/mount.html +1 -1
- package/extension/panel.html +15 -2
- package/extension/panel.js +44 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -441,6 +441,7 @@ offscreen 万一建不起来,扩展不能整个哑掉。
|
|
|
441
441
|
`npx vcorp-chrome desktop install`(登记原生消息宿主,只允许本扩展调用)。每个任务要操作的应用
|
|
442
442
|
逐个点头;终端、注册表、编辑器这类能拿到整台机器的应用云端一律不碰;云端没有截图、剪贴板读取、
|
|
443
443
|
通知和真实键鼠。宿主那边把参数再审一遍,浏览器主窗口的标题和树不出这台电脑。
|
|
444
|
+
macOS 的开启步骤见 [在 macOS 上开启桌面能力](docs/macOS桌面能力.md)。
|
|
444
445
|
|
|
445
446
|
还没做完的,如实说:
|
|
446
447
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 在 macOS 上开启桌面能力
|
|
2
|
+
|
|
3
|
+
开启后,maker.vcorp.ai 的任务可以操作你 Mac 上的应用,比如替你在某个桌面软件里点按钮、填内容。每个任务要用哪些应用,都会先问你。
|
|
4
|
+
|
|
5
|
+
只想让 AI 操作网页的话,不需要做这些,桌面能力默认是关着的。
|
|
6
|
+
|
|
7
|
+
全程大约 10 分钟,要用到一次终端。照着复制命令就行,不需要懂命令行。
|
|
8
|
+
|
|
9
|
+
## 开始之前
|
|
10
|
+
|
|
11
|
+
- 已经装好 vcorp-chrome 扩展,并和 maker.vcorp.ai 配对。步骤见[云端发布](云端发布.md)。
|
|
12
|
+
- 这台 Mac 上打开过 Chrome。支持 Apple 芯片和 Intel 芯片的 Mac。
|
|
13
|
+
|
|
14
|
+
## 第 1 步:安装 Node.js
|
|
15
|
+
|
|
16
|
+
已经装过的话,确认版本在 20 以上,然后跳到第 2 步。
|
|
17
|
+
|
|
18
|
+
1. 打开 <https://nodejs.org>,下载 **LTS** 版本的 macOS 安装包(`.pkg`)。
|
|
19
|
+
2. 双击安装包,一路点“继续”。
|
|
20
|
+
3. 打开“终端”:按 `Command + 空格`,输入 `终端` 或 `Terminal`,按回车。
|
|
21
|
+
4. 输入下面这条命令,按回车:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node -v
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
显示 `v20` 或更大的数字就对了。
|
|
28
|
+
|
|
29
|
+
## 第 2 步:安装桌面组件
|
|
30
|
+
|
|
31
|
+
在终端运行:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx vcorp-chrome@latest desktop install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
如果问 `Ok to proceed?`,输入 `y` 再按回车。
|
|
38
|
+
|
|
39
|
+
看到 `已登记原生消息宿主` 和带 `✅` 的 Chrome 目录,就是装好了。输出里显示的扩展 ID 可能和 `chrome://extensions` 里的不一样,这是正常的,商店版也已经放行。
|
|
40
|
+
|
|
41
|
+
输出末尾的检查可能显示 `agent-desktop 用不了`。这里测的是从终端启动的情况,先继续往下做,**最终以第 4 步里侧栏的显示为准**。
|
|
42
|
+
|
|
43
|
+
## 第 3 步:在扩展里启用
|
|
44
|
+
|
|
45
|
+
1. 点 Chrome 工具栏上的 vcorp-chrome 图标,打开侧栏。
|
|
46
|
+
2. 找到 **桌面应用**,点 **启用桌面能力**。
|
|
47
|
+
3. Chrome 询问权限时点 **允许**。
|
|
48
|
+
4. 第一次启用时扩展会重载,侧栏可能会关掉,重新点图标打开即可。
|
|
49
|
+
|
|
50
|
+
## 第 4 步:给 Chrome 授权“辅助功能”
|
|
51
|
+
|
|
52
|
+
macOS 要求你亲自授权,别的程序替代不了这一步。
|
|
53
|
+
|
|
54
|
+
1. 打开 **系统设置 → 隐私与安全性 → 辅助功能**。
|
|
55
|
+
2. 点列表下方的 **+**,在“应用程序”里选 **Google Chrome**,确认后打开它旁边的开关。需要时输入密码或按 Touch ID。
|
|
56
|
+
3. 如果列表里单独出现了 `agent-desktop`,也把它打开。
|
|
57
|
+
|
|
58
|
+
为什么是 Chrome:桌面组件由 Chrome 启动,macOS 把权限算在启动它的应用头上。
|
|
59
|
+
|
|
60
|
+
然后回到侧栏刷新状态:点 **停用**,再点一次 **启用桌面能力**。启用时才会重新检查权限,所以这一步不能省。
|
|
61
|
+
|
|
62
|
+
**怎么算成功:** 侧栏显示 `已启用 · desktop-…`,后面没有 `agent-desktop 用不了`。
|
|
63
|
+
|
|
64
|
+
## 使用时会发生什么
|
|
65
|
+
|
|
66
|
+
- 任务想用某个应用时,Chrome 会弹通知,侧栏的任务卡片上也有 **允许 / 拒绝**。“自动允许新任务”管不到这里,每个任务都要逐个点头。
|
|
67
|
+
- 涉及发送、删除这类操作时,可能还会再问你一次。
|
|
68
|
+
- 云端任务不能操作终端、系统设置和代码编辑器。任何情况下都不碰密码管理器和“钥匙串访问”。
|
|
69
|
+
- 任务运行时 Chrome 要开着,Mac 不能睡眠。
|
|
70
|
+
|
|
71
|
+
## 常见问题
|
|
72
|
+
|
|
73
|
+
| 你看到的 | 原因 | 怎么办 |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| 终端提示 `command not found: npx` | Node.js 没装好,或终端是安装前打开的 | 关掉终端重开;还不行就重做第 1 步 |
|
|
76
|
+
| `没找到已安装的 Chrome / Edge / Chromium 配置目录` | 这台 Mac 上还没打开过浏览器 | 打开一次 Chrome,再运行第 2 步的命令 |
|
|
77
|
+
| 侧栏显示 `Specified native messaging host not found` | 第 2 步没做,或没做成功 | 运行第 2 步的命令 |
|
|
78
|
+
| 侧栏显示 `Access to the specified native messaging host is forbidden` | 桌面组件是旧版本 | 运行第 2 步的命令,一定要带 `@latest` |
|
|
79
|
+
| 侧栏显示 `Failed to start native messaging host` 或 `Native host has exited`,或者原来好好的突然不行了 | 升级过 Node.js,或 npx 的缓存被清理了 | 运行第 2 步的命令,再回侧栏启用 |
|
|
80
|
+
| `没有拿到权限,桌面能力保持关闭` | 第 3 步的权限询问点了拒绝 | 再点一次 **启用桌面能力**,选允许 |
|
|
81
|
+
| `已启用 · … agent-desktop 用不了` | 还没授权,或授权后没刷新状态 | 重做第 4 步,别漏了最后的“停用再启用” |
|
|
82
|
+
| 任务提示 `云端不能用…` 或 `云端任务不能操作…` | 这个命令或应用不开放给云端任务 | 这是安全限制,不是故障 |
|
|
83
|
+
| 安装扩展时提示 `not trusted by Enhanced Safe Browsing` | Chrome 对较新开发者的常规提示,和桌面能力无关 | 确认是官方商店页面后,继续安装即可 |
|
|
84
|
+
|
|
85
|
+
还没解决的话,把下面两条命令的输出和侧栏显示的文字一起发给我们:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx vcorp-chrome@latest desktop status
|
|
89
|
+
npx vcorp-chrome@latest doctor
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 关闭和卸载
|
|
93
|
+
|
|
94
|
+
- **暂时不用:** 在侧栏点 **停用**。
|
|
95
|
+
- **彻底移除:** 先点 **停用**,再运行下面的命令,最后在“辅助功能”列表里关掉 Google Chrome。
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx vcorp-chrome@latest desktop uninstall
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 关于“完全模式”
|
|
102
|
+
|
|
103
|
+
`npx vcorp-chrome desktop full on` 会打开完全模式。打开后,配对服务器上的任务可以在这台 Mac 上运行任意命令、读写任意文件、操作任何应用,也不再逐个问你,上面说的这些限制都不再生效。
|
|
104
|
+
|
|
105
|
+
**普通使用不需要打开,也不建议打开。** 如果开过,用这条命令关掉:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx vcorp-chrome desktop full off
|
|
109
|
+
```
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- 它只能在你允许过的网站上、在它自己开的标签页里点点填填;你的其它页面它一个都看不到。
|
|
9
9
|
- 每个新任务都要你点「允许」,除非你打开了「自动允许新任务」。随时可以暂停、收回、断开。
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
操作网页不需要装 Node.js,也不需要终端。只有要操作电脑上的桌面应用时才需要,macOS 用户见 [在 macOS 上开启桌面能力](macOS桌面能力.md)。
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
之后某个任务要读这个文件夹时,浏览器再弹一次通知「想读取你的本地文件夹 → 知识库」,侧栏的任务卡片上同样有允许 / 不允许。「自动允许」管不到这里。
|
|
71
71
|
|
|
72
72
|
- 只读。扩展不会改、不会删你的文件。
|
|
73
|
-
- 只读这些类型:Markdown、文本、PDF、Word(.docx)、CSV、JSON、HTML
|
|
73
|
+
- 只读这些类型:Markdown、文本、PDF、Word(.docx)、Excel(.xlsx)、PowerPoint(.pptx)、CSV、JSON、HTML,以及 PNG/JPG/GIF/WebP 图片。隐藏文件、`node_modules`、私钥这类没有这些扩展名的文件不会被列出,也不会被传走。
|
|
74
74
|
- 单个文件最大 32MB,一个任务累计最多 256MB。
|
|
75
75
|
- 服务器只知道显示名,不知道 `D:\...` 这样的路径。
|
|
76
76
|
- 浏览器重启之后,读取权限可能会失效。侧栏或弹出的窗口里点「恢复访问」,再点一次允许即可,不用重新选文件夹。
|
package/extension/background.js
CHANGED
|
@@ -995,6 +995,8 @@ function routeOf(p) {
|
|
|
995
995
|
const contentBudget = (p) => {
|
|
996
996
|
if (p?.__hc === 'wait') return (Number(p.timeout) || 10000) + 5000;
|
|
997
997
|
if (p?.__hc === 'scroll') return Math.min(Number(p.times) || 1, 50) * (Number(p.wait) || 700) + 10000;
|
|
998
|
+
// 云端上传要先在页面里取完整个视频。fetchMs 是扩展按素材大小算的,内容脚本到点自己取消
|
|
999
|
+
if (p?.__hc === 'upload' && p.fetchMs) return p.fetchMs + 15000;
|
|
998
1000
|
return 15000;
|
|
999
1001
|
};
|
|
1000
1002
|
|
|
@@ -1007,7 +1009,7 @@ const contentBudget = (p) => {
|
|
|
1007
1009
|
// 忙 —— 页面在跑重脚本,改走 network / fetch 读数据比干等有用
|
|
1008
1010
|
const STALL = Symbol('stall');
|
|
1009
1011
|
|
|
1010
|
-
async function diagnoseStall(tabId, budget) {
|
|
1012
|
+
async function diagnoseStall(tabId, budget, cmd) {
|
|
1011
1013
|
const secs = Math.round(budget / 1000);
|
|
1012
1014
|
const tab = await chrome.tabs.get(tabId).catch(() => null);
|
|
1013
1015
|
if (!tab) return err('NO_TAB', '标签页已经关了');
|
|
@@ -1024,6 +1026,9 @@ async function diagnoseStall(tabId, budget) {
|
|
|
1024
1026
|
return err('DIALOG_BLOCKING', `页面上开着一个 ${dialog.type} 对话框:「${String(dialog.message || '').slice(0, 120)}」。`
|
|
1025
1027
|
+ `它挡住了页面的全部脚本。这个框要由用户决定点哪个按钮——用 ask 把框里的原文告诉他,请他处理。`);
|
|
1026
1028
|
}
|
|
1029
|
+
if (cmd === 'upload') {
|
|
1030
|
+
return err('PAGE_BUSY', `上传 ${secs} 秒没有回音,结果不明:文件可能稍后才进页面。先 wait 再 snapshot 看上传框,确认没进去才重试,不要连续重试。`);
|
|
1031
|
+
}
|
|
1027
1032
|
return err('PAGE_BUSY', `页面脚本 ${secs} 秒没有响应,但没有发现对话框——页面多半在跑重脚本,或者后台页被浏览器限流。`
|
|
1028
1033
|
+ `别让用户去关什么框。要数据就改走 network / fetch;要操作就稍后重试一次,仍不行再 tabs(action:"select", focus:true) 拉到前台。`);
|
|
1029
1034
|
}
|
|
@@ -1051,10 +1056,10 @@ async function toContent(tabId, payload, frameId = 0) {
|
|
|
1051
1056
|
r = await send();
|
|
1052
1057
|
} catch (e2) {
|
|
1053
1058
|
if (e2 !== STALL) throw e2?.code ? e2 : err('TIMEOUT', `页面无响应(${e2?.message})`);
|
|
1054
|
-
throw await diagnoseStall(tabId, budget);
|
|
1059
|
+
throw await diagnoseStall(tabId, budget, payload.__hc);
|
|
1055
1060
|
}
|
|
1056
1061
|
} else {
|
|
1057
|
-
throw await diagnoseStall(tabId, budget);
|
|
1062
|
+
throw await diagnoseStall(tabId, budget, payload.__hc);
|
|
1058
1063
|
}
|
|
1059
1064
|
} else {
|
|
1060
1065
|
if (e?.code) throw e;
|
|
@@ -2002,7 +2007,9 @@ const HANDLERS = {
|
|
|
2002
2007
|
await cdp.forceFrame(id);
|
|
2003
2008
|
await domQuiet(id, { quiet: 300, cap: 1500, deadline: Date.now() + 2500 });
|
|
2004
2009
|
const again = await guardCreds(id, await snapshotAll(id, p));
|
|
2005
|
-
|
|
2010
|
+
// 重拍过,页面上生效的就是这一份。不管内容多不多都得返回它:还给 agent 第一份的话,
|
|
2011
|
+
// 它手里的 snapshotId 已经作废,下一次点击必报 STALE_SNAPSHOT,而页面其实没变
|
|
2012
|
+
snap = sparse(again.text) ? again : { ...again, text: '(这一页在后台没渲染完,已自动推了一帧再拍)\n' + again.text };
|
|
2006
2013
|
} catch { /* 推不动就按原样返回 */ }
|
|
2007
2014
|
}
|
|
2008
2015
|
return drift ? { ...snap, text: drift + '\n' + snap.text } : snap;
|
|
@@ -95,6 +95,8 @@ const ACT_STEPS = new Set(['click', 'type', 'fill', 'select', 'key', 'scroll', '
|
|
|
95
95
|
export const TASK_CMD_CAP = 1500; // 一个任务最多这么多条命令,防失控循环把账号刷进风控
|
|
96
96
|
export const TASK_IDLE_MS = 3 * 3600e3; // 批准过的任务闲置 3 小时作废,下次要重新点头
|
|
97
97
|
export const MEDIA_MAX = 2 * 1024 ** 3; // 素材上限,与 maker 那侧的上传上限一致
|
|
98
|
+
// 页面取一份素材最多等多久:按 1MB/s 估,封顶 7 分钟,留在服务器 8 分钟的命令超时之内。
|
|
99
|
+
export const mediaFetchMs = (size) => Math.min(30000 + Math.ceil(size / 1048576) * 1000, 7 * 60 * 1000);
|
|
98
100
|
|
|
99
101
|
// 本地文件夹。数字和 opus-video-maker/extlink.js 的 PULL_MAX 必须一致:
|
|
100
102
|
// 扩展拒绝超限的文件,服务器拒绝超限的上传,两边对不齐就会一截一放。
|
|
@@ -103,7 +105,10 @@ export const FS_TASK_MAX = 256 * 1024 * 1024; // 一个任务累计读走的
|
|
|
103
105
|
export const FS_LIST_MAX = 2000; // 一次列出的条目
|
|
104
106
|
export const FS_DEPTH_MAX = 8;
|
|
105
107
|
export const FS_FOLDERS_MAX = 8; // 一个浏览器最多挂这么多个;一个任务也最多批准这么多个
|
|
106
|
-
export const FS_EXTS = new Set([
|
|
108
|
+
export const FS_EXTS = new Set([
|
|
109
|
+
'.md', '.markdown', '.txt', '.pdf', '.docx', '.xlsx', '.pptx', '.csv', '.json', '.html', '.htm',
|
|
110
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp',
|
|
111
|
+
]);
|
|
107
112
|
const FS_SKIP = new Set(['node_modules', '__pycache__', '.git', '.venv']);
|
|
108
113
|
|
|
109
114
|
const err = (code, message) => Object.assign(new Error(message), { code });
|
|
@@ -191,19 +196,23 @@ export function cleanFolderNames(list) {
|
|
|
191
196
|
}
|
|
192
197
|
|
|
193
198
|
// 已经允许、已经拒绝的不再问。空名单就是「先告诉我挂了哪些」,什么都不问。
|
|
194
|
-
|
|
199
|
+
// 挂载文件夹就是用户把它分享出来;打开「自动允许新任务」时,挂着的文件夹直接放行(grant),
|
|
200
|
+
// 和任务一开始的网站一样不再逐个问。没打开时照旧进 ask。
|
|
201
|
+
export function foldersToAsk(use, { granted = [], refused = [], auto = false } = {}) {
|
|
195
202
|
const have = new Set(granted);
|
|
196
203
|
const no = new Set(refused);
|
|
197
204
|
const ask = [];
|
|
205
|
+
const grant = [];
|
|
198
206
|
const already = [];
|
|
199
207
|
const denied = [];
|
|
200
208
|
for (const name of Array.isArray(use) ? use : []) {
|
|
201
209
|
if (typeof name !== 'string') continue;
|
|
202
210
|
if (have.has(name)) { if (!already.includes(name)) already.push(name); continue; }
|
|
203
211
|
if (no.has(name)) { if (!denied.includes(name)) denied.push(name); continue; }
|
|
204
|
-
|
|
212
|
+
const to = auto ? grant : ask;
|
|
213
|
+
if (!to.includes(name)) to.push(name);
|
|
205
214
|
}
|
|
206
|
-
return { ask, already, refused: denied };
|
|
215
|
+
return { ask, grant, already, refused: denied };
|
|
207
216
|
}
|
|
208
217
|
|
|
209
218
|
// 相对路径。'' 是文件夹根;不合法返回 null(和根区分开)。
|