vg-coder-cli 2.0.46 → 2.0.48
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 +418 -0
- package/bin/vg-coder.js +7 -0
- package/bin/vg.js +10 -0
- package/dist/vg-coder-bundle.js +50 -44
- package/package.json +7 -2
- package/src/server/api-server.js +355 -2
- package/src/server/task-queue.js +705 -0
- package/src/server/task-store.js +112 -0
- package/src/server/task-webhook.js +48 -0
- package/src/server/views/css/agent-panel.css +101 -0
- package/src/server/views/js/features/agent-panel.js +230 -9
- package/src/server/views/js/features/git-view.js +1 -1
- package/src/server/views/js/features/task-worker.js +448 -0
- package/src/server/views/js/main.js +4 -0
- package/src/server/views/vg-coder/background.js +17860 -11946
- package/src/server/views/vg-coder/controller.js +42 -10
- package/src/server/views/vg-coder/manifest.json +2 -1
- package/src/server/views/vg-coder/sidepanel.js +13 -7
package/INTEGRATION.md
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# VG Coder — Remote Task API Integration Guide
|
|
2
|
+
|
|
3
|
+
Hướng dẫn tích hợp service ngoài để gửi task chat AI Studio và nhận kết quả qua local server VG Coder.
|
|
4
|
+
|
|
5
|
+
## Kiến trúc
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
External Service ──HTTP──▶ POST /api/tasks (multipart)
|
|
9
|
+
│ enqueue, persist .vg/tasks/<id>/task.json
|
|
10
|
+
▼
|
|
11
|
+
TaskQueue (FIFO)
|
|
12
|
+
│ load-balance → idle worker
|
|
13
|
+
▼
|
|
14
|
+
Socket.IO task:execute ──▶ Browser tab(s) on aistudio.google.com
|
|
15
|
+
│ AIChat.send({prompt, files})
|
|
16
|
+
│ copyLastTurnAsMarkdown()
|
|
17
|
+
▼
|
|
18
|
+
Socket.IO task:complete ◀──── result
|
|
19
|
+
│ persist result.md, fire webhook
|
|
20
|
+
▼
|
|
21
|
+
Launcher recycle ───▶ chrome.tabs.remove + create
|
|
22
|
+
│ fresh tab w/ ?model=<id>
|
|
23
|
+
▼ next task gets clean chat
|
|
24
|
+
External Service ◀── GET /api/tasks/:id (polling)
|
|
25
|
+
External Service ◀── POST <webhookUrl> (push, retry 3×)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **Server**: `http://127.0.0.1:6868` (localhost-only, không auth).
|
|
29
|
+
- **Workers**: mỗi tab Chrome trên `aistudio.google.com` với extension VG Coder = 1 worker. Server tự load-balance song song giữa các worker idle.
|
|
30
|
+
- **Launchers**: mỗi Chrome profile = 1 launcher (background service worker của extension). Dùng để mở/đóng tab AI Studio và lock model cho từng task.
|
|
31
|
+
- **Auto-recycle**: sau mỗi task done/failed, server gọi launcher đóng tab worker đó + mở lại tab mới với `?model=<target>` — guarantee model lock per task. Mark worker `recycling` để tránh race.
|
|
32
|
+
- **Failover**: rate-limit / quota error → mark worker `rate_limited` 30 phút, requeue task sang worker khác (max 3 attempts). Tab giữ nguyên qua cooldown (không recycle khi rate-limited).
|
|
33
|
+
- **Default model**: `gemini-3-flash-preview` (free + multimodal — verified working với image + PDF). Avoid `-lite` (text-only) hoặc `-pro-preview` (paid alias to Deep Research).
|
|
34
|
+
- **Persistence**: tasks lưu vào `.vg/tasks/<id>/{task.json,result.md,files/}` của active project.
|
|
35
|
+
|
|
36
|
+
## Setup chạy
|
|
37
|
+
|
|
38
|
+
### Server
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g vg-coder-cli # hoặc: cd repo && npm run build
|
|
42
|
+
vg start # khởi động server :6868
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Worker (browser tab)
|
|
46
|
+
|
|
47
|
+
Mỗi Google account muốn dùng → 1 Chrome profile riêng:
|
|
48
|
+
|
|
49
|
+
1. Chrome → user menu góc trên-phải → **Add → Person** → đặt tên (ví dụ "Account 2") → Continue.
|
|
50
|
+
2. Cửa sổ mới: login Google account đó.
|
|
51
|
+
3. `chrome://extensions` → bật **Developer mode** → **Load unpacked** → chọn:
|
|
52
|
+
```
|
|
53
|
+
/Users/<you>/.nvm/.../node_modules/vg-coder-cli/src/server/views/vg-coder
|
|
54
|
+
```
|
|
55
|
+
(Có thể dùng đường dẫn local repo nếu dev: `<repo>/src/server/views/vg-coder`.)
|
|
56
|
+
4. Truy cập `https://aistudio.google.com`.
|
|
57
|
+
5. Click **Allow** trên popup `Access other apps and services on this device` (Chrome 130+ Private Network Access — chỉ hiện 1 lần / profile).
|
|
58
|
+
|
|
59
|
+
> Worker tự register sau ~3-5s. Email được scrape từ DOM AI Studio. Verify:
|
|
60
|
+
> ```bash
|
|
61
|
+
> curl -s http://127.0.0.1:6868/api/workers | jq
|
|
62
|
+
> ```
|
|
63
|
+
|
|
64
|
+
## REST API
|
|
65
|
+
|
|
66
|
+
### POST `/api/tasks` — submit task
|
|
67
|
+
|
|
68
|
+
`Content-Type: multipart/form-data`
|
|
69
|
+
|
|
70
|
+
| Field | Required | Mô tả |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `prompt` | yes (hoặc files) | Text gửi cho AI |
|
|
73
|
+
| `files[]` | no | File đính kèm (max 50 MB / file, max 10 file). PDF, ảnh, audio, video — AI Studio không nhận text |
|
|
74
|
+
| `webhookUrl` | no | URL nhận callback khi task xong |
|
|
75
|
+
| `meta` | no | JSON string, đính kèm metadata tùy ý (forward nguyên về webhook) |
|
|
76
|
+
| `workerLabel` | no | Email worker để pin task vào tài khoản cụ thể (vd `alice@gmail.com`) |
|
|
77
|
+
|
|
78
|
+
**Response 202**
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"taskId": "t_1778250830243_8630f8",
|
|
83
|
+
"status": "queued",
|
|
84
|
+
"position": 1,
|
|
85
|
+
"pollUrl": "/api/tasks/t_1778250830243_8630f8",
|
|
86
|
+
"createdAt": 1778250830243
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Errors**: 400 (thiếu prompt + files), 413 (file > 50 MB), 500 (server error).
|
|
91
|
+
|
|
92
|
+
### GET `/api/tasks/:id` — poll status
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"id": "t_...",
|
|
97
|
+
"status": "queued|running|done|failed|canceled",
|
|
98
|
+
"prompt": "...",
|
|
99
|
+
"meta": { ... },
|
|
100
|
+
"files": [{ "idx": 0, "name": "...", "mime": "...", "size": 1234 }],
|
|
101
|
+
"webhookUrl": "https://...",
|
|
102
|
+
"webhook": { "attempts": [{ "at": 0, "status": 200 }], "deliveredAt": 0 },
|
|
103
|
+
"worker": { "socketId": "...", "email": "alice@gmail.com", "chatId": "..." },
|
|
104
|
+
"workerLabel": null,
|
|
105
|
+
"attempts": [
|
|
106
|
+
{ "workerEmail": "alice@gmail.com", "code": "rate_limit_exceeded", "at": 0 }
|
|
107
|
+
],
|
|
108
|
+
"result": { "chatId": "...", "markdown": "..." },
|
|
109
|
+
"error": { "code": "...", "message": "..." },
|
|
110
|
+
"timing": { "createdAt": 0, "queuedAt": 0, "startedAt": 0, "finishedAt": 0, "durationMs": 0 },
|
|
111
|
+
"workingDir": "/abs/path"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`result.markdown` chỉ có khi `status="done"`. `attempts[]` ghi mọi lần worker bị rate-limit và task được requeue.
|
|
116
|
+
|
|
117
|
+
### GET `/api/tasks` — list tasks
|
|
118
|
+
|
|
119
|
+
Query: `?status=done&limit=50`. Trả về summary (`id`, `status`, `prompt[0..200]`, `createdAt`, `durationMs`, `webhookUrl`).
|
|
120
|
+
|
|
121
|
+
### DELETE `/api/tasks/:id` — cancel
|
|
122
|
+
|
|
123
|
+
- `queued` → status `canceled`, remove khỏi queue.
|
|
124
|
+
- `running` → best-effort: server gửi `task:cancel` cho worker, mark worker idle, status `canceled`, kết quả discard.
|
|
125
|
+
- `done|failed|canceled` → 409.
|
|
126
|
+
|
|
127
|
+
### GET `/api/tasks/:id/files/:idx` — file blob
|
|
128
|
+
|
|
129
|
+
Worker dùng để fetch file đã upload. Caller thường không gọi.
|
|
130
|
+
|
|
131
|
+
### GET `/api/workers` — list workers
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"workers": [
|
|
136
|
+
{
|
|
137
|
+
"id": "abc...",
|
|
138
|
+
"email": "alice@gmail.com",
|
|
139
|
+
"status": "idle|busy|rate_limited",
|
|
140
|
+
"currentTaskId": null,
|
|
141
|
+
"cooldownUntil": 1778252081156,
|
|
142
|
+
"lastSeen": 1778250177105,
|
|
143
|
+
"meta": { "domain": "aistudio.google.com", "chatId": "...", "registeredAt": 0 }
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### GET `/api/launchers` — list profile launchers
|
|
150
|
+
|
|
151
|
+
Mỗi Chrome profile có extension cài → background service worker = 1 launcher kết nối tới server qua socket.io. Launcher dùng để **auto-open AI Studio tab** khi không còn worker nào idle khớp với task (kể cả task pin email).
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"launchers": [
|
|
156
|
+
{
|
|
157
|
+
"id": "X0cKPmkz...",
|
|
158
|
+
"chromeId": "75cd593d-a861-4a6f-89a0-386e1e9e61c5",
|
|
159
|
+
"email": "alice@gmail.com",
|
|
160
|
+
"lastSeen": 1778305286920
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`chromeId` là UUID lưu `chrome.storage.sync` lúc cài extension (per-profile). `email` được bind lúc worker đầu tiên trong profile đó register — sau đó server biết profile nào sign in account nào → routing pin chính xác.
|
|
167
|
+
|
|
168
|
+
Auto-open flow:
|
|
169
|
+
1. Task `enqueue` + không có worker idle khớp pin → server emit `launcher:open_aistudio` cho launcher có email khớp.
|
|
170
|
+
2. Launcher SW: `chrome.tabs.query({ url: '*://aistudio.google.com/*' })` — nếu có tab cũ thì focus, không có thì `chrome.tabs.create({ url: '...?model=gemini-3-flash-preview' })`.
|
|
171
|
+
3. Worker mới register sau ~3-5s → task dispatch.
|
|
172
|
+
4. Fallback nếu không có launcher: server `open https://aistudio.google.com` qua OS default browser handler (cũ).
|
|
173
|
+
|
|
174
|
+
### Tab management API (qua launcher)
|
|
175
|
+
|
|
176
|
+
Server có thể chủ động list / đóng / mở tab AI Studio trong từng profile:
|
|
177
|
+
|
|
178
|
+
| Method | Path | Body / Query | Mô tả |
|
|
179
|
+
|---|---|---|---|
|
|
180
|
+
| `GET` | `/api/launcher/tabs` | `?label=<email>` (optional) | List tabs trong profile (hoặc all profiles nếu bỏ label) |
|
|
181
|
+
| `POST` | `/api/launcher/close-tab` | `{ workerLabel?, tabId? }` | Đóng tab cụ thể, hoặc tất cả tab AI Studio nếu bỏ `tabId` |
|
|
182
|
+
| `POST` | `/api/launcher/open-tab` | `{ workerLabel?, model?, url?, active? }` | Mở tab mới. `model` mặc định `gemini-3-flash-preview` |
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# List tab tất cả profile
|
|
186
|
+
curl -s http://127.0.0.1:6868/api/launcher/tabs | jq
|
|
187
|
+
|
|
188
|
+
# Reset tab profile alice về model multimodal
|
|
189
|
+
curl -X POST -d '{"workerLabel":"alice@gmail.com"}' \
|
|
190
|
+
-H 'Content-Type: application/json' http://127.0.0.1:6868/api/launcher/close-tab
|
|
191
|
+
curl -X POST -d '{"workerLabel":"alice@gmail.com","model":"gemini-3-flash-preview"}' \
|
|
192
|
+
-H 'Content-Type: application/json' http://127.0.0.1:6868/api/launcher/open-tab
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Modal auto-handling
|
|
196
|
+
|
|
197
|
+
AI Studio thỉnh thoảng pop modal chặn task. Worker tự detect + click button đúng (poll mỗi 400 ms):
|
|
198
|
+
|
|
199
|
+
| Modal heading | Action |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `Start creating with media in Google AI Studio` | Click **Acknowledge** |
|
|
202
|
+
| `Link a paid API key` / `Set up billing` | Click X (close) — fallback model multimodal free khác |
|
|
203
|
+
| `We have updated our Terms of Service` | Click **Dismiss** |
|
|
204
|
+
| Preference voting (`Skip`) | Click Skip |
|
|
205
|
+
|
|
206
|
+
## Webhook payload
|
|
207
|
+
|
|
208
|
+
Khi task chuyển `done | failed | canceled` và caller có `webhookUrl`, server `POST <url>` với:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"taskId": "t_...",
|
|
213
|
+
"status": "done",
|
|
214
|
+
"result": { "markdown": "...", "chatId": "..." },
|
|
215
|
+
"error": null,
|
|
216
|
+
"durationMs": 14178,
|
|
217
|
+
"meta": { "your-custom-data": 123 }
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- 3 lần retry với delay 1s/4s/16s nếu non-2xx.
|
|
222
|
+
- Failure không làm fail task; xem `task.webhook.attempts[]` để debug.
|
|
223
|
+
|
|
224
|
+
## Error codes
|
|
225
|
+
|
|
226
|
+
| Code | Khi nào | Failover? |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `rate_limit_exceeded` | "You've reached your rate limit" | ✅ retry sang worker khác |
|
|
229
|
+
| `quota_exceeded` | "Failed to generate content: user has exceeded quota" | ✅ |
|
|
230
|
+
| `generation_failed` | Toast "Failed to generate content" khác | ✅ |
|
|
231
|
+
| `model_internal_error` | "An internal error has occurred" | ❌ task fails |
|
|
232
|
+
| `token_count_failed` | `.token-status-error` element | ❌ |
|
|
233
|
+
| `worker_disconnected` | Worker tab đóng giữa chừng | ✅ nếu pin cho phép |
|
|
234
|
+
| `server_restart` | Server restart khi task đang running | ❌ task fails, fire webhook 1 lần |
|
|
235
|
+
| `dispatch_failed` | Lỗi gửi task tới worker | ❌ |
|
|
236
|
+
|
|
237
|
+
Failover chỉ kick khi:
|
|
238
|
+
- Code thuộc nhóm rate-limit/quota
|
|
239
|
+
- `attempts.length < 3`
|
|
240
|
+
- Có worker khác `idle` (và worker đó match `workerLabel` nếu pin)
|
|
241
|
+
|
|
242
|
+
## Pin to specific account
|
|
243
|
+
|
|
244
|
+
Pin = task chỉ chạy trên worker có email khớp `workerLabel`. Nếu worker đó `rate_limited` → task stay `queued` đến khi cooldown reset (30 phút). Không failover khi pin set.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
curl -F prompt="Use alice's quota only" \
|
|
248
|
+
-F workerLabel="alice@gmail.com" \
|
|
249
|
+
http://127.0.0.1:6868/api/tasks
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Examples
|
|
253
|
+
|
|
254
|
+
### Submit + poll
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
TID=$(curl -s -F prompt="Tóm tắt PDF này 3 gạch đầu dòng" \
|
|
258
|
+
-F files=@spec.pdf \
|
|
259
|
+
http://127.0.0.1:6868/api/tasks \
|
|
260
|
+
| jq -r .taskId)
|
|
261
|
+
|
|
262
|
+
while true; do
|
|
263
|
+
RESP=$(curl -s "http://127.0.0.1:6868/api/tasks/$TID")
|
|
264
|
+
STATUS=$(echo "$RESP" | jq -r .status)
|
|
265
|
+
echo "status=$STATUS"
|
|
266
|
+
case $STATUS in done|failed|canceled) break;; esac
|
|
267
|
+
sleep 2
|
|
268
|
+
done
|
|
269
|
+
|
|
270
|
+
echo "$RESP" | jq -r .result.markdown
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Submit with webhook
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
curl -F prompt="Phân tích file đính kèm" \
|
|
277
|
+
-F files=@image.png \
|
|
278
|
+
-F webhookUrl="https://your-service.example/vg-callback" \
|
|
279
|
+
-F meta='{"jobId":"abc","userId":42}' \
|
|
280
|
+
http://127.0.0.1:6868/api/tasks
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Service nhận POST từ server local → forward lên cloud / write DB.
|
|
284
|
+
|
|
285
|
+
### Node.js client
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
async function submitVgTask({ prompt, files = [], webhookUrl, workerLabel, meta }) {
|
|
289
|
+
const fd = new FormData();
|
|
290
|
+
fd.append('prompt', prompt);
|
|
291
|
+
if (webhookUrl) fd.append('webhookUrl', webhookUrl);
|
|
292
|
+
if (workerLabel) fd.append('workerLabel', workerLabel);
|
|
293
|
+
if (meta) fd.append('meta', JSON.stringify(meta));
|
|
294
|
+
for (const f of files) fd.append('files', f.blob, f.name);
|
|
295
|
+
|
|
296
|
+
const r = await fetch('http://127.0.0.1:6868/api/tasks', { method: 'POST', body: fd });
|
|
297
|
+
if (r.status !== 202) throw new Error(`HTTP ${r.status}`);
|
|
298
|
+
return r.json();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function pollVgTask(taskId, { intervalMs = 2000, timeoutMs = 5 * 60_000 } = {}) {
|
|
302
|
+
const start = Date.now();
|
|
303
|
+
while (Date.now() - start < timeoutMs) {
|
|
304
|
+
const r = await fetch(`http://127.0.0.1:6868/api/tasks/${taskId}`);
|
|
305
|
+
const t = await r.json();
|
|
306
|
+
if (['done', 'failed', 'canceled'].includes(t.status)) return t;
|
|
307
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
308
|
+
}
|
|
309
|
+
throw new Error('timeout');
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Debug API
|
|
314
|
+
|
|
315
|
+
Cùng tiếp đầu `/api/worker/...`. Optional query/body `workerLabel` để target worker cụ thể; bỏ qua = worker idle đầu tiên.
|
|
316
|
+
|
|
317
|
+
| Endpoint | Mô tả |
|
|
318
|
+
|---|---|
|
|
319
|
+
| `GET /api/worker/url` | URL/title/chatId/rateLimit hiện tại |
|
|
320
|
+
| `POST /api/worker/probe` | `{selector, kind}` → query DOM, kind = `text\|html\|outer\|attrs` |
|
|
321
|
+
| `POST /api/worker/dom` | `{selector?, maxBytes?}` → outerHTML, truncate nếu > maxBytes (default 200 KB) |
|
|
322
|
+
| `POST /api/worker/eval` | `{code, timeoutMs?}` → eval JS trong page (async được, return value serialize JSON) |
|
|
323
|
+
| `POST /api/worker/logs` | `{since?, level?, limit?, clear?}` → console + window.error + unhandledrejection (ring buffer 200) |
|
|
324
|
+
| `GET /api/worker/screenshot?format=png\|jpeg&quality=NN` | Native viewport capture qua `chrome.tabs.captureVisibleTab` (~250-500 ms). Tab AI Studio phải active. |
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Xem worker đang ở chat nào
|
|
328
|
+
curl http://127.0.0.1:6868/api/worker/url?label=alice@gmail.com | jq
|
|
329
|
+
|
|
330
|
+
# Inspect rate-limit text bất kỳ lúc nào
|
|
331
|
+
curl -X POST -H 'Content-Type: application/json' \
|
|
332
|
+
-d '{"selector":".model-error, ms-upgrade-options-callout","kind":"text"}' \
|
|
333
|
+
http://127.0.0.1:6868/api/worker/probe | jq
|
|
334
|
+
|
|
335
|
+
# Eval custom code
|
|
336
|
+
curl -X POST -H 'Content-Type: application/json' \
|
|
337
|
+
-d '{"code":"return document.querySelectorAll(\"ms-chat-turn\").length"}' \
|
|
338
|
+
http://127.0.0.1:6868/api/worker/eval | jq
|
|
339
|
+
|
|
340
|
+
# Capture screenshot worker hiện tại (PNG mặc định)
|
|
341
|
+
curl http://127.0.0.1:6868/api/worker/screenshot -o /tmp/snap.png && open /tmp/snap.png
|
|
342
|
+
|
|
343
|
+
# JPEG nén nhẹ — nhanh hơn, file nhỏ hơn (~50% size)
|
|
344
|
+
curl "http://127.0.0.1:6868/api/worker/screenshot?format=jpeg&quality=60&label=alice@gmail.com" -o /tmp/snap.jpg
|
|
345
|
+
|
|
346
|
+
# Xem console errors gần đây
|
|
347
|
+
curl -X POST -H 'Content-Type: application/json' \
|
|
348
|
+
-d '{"level":"error","limit":20}' \
|
|
349
|
+
http://127.0.0.1:6868/api/worker/logs | jq
|
|
350
|
+
|
|
351
|
+
# Logs từ thời điểm cụ thể (epoch ms) — polling incremental
|
|
352
|
+
curl -X POST -H 'Content-Type: application/json' \
|
|
353
|
+
-d '{"since":1778300000000,"limit":50}' \
|
|
354
|
+
http://127.0.0.1:6868/api/worker/logs | jq '.logs[] | {at, level, message}'
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Known limitations
|
|
358
|
+
|
|
359
|
+
- **PNA prompt** lần đầu / profile: Chrome 130+ bắt user click Allow. Server-side `Access-Control-Allow-Private-Network` header có nhưng Chrome vẫn show prompt; click 1 lần → permanent.
|
|
360
|
+
- **AI Studio file types**: chỉ chấp nhận PDF / ảnh / audio / video. File text/markdown/code thường fail upload.
|
|
361
|
+
- **Chat reset per task = recycle tab**: mỗi task xong server đóng tab + mở tab mới (`?model=<target>`). Tab disconnect ~3-5s giữa các task. Không "continue same chat".
|
|
362
|
+
- **Single quota per account/day**: rate-limit auto-failover chỉ chuyển sang account còn quota.
|
|
363
|
+
- **AI Studio đôi khi treo silent**: turn không render sau Run. Worker timeout default 5 phút sẽ đẩy task về `failed`.
|
|
364
|
+
- **Tab AI Studio phải visible + active trong khi task chạy** — xem mục _Tab visibility constraint_ bên dưới.
|
|
365
|
+
- **Model selection bị Angular routerLink ignore href**: AI Studio's "+ New chat" link không preserve `?model=` query → session drift sang Deep Research Preview. Workaround = recycle tab per task (đã implement).
|
|
366
|
+
- **Binary file upload**: phải dùng direct fetch + Blob, KHÔNG qua extension proxy fetch (`vetgoFetch arrayBuffer`) vì chrome.runtime structured-clone corrupt ArrayBuffer (verify SHA-256 mismatch).
|
|
367
|
+
|
|
368
|
+
## Tab visibility constraint
|
|
369
|
+
|
|
370
|
+
Hệ thống dùng cơ chế "Copy as markdown" của AI Studio để lấy kết quả task — cụ thể: mở menu ⋮ trên turn cuối → click "Copy as markdown" → AI Studio gọi `document.execCommand('copy')` → script hook bắt nội dung. Đây là path duy nhất lấy được markdown chuẩn (DOM scrape không lossless cho code block / table / latex).
|
|
371
|
+
|
|
372
|
+
Chrome enforce 2 điều kiện ở C++ layer cho `execCommand('copy')`, **không thể bypass bằng API hay flag**:
|
|
373
|
+
|
|
374
|
+
1. Tab phải là **active tab** trong window của nó (`document.visibilityState === 'visible'`).
|
|
375
|
+
2. Window chứa tab phải **không bị occlude** (không minimize, không bị app khác fullscreen che kín).
|
|
376
|
+
|
|
377
|
+
Hệ quả: trong khi 1 task đang chạy (~10-15s) trên 1 worker tab, **user không được**:
|
|
378
|
+
|
|
379
|
+
- Click sang tab khác trong cùng window AI Studio.
|
|
380
|
+
- Minimize window đó (Cmd+M / nút vàng).
|
|
381
|
+
- Để app khác fullscreen che kín window đó (đặc biệt trên macOS — strict occlusion detection).
|
|
382
|
+
|
|
383
|
+
User vẫn có thể:
|
|
384
|
+
|
|
385
|
+
- Làm việc ở **window Chrome khác** hoặc **app khác** (Cmd+Tab) — chỉ cần window AI Studio không bị che kín. Tab vẫn `visible` vì là active trong window của nó.
|
|
386
|
+
- Đặt window AI Studio trên **monitor thứ 2** — luôn visible, không vướng workflow chính.
|
|
387
|
+
- Đóng tab giữa các task (không đụng vào trong khi task chạy) — task tiếp theo launcher tự mở lại.
|
|
388
|
+
|
|
389
|
+
Đã thử các approach để bypass và đều fail:
|
|
390
|
+
|
|
391
|
+
| Approach | Vấn đề |
|
|
392
|
+
|---|---|
|
|
393
|
+
| `chrome.windows.create({ state: 'minimized' })` | `visibilityState='hidden'` → clipboard fail |
|
|
394
|
+
| `state: 'normal' + focused: false` | macOS occlusion → vẫn `hidden` nếu bị app khác che |
|
|
395
|
+
| Tab group collapsed | Tab bị treat như background tab → `hidden` |
|
|
396
|
+
| `Page.bringToFront` (CDP) | Không override OS-level occlusion |
|
|
397
|
+
| DOM scrape thay clipboard | Mất format code/latex/table → không acceptable |
|
|
398
|
+
|
|
399
|
+
**Trade-off**: muốn task chạy 100% reliable thì user cần tuân thủ điều kiện trên. Nếu chấp nhận task fail thỉnh thoảng (do user vô tình minimize hoặc occlude), có thể bỏ qua — task sẽ failover sang worker khác hoặc retry.
|
|
400
|
+
|
|
401
|
+
## Health check
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
curl http://127.0.0.1:6868/health
|
|
405
|
+
# {"status":"ok","version":"2.0.49"}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## End-to-end smoke test
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
# T1-T5: submit, file+webhook, cancel, list workers, pin
|
|
412
|
+
node tests/remote-task-e2e.js
|
|
413
|
+
|
|
414
|
+
# D1-D4: debug API + file upload (logs, screenshot, image, PDF)
|
|
415
|
+
node tests/debug-and-files-test.js
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
`debug-and-files-test.js` ghi log ra `/tmp/vg-test-log.txt`, screenshot ra `/tmp/vg-test-screenshot.{png,jpg}`, kết quả markdown ra `/tmp/vg-test-{image,pdf}-result.md`. Cần ≥1 worker idle với email thật + file fixture `tests/feline-xray-chest7.jpg` + `tests/REMOTE-SIGNING.pdf`. Verified: 4/4 tests pass trong ~87s.
|
package/bin/vg-coder.js
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* VG Coder CLI Entry Point
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// Notify the user when a newer version is on npm. See bin/vg.js for details.
|
|
8
|
+
try {
|
|
9
|
+
const updateNotifier = require('update-notifier');
|
|
10
|
+
const pkg = require('../package.json');
|
|
11
|
+
updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 6 }).notify({ defer: true });
|
|
12
|
+
} catch (_) { /* ignore */ }
|
|
13
|
+
|
|
7
14
|
const VGCoderCLI = require('../src/index');
|
|
8
15
|
|
|
9
16
|
// Create and run CLI
|
package/bin/vg.js
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
* VG Coder CLI Entry Point
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// Notify the user when a newer version is on npm. update-notifier caches
|
|
8
|
+
// the check (default ~6 hours via configstore) and prints a banner on
|
|
9
|
+
// next process exit (defer: true) so it never blocks startup. Wrapped in
|
|
10
|
+
// try/catch so a missing module / offline environment never breaks the CLI.
|
|
11
|
+
try {
|
|
12
|
+
const updateNotifier = require('update-notifier');
|
|
13
|
+
const pkg = require('../package.json');
|
|
14
|
+
updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 6 }).notify({ defer: true });
|
|
15
|
+
} catch (_) { /* ignore */ }
|
|
16
|
+
|
|
7
17
|
const VGCoderCLI = require('../src/index');
|
|
8
18
|
|
|
9
19
|
// Create and run CLI
|