vcorp-chrome 1.5.0 → 1.7.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/docs//345/215/217/350/256/256.md +16 -1
- package/extension/background.js +2 -0
- package/extension/cloud-policy.js +3 -0
- package/extension/cloud.js +28 -6
- package/extension/manifest.json +1 -1
- package/extension/mark.js +16 -1
- package/extension/panel.html +4 -0
- package/extension/panel.js +5 -1
- package/package.json +1 -1
|
@@ -492,9 +492,24 @@ ref 就作废了**(ref 绑定在某一份快照上)。
|
|
|
492
492
|
→ { "outcome": "continued|cancelled|timed_out|completed|disabled", "text": "…" }
|
|
493
493
|
```
|
|
494
494
|
|
|
495
|
+
扫码、验证码、滑块不带 `kind`:这种只能本人做,免确认开关管不到。
|
|
496
|
+
对外动作(发消息、投递、发帖)要写 `"kind": "confirm"`,别的值一律当成没写。
|
|
497
|
+
|
|
498
|
+
```jsonc
|
|
499
|
+
{ "cmd": "ask", "params": {
|
|
500
|
+
"kind": "confirm",
|
|
501
|
+
"title": "确认发消息",
|
|
502
|
+
"prompt": "向对方发送下面这段话。点「我完成了」就发出去。"
|
|
503
|
+
} }
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
云端通道里,用户开了「对外动作不再逐条确认」,或在这个任务的浮条上点过「本任务内都同意」,
|
|
507
|
+
后续 `kind: "confirm"` 的 ask 不再弹窗,直接 `{ "outcome": "continued", "auto": true }`。
|
|
508
|
+
支付确认不走这条命令,没有跳过的办法。
|
|
509
|
+
|
|
495
510
|
| outcome | 含义 | agent 该怎么办 |
|
|
496
511
|
|---|---|---|
|
|
497
|
-
| `continued` |
|
|
512
|
+
| `continued` | 用户点了「我完成了」,或免确认直接放行(此时还有 `auto: true`) | 继续;`auto: true` 时在交付里记一笔 |
|
|
498
513
|
| `completed` | `until` 判据自动命中 | 继续 |
|
|
499
514
|
| `cancelled` | 用户点了取消 | **停止这个任务**,不要换个方式重试 |
|
|
500
515
|
| `timed_out` | 无人响应 | 用户不在电脑前 |
|
package/extension/background.js
CHANGED
|
@@ -2626,6 +2626,7 @@ const HANDLERS = {
|
|
|
2626
2626
|
const started = Date.now();
|
|
2627
2627
|
await chrome.tabs.sendMessage(id, {
|
|
2628
2628
|
__hcAsk: 'show', title: p.title, prompt: p.prompt, timeout, wantNote: p.wantNote !== false,
|
|
2629
|
+
...(p.kind === 'confirm' ? { offerAlways: true } : {}),
|
|
2629
2630
|
});
|
|
2630
2631
|
const panel = pollPanel(id, timeout);
|
|
2631
2632
|
|
|
@@ -2650,6 +2651,7 @@ const HANDLERS = {
|
|
|
2650
2651
|
|
|
2651
2652
|
return {
|
|
2652
2653
|
outcome: res.outcome,
|
|
2654
|
+
...(res.always ? { always: true } : {}),
|
|
2653
2655
|
text: `${head}${res.note ? `\n用户留言:${res.note}` : ''}\n\n${snap.text}`,
|
|
2654
2656
|
};
|
|
2655
2657
|
},
|
|
@@ -334,6 +334,9 @@ export function checkCommand(cmd, params, { hosts, origin }) {
|
|
|
334
334
|
return p;
|
|
335
335
|
case 'ask':
|
|
336
336
|
p.timeout = Math.min(Math.max(Number(p.timeout) || 300000, 5000), 600000);
|
|
337
|
+
// 只有对外动作的确认可以被免确认开关跳过。别的值(含没写)一律当成人必须在场:
|
|
338
|
+
// 扫码、验证码、滑块不能因为用户开了开关就被静默放行。
|
|
339
|
+
if (p.kind !== 'confirm') delete p.kind;
|
|
337
340
|
return p;
|
|
338
341
|
case 'sites': {
|
|
339
342
|
const { sites, bad } = cleanSites(p.add);
|
package/extension/cloud.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// 所以这里的每一条命令都按「对面可能已经被攻破」来处理,规矩全在 cloud-policy.js:
|
|
6
6
|
// 只有页面操作那一小组命令、只能停在允许名单(以及本任务里批准的网站)上、只能碰自己开的标签页、
|
|
7
7
|
// 上传只能用服务器上的素材。新任务要用户点头;开了自动允许则新任务和它一开始的网站直接放行。
|
|
8
|
-
//
|
|
8
|
+
// 任务开始之后追加的网站、本地文件夹,仍要逐个点头。带 kind=confirm 的 ask 是对外动作的确认:
|
|
9
|
+
// 用户开了免确认,或在这个任务里点过「本任务内都同意」,就直接放行。扫码和验证码不带 kind,照旧等人。
|
|
10
|
+
// 没有预设的平台表:要去哪个网站,任务自己申请。
|
|
9
11
|
//
|
|
10
12
|
// 身份:配对时在这里生成一对 ECDSA P-256 密钥,私钥以不可导出的 CryptoKey 存在
|
|
11
13
|
// IndexedDB,服务器只拿到公钥。每次连接对服务器给的随机数签名,签名里带上服务器
|
|
@@ -153,7 +155,7 @@ async function confirmClaim() {
|
|
|
153
155
|
await setCfg({
|
|
154
156
|
origin: c.origin, deviceId: r.deviceId, account: String(r.account || c.account), pairedAt: Date.now(),
|
|
155
157
|
// 换绑时沿用之前允许的网站;第一次配对名单是空的,等任务申请、用户点允许
|
|
156
|
-
sites: policy.cleanAllowList(old?.sites), auto: false, paused: false,
|
|
158
|
+
sites: policy.cleanAllowList(old?.sites), auto: false, autoConfirm: false, paused: false,
|
|
157
159
|
});
|
|
158
160
|
await chrome.storage.session.remove(CLAIM);
|
|
159
161
|
await log({ ev: 'paired', account: r.account || c.account, origin: c.origin });
|
|
@@ -240,7 +242,7 @@ export async function connect() {
|
|
|
240
242
|
const mounted = await folders.names().catch(() => []);
|
|
241
243
|
sock.send(JSON.stringify({
|
|
242
244
|
type: 'hello', v: 1, deviceId: cfg.deviceId, sig,
|
|
243
|
-
version: chrome.runtime.getManifest().version, sites: policy.cleanAllowList(cfg.sites), auto: !!cfg.auto,
|
|
245
|
+
version: chrome.runtime.getManifest().version, sites: policy.cleanAllowList(cfg.sites), auto: !!cfg.auto, autoConfirm: !!cfg.autoConfirm,
|
|
244
246
|
folders: mounted,
|
|
245
247
|
}));
|
|
246
248
|
} catch { try { sock.close(); } catch { /* 已经废了 */ } }
|
|
@@ -566,7 +568,7 @@ async function openMount(folderId = '') {
|
|
|
566
568
|
async function reportFolders() {
|
|
567
569
|
const mounted = await folders.names().catch(() => []);
|
|
568
570
|
const cfg = await getCfg();
|
|
569
|
-
if (cfg && ws?.readyState === 1) send({ type: 'config', sites: allowOf(cfg), auto: !!cfg.auto, folders: mounted });
|
|
571
|
+
if (cfg && ws?.readyState === 1) send({ type: 'config', sites: allowOf(cfg), auto: !!cfg.auto, autoConfirm: !!cfg.autoConfirm, folders: mounted });
|
|
570
572
|
await chrome.storage.session.set({ folderTick: Date.now() }).catch(() => {});
|
|
571
573
|
}
|
|
572
574
|
|
|
@@ -724,12 +726,27 @@ async function exec(m) {
|
|
|
724
726
|
return policy.redactOffList(tab.pendingUrl || tab.url);
|
|
725
727
|
}
|
|
726
728
|
|
|
729
|
+
// 对外动作的确认:全局开关或本任务里点过「都同意」,就不弹浮条。
|
|
730
|
+
// 不带 kind 的 ask(扫码、验证码)不满足条件,继续往下弹窗。
|
|
731
|
+
if (m.cmd === 'ask' && p.kind === 'confirm' && (cfg.autoConfirm || updated.confirmAll)) {
|
|
732
|
+
await log({ ev: 'ask_auto', job: jobId, msg: String(p.prompt || '').slice(0, 160) });
|
|
733
|
+
return { outcome: 'continued', auto: true, text: '✅ 用户已设置对外动作免确认,直接继续。' };
|
|
734
|
+
}
|
|
735
|
+
|
|
727
736
|
// 浮条上写明是谁在问:云端发来的文字可能是钓鱼,用户得知道这不是本机 agent 说的
|
|
728
737
|
if (m.cmd === 'ask') p.title = `☁ ${policy.hostOf(cfg.origin)}:${String(p.title || '需要你搭把手').slice(0, 60)}`;
|
|
729
738
|
const data = m.cmd === 'upload'
|
|
730
739
|
? await uploadMedia(p, tid, sid, label)
|
|
731
740
|
: await deps.run(m.cmd, p, tid, sid, label);
|
|
732
741
|
|
|
742
|
+
// 「本任务内都同意」只记在这个任务上。任务结束时整条记录删掉,不会带到下一个任务。
|
|
743
|
+
// always 是扩展内部的记号,不回给服务器。
|
|
744
|
+
if (m.cmd === 'ask' && data?.always) {
|
|
745
|
+
await updateTask(jobId, (t) => { t.confirmAll = true; });
|
|
746
|
+
await log({ ev: 'confirm_task', job: jobId, msg: String(p.prompt || '').slice(0, 160) });
|
|
747
|
+
delete data.always;
|
|
748
|
+
}
|
|
749
|
+
|
|
733
750
|
// 结果出门前再查一遍本任务所有标签页:有一页跳出了白名单,整份结果都不能出去
|
|
734
751
|
const after = (await getTasks())[jobId]?.tabs || [];
|
|
735
752
|
for (const id of after) {
|
|
@@ -814,7 +831,7 @@ async function panelState() {
|
|
|
814
831
|
getCfg(), chrome.storage.session.get(STATUS), chrome.storage.session.get(CLAIM), getTasks(), chrome.storage.local.get(LOG),
|
|
815
832
|
]);
|
|
816
833
|
return {
|
|
817
|
-
cfg: cfg && { origin: cfg.origin, account: cfg.account, sites: allowOf(cfg), auto: cfg.auto, paused: cfg.paused, pairedAt: cfg.pairedAt },
|
|
834
|
+
cfg: cfg && { origin: cfg.origin, account: cfg.account, sites: allowOf(cfg), auto: cfg.auto, autoConfirm: !!cfg.autoConfirm, paused: cfg.paused, pairedAt: cfg.pairedAt },
|
|
818
835
|
status: status || { state: 'off' },
|
|
819
836
|
claim: claimed && claimed.expiresAt > Date.now() ? { origin: claimed.origin, account: claimed.account } : null,
|
|
820
837
|
tasks: Object.values(tasks).sort((a, b) => b.lastAt - a.lastAt),
|
|
@@ -831,7 +848,7 @@ async function patchCfg(patch) {
|
|
|
831
848
|
const next = { ...rest, ...patch };
|
|
832
849
|
await setCfg(next);
|
|
833
850
|
const mounted = await folders.names().catch(() => []);
|
|
834
|
-
send({ type: 'config', sites: allowOf(next), auto: !!next.auto, folders: mounted });
|
|
851
|
+
send({ type: 'config', sites: allowOf(next), auto: !!next.auto, autoConfirm: !!next.autoConfirm, folders: mounted });
|
|
835
852
|
return next;
|
|
836
853
|
}
|
|
837
854
|
|
|
@@ -856,6 +873,11 @@ async function onPanel(m) {
|
|
|
856
873
|
await log({ ev: 'site_off', msg: host });
|
|
857
874
|
return panelState();
|
|
858
875
|
}
|
|
876
|
+
case 'setAutoConfirm': {
|
|
877
|
+
await patchCfg({ autoConfirm: !!m.autoConfirm });
|
|
878
|
+
await log({ ev: m.autoConfirm ? 'confirm_on' : 'confirm_off' });
|
|
879
|
+
return panelState();
|
|
880
|
+
}
|
|
859
881
|
case 'setAuto': {
|
|
860
882
|
await patchCfg({ auto: !!m.auto });
|
|
861
883
|
await log({ ev: m.auto ? 'auto_on' : 'auto_off' });
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "vcorp-chrome",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.7.0",
|
|
5
5
|
"description": "让 AI agent 操控你已登录的 Chrome。基于花叔的 huashu-chrome(MIT)修改。",
|
|
6
6
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq6xRqEwG4yTuHRG1Zah3j//T3B72XnazyZ+9fOCa5P4bBLPmNBN7YAGL5WnjtPVJefXT9r2Ol0qvNztayDG3dMjK6AZoWccgKa5rhxcvWgFUjanXtmBBQ0qgYwNhuEJSkdinLLtyUg6YmK7Mn72NWpfa/NfqNHdK5AHslvvtJ50iQAYmabl2Nd3vOJ9DxtLBt8a2DfoGPWizgEB3+gkqm2PB8LywwwoiGKd7acr1wZEWmWK9/s0YWdzIstjlvk5AO5XFn6EtEzW8tAAOwBka9239pcyj0kEogWctHLJn7p9VFru38r+I1USyGn6u9XQ68n9edWddxBYokETCqEMT9QIDAQAB",
|
|
7
7
|
"minimum_chrome_version": "116",
|
package/extension/mark.js
CHANGED
|
@@ -213,6 +213,9 @@
|
|
|
213
213
|
.ask .ok:hover { background: #ea580c; }
|
|
214
214
|
.ask .no { background: #fff; color: #666; border-color: #e2e2e2; }
|
|
215
215
|
.ask .no:hover { background: #f6f6f6; }
|
|
216
|
+
.ask.confirm { width: 420px; }
|
|
217
|
+
.ask .all { background: #fff; color: #9a3412; border-color: #fed7aa; font-size: 12px; padding: 7px 10px; }
|
|
218
|
+
.ask .all:hover { background: #fff7ed; }
|
|
216
219
|
/* 支付确认:同一个浮条换一身红。这类打断一年遇不上几次,
|
|
217
220
|
必须一眼就和「帮我解个验证码」区分开——看错了是要花钱的。 */
|
|
218
221
|
.ask.danger .head { background: linear-gradient(135deg,#fef2f2,#fee2e2); }
|
|
@@ -228,6 +231,8 @@
|
|
|
228
231
|
.ask .note { background: #2a2a2c; border-color: #3a3a3c; color: #f2f2f2; }
|
|
229
232
|
.ask .no { background: #2a2a2c; color: #ccc; border-color: #3a3a3c; }
|
|
230
233
|
.ask .no:hover { background: #333; }
|
|
234
|
+
.ask .all { background: #2a2a2c; color: #fdba74; border-color: #7c2d12; }
|
|
235
|
+
.ask .all:hover { background: #3b2a1a; }
|
|
231
236
|
.ask.danger .head { background: linear-gradient(135deg,#3f1d1d,#2a1414); }
|
|
232
237
|
.ask .what { background: #2a2a2c; }
|
|
233
238
|
.ask .amount { color: #f87171; }
|
|
@@ -546,11 +551,12 @@
|
|
|
546
551
|
let askOutcome = null; // 已结算的结果,等 background 来取
|
|
547
552
|
let askTimer = null;
|
|
548
553
|
|
|
549
|
-
function closeAsk(result, note) {
|
|
554
|
+
function closeAsk(result, note, extra) {
|
|
550
555
|
if (askEl) { askEl.remove(); askEl = null; }
|
|
551
556
|
if (askTimer) { clearInterval(askTimer); askTimer = null; }
|
|
552
557
|
askSettle = null;
|
|
553
558
|
askOutcome = { outcome: result, note: note || '' };
|
|
559
|
+
if (extra?.always) askOutcome.always = true;
|
|
554
560
|
maybeTeardown();
|
|
555
561
|
}
|
|
556
562
|
|
|
@@ -578,6 +584,7 @@
|
|
|
578
584
|
askEl.querySelector('.t').textContent = msg.title || `${chrome.runtime.getManifest().name} 需要你搭把手`;
|
|
579
585
|
askEl.querySelector('.p').textContent = msg.prompt || '';
|
|
580
586
|
if (msg.danger) askEl.classList.add('danger');
|
|
587
|
+
if (msg.offerAlways && !msg.danger) askEl.classList.add('confirm');
|
|
581
588
|
if (msg.okText) askEl.querySelector('.ok').textContent = msg.okText;
|
|
582
589
|
if (msg.noText) askEl.querySelector('.no').textContent = msg.noText;
|
|
583
590
|
// 「点哪个按钮、在哪个站、多少钱」单独拎出来,不混在正文里。
|
|
@@ -609,6 +616,14 @@
|
|
|
609
616
|
|
|
610
617
|
askEl.querySelector('.ok').addEventListener('click', () => closeAsk('continued', noteEl?.value));
|
|
611
618
|
askEl.querySelector('.no').addEventListener('click', () => closeAsk('cancelled', noteEl?.value));
|
|
619
|
+
// 文案写死在扩展里,不接 agent 传来的字。支付确认不给这个按钮。
|
|
620
|
+
if (msg.offerAlways && !msg.danger) {
|
|
621
|
+
const always = document.createElement('button');
|
|
622
|
+
always.className = 'all';
|
|
623
|
+
always.textContent = '本任务内都同意';
|
|
624
|
+
askEl.querySelector('.ok').before(always);
|
|
625
|
+
always.addEventListener('click', () => closeAsk('continued', noteEl?.value, { always: true }));
|
|
626
|
+
}
|
|
612
627
|
|
|
613
628
|
// 挂在 own 之外:会话区重画时不触碰 ask(输入框的焦点经不起挪动)
|
|
614
629
|
wrap.querySelector('.dock').appendChild(askEl);
|
package/extension/panel.html
CHANGED
|
@@ -163,6 +163,10 @@
|
|
|
163
163
|
<span class="txt">自动允许新任务<div class="sub">不再逐个弹窗确认。只在你信任这个账号时打开:新任务,以及它一开始就要去的网站,会直接放行。任务开始之后再加的网站,还有本地文件夹,仍然要你逐个点头</div></span>
|
|
164
164
|
<input type="checkbox" class="toggle" id="cloudAuto" />
|
|
165
165
|
</label>
|
|
166
|
+
<label class="sw-row">
|
|
167
|
+
<span class="txt">对外动作不再逐条确认<div class="sub">发消息、投递、发帖之前不再问你。扫码、验证码仍然要你本人来。付款一律不做</div></span>
|
|
168
|
+
<input type="checkbox" class="toggle" id="cloudAutoConfirm" />
|
|
169
|
+
</label>
|
|
166
170
|
<label class="sw-row">
|
|
167
171
|
<span class="txt">暂停<div class="sub">立刻断开,云端的命令一条都不接,直到你再打开</div></span>
|
|
168
172
|
<input type="checkbox" class="toggle" id="cloudPause" />
|
package/extension/panel.js
CHANGED
|
@@ -251,7 +251,9 @@ const LOG_TEXT = {
|
|
|
251
251
|
paired: '配对', unpaired: '解除配对', approved: '批准任务', denied: '拒绝任务', ended: '任务结束',
|
|
252
252
|
sites_on: '允许了新网站', sites_off: '拒绝了新网站', sites_timeout: '新网站没回应',
|
|
253
253
|
folders_on: '允许了本地文件夹', folders_off: '拒绝了本地文件夹', folders_timeout: '本地文件夹没回应',
|
|
254
|
-
site_on: '网站加入允许名单', site_off: '从允许名单移除网站', auto_on: '打开自动允许', auto_off: '关闭自动允许',
|
|
254
|
+
site_on: '网站加入允许名单', site_off: '从允许名单移除网站', auto_on: '打开自动允许', auto_off: '关闭自动允许',
|
|
255
|
+
confirm_on: '打开对外动作免确认', confirm_off: '关闭对外动作免确认', ask_auto: '免确认放行', confirm_task: '本任务内都同意',
|
|
256
|
+
paused: '暂停', resumed: '恢复',
|
|
255
257
|
};
|
|
256
258
|
|
|
257
259
|
const PERM_WORD = { granted: '可读', prompt: '需要恢复访问', denied: '已拒绝', unknown: '打不开' };
|
|
@@ -303,6 +305,7 @@ function renderCloud(s) {
|
|
|
303
305
|
$('cloudSub').textContent = s.status?.state === 'error' || s.status?.state === 'paused'
|
|
304
306
|
? s.status.message : `账号「${s.cfg.account || ''}」`;
|
|
305
307
|
$('cloudAuto').checked = !!s.cfg.auto;
|
|
308
|
+
$('cloudAutoConfirm').checked = !!s.cfg.autoConfirm;
|
|
306
309
|
$('cloudPause').checked = !!s.cfg.paused;
|
|
307
310
|
|
|
308
311
|
// 允许名单:用户点过「允许」的网站,含子域。没有预设的平台。
|
|
@@ -396,6 +399,7 @@ $('claimYes').onclick = async () => {
|
|
|
396
399
|
};
|
|
397
400
|
$('claimNo').onclick = () => cloudCall('cancelClaim');
|
|
398
401
|
$('cloudAuto').onchange = (e) => cloudCall('setAuto', { auto: e.target.checked });
|
|
402
|
+
$('cloudAutoConfirm').onchange = (e) => cloudCall('setAutoConfirm', { autoConfirm: e.target.checked });
|
|
399
403
|
$('cloudPause').onchange = (e) => cloudCall('setPaused', { paused: e.target.checked });
|
|
400
404
|
$('cloudReconn').onclick = () => cloudCall('reconnect');
|
|
401
405
|
$('cloudUnpair').onclick = () => {
|