vg-coder-cli 2.0.71 → 2.0.73
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/INTEGRATION.md +105 -0
- package/package.json +1 -1
- package/src/server/task-queue.js +30 -4
- package/src/server/views/vg-coder/background.js +38 -16
package/INTEGRATION.md
CHANGED
|
@@ -219,6 +219,8 @@ Server có thể chủ động list / đóng / mở tab AI Studio trong từng p
|
|
|
219
219
|
| `POST` | `/api/launcher/open-tab` | `{ chromeId? \| workerLabel?, model?, url?, active? }` | Mở tab mới. `model` mặc định `gemini-3-flash-preview`. Response v2.0.52+ kèm `requested_model` / `actual_model` / `fallback_occurred` (URL-based, **không reliable** với AI Studio versions mới — xem note) |
|
|
220
220
|
| `GET` | `/api/launcher/debug` | `?chromeId=<uuid>` \| `?workerLabel=<email>` (optional) | Dump launcher SW state: `swChromeId`, `syncStorage`, `localStorage`, AI Studio tabs, windows count, runtime info. Bỏ pin → broadcast all launchers. (v2.0.65+) |
|
|
221
221
|
| `POST` | `/api/launcher/exec` | `{ chromeId? \| workerLabel? \| all?, cmd, args?, timeoutMs? }` | Chạy 1 lệnh `chrome.*` đã định nghĩa sẵn trong launcher SW. (v2.0.66+, thay cho `eval` bị Manifest V3 CSP chặn) |
|
|
222
|
+
| `POST` | `/api/launcher/wait-ready` | `{ chromeId, timeoutMs?, requireEmail?, requireWorker? }` | Block sync cho đến khi launcher có email scraped + ≥1 idle worker. Thay 2 vòng poll bằng 1 call. (v2.0.68+) |
|
|
223
|
+
| `GET` | `/api/worker/extension-info` | `?chromeId=<uuid>` \| `?workerLabel=<email>` | Dump extension metadata (`installType`, `version`, `permissions`, `hostPermissions`) qua content-script. Dùng để diagnose duplicate installs (Load Unpacked vs Web Store). (v2.0.71+) |
|
|
222
224
|
|
|
223
225
|
**Pin precedence**: `chromeId` > `workerLabel` > default. `chromeId` chỉ exact-match một
|
|
224
226
|
launcher cụ thể — dùng để address **profile chưa bind email** (mới cài extension,
|
|
@@ -364,6 +366,109 @@ Pattern recommend cho client: bỏ qua `actual_model` ở open-tab response, ki
|
|
|
364
366
|
tra `task.result.actualModel === requested_model` sau mỗi task done. Nếu khác
|
|
365
367
|
→ AI Studio đã silently fallback, retry với model khác hoặc fail-fast.
|
|
366
368
|
|
|
369
|
+
### Sync ready check — `/api/launcher/wait-ready` (v2.0.68+)
|
|
370
|
+
|
|
371
|
+
Thay vì 2 vòng poll (`/api/launchers` để chờ email scrape + `/api/workers` để
|
|
372
|
+
chờ worker idle), endpoint này block 1 lần đến khi launcher sẵn sàng dispatch
|
|
373
|
+
task. Counter pattern thông thường:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# 1. open-tab in chromeId X
|
|
377
|
+
curl -X POST -d '{"chromeId":"X","model":"gemini-3.1-pro-preview"}' \
|
|
378
|
+
http://127.0.0.1:6868/api/launcher/open-tab
|
|
379
|
+
|
|
380
|
+
# 2. wait until launcher email bound + worker registered + idle (one sync call)
|
|
381
|
+
curl -X POST -d '{"chromeId":"X","timeoutMs":15000}' \
|
|
382
|
+
http://127.0.0.1:6868/api/launcher/wait-ready
|
|
383
|
+
# → { ok, launcher: {email, ...}, worker: {email, status, ...}, elapsedMs }
|
|
384
|
+
|
|
385
|
+
# 3. submit task pinned to chromeId
|
|
386
|
+
curl -F prompt="..." -F chromeId=X http://127.0.0.1:6868/api/tasks
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Body fields:
|
|
390
|
+
|
|
391
|
+
| Field | Default | Mô tả |
|
|
392
|
+
|---|---|---|
|
|
393
|
+
| `chromeId` | required | UUID Chrome profile |
|
|
394
|
+
| `timeoutMs` | 15000 | clamp `[1000, 60000]` |
|
|
395
|
+
| `requireEmail` | `true` | đợi launcher.email != null (DOM scrape xong) |
|
|
396
|
+
| `requireWorker` | `true` | đợi ≥1 worker `idle` + chromeId match + email bound |
|
|
397
|
+
|
|
398
|
+
Status codes:
|
|
399
|
+
- **200**: `{ ok: true, launcher, worker, elapsedMs }` — sẵn sàng
|
|
400
|
+
- **404** `launcher_not_found`: chromeId chưa từng match launcher (immediate)
|
|
401
|
+
- **504** `wait_ready_timeout`: điều kiện không thoả trong timeoutMs (trả về `details` để debug)
|
|
402
|
+
|
|
403
|
+
### Extension diagnostics — `/api/worker/extension-info` (v2.0.71+)
|
|
404
|
+
|
|
405
|
+
Dump metadata của extension đang chạy trong tab AI Studio — `installType`,
|
|
406
|
+
`version`, `runtimeId`, `permissions`. Dùng khi:
|
|
407
|
+
|
|
408
|
+
- Phát hiện profile có duplicate install (Load Unpacked + Web Store cùng tồn tại)
|
|
409
|
+
- Verify extension version sau khi update npm package
|
|
410
|
+
- Diagnose "tabId null" (extension cũ chưa propagate field)
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
curl -s "http://127.0.0.1:6868/api/worker/extension-info?workerLabel=alice@gmail.com" | jq
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Response:
|
|
417
|
+
|
|
418
|
+
```json
|
|
419
|
+
{
|
|
420
|
+
"ok": true,
|
|
421
|
+
"info": {
|
|
422
|
+
"runtimeId": "comfeilpfnlaoijgndaikpniioglmonf",
|
|
423
|
+
"version": "1.0.0",
|
|
424
|
+
"name": "VetGo Pro",
|
|
425
|
+
"manifestVersion": 3,
|
|
426
|
+
"installType": "development", // "development" = Load Unpacked; "normal" = Web Store
|
|
427
|
+
"enabled": true,
|
|
428
|
+
"permissions": ["alarms", "cookies", ...],
|
|
429
|
+
"hostPermissions": ["<all_urls>"]
|
|
430
|
+
},
|
|
431
|
+
"tabUrl": "https://aistudio.google.com/...",
|
|
432
|
+
"vetgo": {
|
|
433
|
+
"chromeId": "...",
|
|
434
|
+
"tabId": 437926742,
|
|
435
|
+
"extInfo": { ... } // same content as .info, source of truth
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
`.info` lấy từ `window.vetgo.extInfo` (background.ts inject lúc CONTROLLER script
|
|
441
|
+
load). Page-context content-script không có `chrome.runtime.sendMessage` → đây là
|
|
442
|
+
duy nhất cách lấy metadata từ extension về.
|
|
443
|
+
|
|
444
|
+
**Trường hợp thường gặp**: `installType="normal"` + `runtimeId` khác với mọi profile
|
|
445
|
+
khác → Web Store install cũ vẫn còn active, đè Load Unpacked. Fix: `chrome://extensions`
|
|
446
|
+
→ Remove Web Store version → restart Chrome.
|
|
447
|
+
|
|
448
|
+
### ChromeId stability — `storage.local` migration (v2.0.72+)
|
|
449
|
+
|
|
450
|
+
Trước v2.0.72: chromeId lưu trong `chrome.storage.sync` (đồng bộ qua Google
|
|
451
|
+
account). Nếu reinstall extension trong profile có account từng cài extension
|
|
452
|
+
khác → storage.sync sync ngược UUID cũ về → launcher SW init mint UUID mới
|
|
453
|
+
nhưng background.ts CONTROLLER handler đọc UUID đã sync ngược → 2 chromeId
|
|
454
|
+
khác nhau cho cùng 1 profile.
|
|
455
|
+
|
|
456
|
+
Từ v2.0.72: dùng `chrome.storage.local` (per-profile, không cross account).
|
|
457
|
+
Migration tự động: lần đầu boot extension, nếu `storage.local.id` rỗng nhưng
|
|
458
|
+
`storage.sync.id` có → copy `.sync.id` sang `.local.id`. Sau đó `.local.id`
|
|
459
|
+
là source of truth.
|
|
460
|
+
|
|
461
|
+
Verify chromeId nhất quán:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
curl -s -X POST -d '{"all":true,"cmd":"storage.local.get","args":{"keys":["id"]}}' \
|
|
465
|
+
-H 'Content-Type: application/json' \
|
|
466
|
+
http://127.0.0.1:6868/api/launcher/exec \
|
|
467
|
+
| jq '.[] | {launcher: .launcher.chromeId, local: .value.id, ok: (.launcher.chromeId == .value.id)}'
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Tất cả `ok: true` = pipeline đúng.
|
|
471
|
+
|
|
367
472
|
### Modal auto-handling
|
|
368
473
|
|
|
369
474
|
AI Studio thỉnh thoảng pop modal chặn task. Worker tự detect + click button đúng (poll mỗi 400 ms):
|
package/package.json
CHANGED
package/src/server/task-queue.js
CHANGED
|
@@ -1009,13 +1009,12 @@ class TaskQueue {
|
|
|
1009
1009
|
_autoLaunchBrowser(task = null) {
|
|
1010
1010
|
const now = Date.now();
|
|
1011
1011
|
if (now - this.launchedAt < AUTO_LAUNCH_DEBOUNCE_MS) return;
|
|
1012
|
-
this.launchedAt = now;
|
|
1013
1012
|
|
|
1014
1013
|
// Prefer asking a connected launcher (extension background SW in the right
|
|
1015
|
-
// Chrome profile) to open / focus the AI Studio tab.
|
|
1016
|
-
// `open` if no launcher matches the pin or none are connected.
|
|
1014
|
+
// Chrome profile) to open / focus the AI Studio tab.
|
|
1017
1015
|
const launcher = this._pickLauncher(task);
|
|
1018
1016
|
if (launcher) {
|
|
1017
|
+
this.launchedAt = now;
|
|
1019
1018
|
const tag = launcher.email || `chromeId=${launcher.chromeId.slice(0, 8)}…`;
|
|
1020
1019
|
console.log(chalk.cyan(`[TaskQueue] No matching worker — asking launcher ${tag} to open AI Studio`));
|
|
1021
1020
|
try {
|
|
@@ -1033,10 +1032,37 @@ class TaskQueue {
|
|
|
1033
1032
|
return;
|
|
1034
1033
|
} catch (err) {
|
|
1035
1034
|
console.log(chalk.yellow(`[TaskQueue] Launcher emit failed: ${err.message}`));
|
|
1036
|
-
// fall through to OS open
|
|
1035
|
+
// fall through to OS open ONLY if task has no explicit pin
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// If the task has an explicit pin (chromeId or workerLabel) and no launcher
|
|
1040
|
+
// matches, do NOT fall back to OS `open` — that command opens the URL in
|
|
1041
|
+
// whatever Chrome profile is currently focused on the host (potentially a
|
|
1042
|
+
// profile that uninstalled the extension or belongs to a different user),
|
|
1043
|
+
// leaking the task target into an unrelated profile and respawning every
|
|
1044
|
+
// 30s as long as the task stays queued. Fail the task instead.
|
|
1045
|
+
const hasExplicitPin = !!(task?.chromeId || task?.workerLabel);
|
|
1046
|
+
if (hasExplicitPin) {
|
|
1047
|
+
const pinDesc = task.chromeId ? `chromeId=${task.chromeId.slice(0, 8)}…` : `workerLabel=${task.workerLabel}`;
|
|
1048
|
+
console.log(chalk.yellow(`[TaskQueue] Task ${task.id} pinned to ${pinDesc} but no matching launcher — failing without OS-open fallback`));
|
|
1049
|
+
// Mark task failed so _drain doesn't keep re-triggering autoLaunch.
|
|
1050
|
+
const t = this.cache.get(task.id);
|
|
1051
|
+
if (t && t.status === 'queued') {
|
|
1052
|
+
t.status = 'failed';
|
|
1053
|
+
t.error = { code: 'launcher_not_found', message: `No connected launcher matches pin ${pinDesc}` };
|
|
1054
|
+
t.timing.finishedAt = Date.now();
|
|
1055
|
+
t.timing.durationMs = t.timing.finishedAt - (t.timing.startedAt || t.timing.createdAt);
|
|
1056
|
+
this.pending = this.pending.filter(p => p.id !== task.id);
|
|
1057
|
+
store.saveTask(t).catch(() => {});
|
|
1058
|
+
webhook.deliver(t).catch(() => {});
|
|
1037
1059
|
}
|
|
1060
|
+
return;
|
|
1038
1061
|
}
|
|
1039
1062
|
|
|
1063
|
+
// No pin at all — safe to OS-open default URL since the task accepts
|
|
1064
|
+
// any profile. Apply debounce so we don't spam tabs across the host.
|
|
1065
|
+
this.launchedAt = now;
|
|
1040
1066
|
let cmd;
|
|
1041
1067
|
switch (process.platform) {
|
|
1042
1068
|
case 'darwin': cmd = `open "${AUTO_LAUNCH_URL}"`; break;
|