agen-vektor 0.3.23 → 0.3.25
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 +14 -0
- package/dist/tools/e2b.js +238 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,20 @@ npm install -g agen-vektor
|
|
|
71
71
|
vector
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
### Update (existing install)
|
|
75
|
+
|
|
76
|
+
Sudah install VectorHead sebelumnya? Tinggal update paket global npm-nya lalu
|
|
77
|
+
verifikasi versi:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install -g agen-vektor@latest
|
|
81
|
+
vector --version
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Config lama aman: model default yang sudah usang (`glm-5.3-flash` dst.)
|
|
85
|
+
otomatis termigrasi saat aplikasi pertama kali dijalankan setelah update.
|
|
86
|
+
Tidak ada langkah manual.
|
|
87
|
+
|
|
74
88
|
### From source
|
|
75
89
|
|
|
76
90
|
```bash
|
package/dist/tools/e2b.js
CHANGED
|
@@ -8,7 +8,7 @@ const GW_BASE = free_tier_1.FREE_GATEWAY_URL.replace(/\/v1$/, '');
|
|
|
8
8
|
const POLL_INTERVAL_MS = Math.max(10, Number(process.env.VECTOR_E2B_POLL_MS) || 3_000);
|
|
9
9
|
const POLL_MAX_MS = 320_000; // > E2B_MAX_TIMEOUT_MS worker (300s) + margin
|
|
10
10
|
const OUT_CAP = 20_000;
|
|
11
|
-
async function postRun(command, timeoutMs) {
|
|
11
|
+
async function postRun(command, timeoutMs, proxy) {
|
|
12
12
|
const controller = new AbortController();
|
|
13
13
|
const timer = setTimeout(() => controller.abort(), 30_000);
|
|
14
14
|
try {
|
|
@@ -16,7 +16,7 @@ async function postRun(command, timeoutMs) {
|
|
|
16
16
|
method: 'POST',
|
|
17
17
|
signal: controller.signal,
|
|
18
18
|
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
19
|
-
body: JSON.stringify({ command, timeout_ms: timeoutMs }),
|
|
19
|
+
body: JSON.stringify({ command, timeout_ms: timeoutMs, proxy }),
|
|
20
20
|
});
|
|
21
21
|
const data = (await res.json().catch(() => null));
|
|
22
22
|
return { res, data };
|
|
@@ -25,6 +25,15 @@ async function postRun(command, timeoutMs) {
|
|
|
25
25
|
clearTimeout(timer);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
/** Daftar proxy Webshare ter-mask (tanpa kredensial) via gateway relay. */
|
|
29
|
+
async function fetchWebshareProxies() {
|
|
30
|
+
const res = await fetch(GW_BASE + '/v1/webshare/proxies', {
|
|
31
|
+
headers: { 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
32
|
+
signal: AbortSignal.timeout(30_000),
|
|
33
|
+
});
|
|
34
|
+
const data = (await res.json().catch(() => null));
|
|
35
|
+
return { count: data?.count || 0, proxies: data?.proxies || [], error: res.ok ? undefined : data?.error || 'HTTP ' + res.status };
|
|
36
|
+
}
|
|
28
37
|
async function fetchStatus(id) {
|
|
29
38
|
const controller = new AbortController();
|
|
30
39
|
const timer = setTimeout(() => controller.abort(), 20_000);
|
|
@@ -74,6 +83,7 @@ function createE2bTools() {
|
|
|
74
83
|
properties: {
|
|
75
84
|
command: { type: 'string', description: 'The shell command to run in the sandbox (bash -lc; use && to chain)' },
|
|
76
85
|
timeout_ms: { type: 'number', description: 'Timeout in ms (default 120000, max 300000)' },
|
|
86
|
+
proxy: { type: 'boolean', description: 'Route the command through a rotating Webshare proxy (curl/wget automatically proxied — different exit IP, anti rate-limit). Requires the gateway to have a Webshare key.' },
|
|
77
87
|
},
|
|
78
88
|
required: ['command'],
|
|
79
89
|
},
|
|
@@ -83,8 +93,9 @@ function createE2bTools() {
|
|
|
83
93
|
if (!command)
|
|
84
94
|
return { output: 'ERROR: provide a "command"' };
|
|
85
95
|
const timeoutMs = Math.min(300_000, Math.max(5_000, Number(args.timeout_ms) || 120_000));
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
const useProxy = args.proxy === true;
|
|
97
|
+
ctx.onActivity?.('e2b', `sandbox${useProxy ? '+proxy' : ''}: ${command.slice(0, 80)}`);
|
|
98
|
+
const { res, data } = await postRun(command, timeoutMs, useProxy);
|
|
88
99
|
if (!res.ok || !data || !data.id) {
|
|
89
100
|
const why = data?.error ? ` — ${(0, credentials_1.redact)(data.error)}` : '';
|
|
90
101
|
return { output: `ERROR: e2b relay HTTP ${res.status}${why}`, summary: 'e2b relay error' };
|
|
@@ -140,5 +151,228 @@ function createE2bTools() {
|
|
|
140
151
|
};
|
|
141
152
|
},
|
|
142
153
|
},
|
|
154
|
+
{
|
|
155
|
+
// ------------------------------------------------------------------
|
|
156
|
+
// CLOUD SESSION: sandbox persisten utk alur multi-langkah. Workflow
|
|
157
|
+
// browser testing (Chromium/Playwright headless DIJALANKAN DI SANDBOX):
|
|
158
|
+
// 1. e2b_cloud {action:'create'}
|
|
159
|
+
// 2. e2b_upload per file proyek (path absolut /home/user/...)
|
|
160
|
+
// 3. e2b_exec: npm install && npx playwright install --with-deps
|
|
161
|
+
// chromium (SEKALI per session — install tersimpan di sandbox)
|
|
162
|
+
// 4. e2b_exec: node test.mjs (script playwright; screenshot →
|
|
163
|
+
// /home/user/shots/...). ⚠️ chromium.launch WAJIB args:
|
|
164
|
+
// --no-sandbox --disable-dev-shm-usage --disable-gpu
|
|
165
|
+
// --js-flags=--jitless (tanpa itu → Target crashed: V8 CodeRange
|
|
166
|
+
// OOM di VM kecil; --single-process dilarang — exit 13)
|
|
167
|
+
// 5. poll e2b_exec (jobId) sampai status done → baca output/exit
|
|
168
|
+
// 6. e2b_download artefak (screenshot/log) bila perlu
|
|
169
|
+
// 7. e2b_cloud {action:'close'} — WAJIB di akhir (kredit shared)
|
|
170
|
+
// ------------------------------------------------------------------
|
|
171
|
+
definition: {
|
|
172
|
+
name: 'e2b_cloud',
|
|
173
|
+
description: 'Manage a persistent cloud sandbox session (E2B VM) for multi-step work: create a session, or close it when finished. Unlike e2b_run (one-shot), files and installed packages PERSIST across calls within the session — use for project upload + build/test + browser testing (Chromium/Playwright run headless inside the sandbox). ALWAYS close the session when done (shared quota). Browser testing REQUIRES launching Chromium with: --no-sandbox --disable-dev-shm-usage --disable-gpu --js-flags=--jitless (without --jitless the page crashes: V8 CodeRange OOM on the small E2B VM). Playwright is NOT preinstalled — run: npm i playwright && npx playwright install --with-deps chromium (1-3 min, persists for the session).',
|
|
174
|
+
parameters: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
action: { type: 'string', enum: ['create', 'close'], description: 'create = new persistent sandbox; close = kill it' },
|
|
178
|
+
id: { type: 'string', description: 'Session id (required for close)' },
|
|
179
|
+
ttl_s: { type: 'number', description: 'Session lifetime in seconds for create (default 1800, max 3600)' },
|
|
180
|
+
proxy: { type: 'boolean', description: 'create only: install a rotating Webshare proxy in the sandbox (~/.curlrc + profile.d) so curl/wget in every exec uses a rotating exit IP' },
|
|
181
|
+
},
|
|
182
|
+
required: ['action'],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
async execute(args, ctx) {
|
|
186
|
+
const action = String(args.action || '');
|
|
187
|
+
if (action === 'create') {
|
|
188
|
+
const ttl = Math.min(3600, Math.max(120, Number(args.ttl_s) || 1800));
|
|
189
|
+
const useProxy = args.proxy === true;
|
|
190
|
+
ctx.onActivity?.('e2b', `cloud session create (${ttl}s${useProxy ? '+proxy' : ''})`);
|
|
191
|
+
const res = await fetch(GW_BASE + '/v1/e2b/session', {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
194
|
+
body: JSON.stringify({ ttl_s: ttl, proxy: useProxy }),
|
|
195
|
+
signal: AbortSignal.timeout(60_000),
|
|
196
|
+
});
|
|
197
|
+
const data = (await res.json().catch(() => null));
|
|
198
|
+
if (!res.ok || !data?.id) {
|
|
199
|
+
const why = data?.error ? ` — ${(0, credentials_1.redact)(data.error)}` : '';
|
|
200
|
+
return { output: `ERROR: e2b session HTTP ${res.status}${why}`, summary: 'e2b session error' };
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
output: `session created: ${data.id} (ttl ${data.ttl}s${data.proxy ? `, proxy ON via ${data.proxyCountry || '?'} — curl/wget otomatis lewat IP rotasi` : ''})\nNext: e2b_upload files → e2b_exec commands (poll jobId) → e2b_download artifacts → e2b_cloud close.\nSession id: ${data.id}`,
|
|
204
|
+
summary: `e2b session ${data.id}`,
|
|
205
|
+
data,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (action === 'close') {
|
|
209
|
+
const id = String(args.id || '');
|
|
210
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
211
|
+
return { output: 'ERROR: provide the session "id"' };
|
|
212
|
+
ctx.onActivity?.('e2b', `cloud session close ${id}`);
|
|
213
|
+
const res = await fetch(GW_BASE + '/v1/e2b/close?id=' + encodeURIComponent(id), { method: 'POST', signal: AbortSignal.timeout(30_000) });
|
|
214
|
+
if (!res.ok)
|
|
215
|
+
return { output: `ERROR: e2b close HTTP ${res.status}`, summary: 'e2b close error' };
|
|
216
|
+
return { output: `session ${id} closed (sandbox killed).`, summary: `e2b session ${id} closed` };
|
|
217
|
+
}
|
|
218
|
+
return { output: 'ERROR: action harus create|close' };
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
definition: {
|
|
223
|
+
name: 'e2b_upload',
|
|
224
|
+
description: 'Upload one file into a persistent e2b cloud session (from e2b_cloud create). Content is sent as base64 — read local files first (size limit 20 MB). Target path must be absolute under /home/user/ or /tmp/.',
|
|
225
|
+
parameters: {
|
|
226
|
+
type: 'object',
|
|
227
|
+
properties: {
|
|
228
|
+
id: { type: 'string', description: 'Session id from e2b_cloud create' },
|
|
229
|
+
path: { type: 'string', description: 'Absolute path in the sandbox, e.g. /home/user/app/package.json' },
|
|
230
|
+
content_b64: { type: 'string', description: 'File content encoded as base64' },
|
|
231
|
+
},
|
|
232
|
+
required: ['id', 'path', 'content_b64'],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
async execute(args) {
|
|
236
|
+
const id = String(args.id || '');
|
|
237
|
+
const path = String(args.path || '').trim();
|
|
238
|
+
const content = String(args.content_b64 || '');
|
|
239
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
240
|
+
return { output: 'ERROR: invalid session id' };
|
|
241
|
+
if (!/^\/(home\/user\/|tmp\/)/.test(path) || path.includes('..'))
|
|
242
|
+
return { output: 'ERROR: path harus absolut di bawah /home/user/ atau /tmp/' };
|
|
243
|
+
if (!content)
|
|
244
|
+
return { output: 'ERROR: content_b64 kosong' };
|
|
245
|
+
const res = await fetch(GW_BASE + '/v1/e2b/upload?id=' + encodeURIComponent(id), {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
248
|
+
body: JSON.stringify({ path, content_b64: content }),
|
|
249
|
+
signal: AbortSignal.timeout(120_000),
|
|
250
|
+
});
|
|
251
|
+
const data = (await res.json().catch(() => null));
|
|
252
|
+
if (!res.ok || !data?.ok) {
|
|
253
|
+
const why = data?.error ? ` — ${(0, credentials_1.redact)(data.error)}` : '';
|
|
254
|
+
return { output: `ERROR: e2b upload HTTP ${res.status}${why}`, summary: 'e2b upload error' };
|
|
255
|
+
}
|
|
256
|
+
return { output: `uploaded: ${path} (${data.size} bytes)`, summary: `e2b upload ${path}` };
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
definition: {
|
|
261
|
+
name: 'e2b_exec',
|
|
262
|
+
description: 'Run a command DETACHED inside a persistent e2b cloud session and WAIT for it to finish (polls automatically; long-running OK — e.g. npm install, npx playwright install --with-deps chromium, node test.mjs). Returns stdout/stderr + exit code. Use e2b_run for one-shot no-session commands instead. For Playwright scripts: launch Chromium with --no-sandbox --disable-dev-shm-usage --disable-gpu --js-flags=--jitless (else "Target crashed" — V8 CodeRange OOM on the small E2B VM) and NEVER --single-process (hard crash).',
|
|
263
|
+
parameters: {
|
|
264
|
+
type: 'object',
|
|
265
|
+
properties: {
|
|
266
|
+
id: { type: 'string', description: 'Session id from e2b_cloud create' },
|
|
267
|
+
command: { type: 'string', description: 'Shell command to run in the sandbox (bash -lc)' },
|
|
268
|
+
},
|
|
269
|
+
required: ['id', 'command'],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
async execute(args, ctx) {
|
|
273
|
+
const id = String(args.id || '');
|
|
274
|
+
const command = String(args.command || '').trim();
|
|
275
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
276
|
+
return { output: 'ERROR: invalid session id' };
|
|
277
|
+
if (!command)
|
|
278
|
+
return { output: 'ERROR: provide a "command"' };
|
|
279
|
+
ctx.onActivity?.('e2b', `exec: ${command.slice(0, 80)}`);
|
|
280
|
+
const start = await fetch(GW_BASE + '/v1/e2b/exec?id=' + encodeURIComponent(id), {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
283
|
+
body: JSON.stringify({ command }),
|
|
284
|
+
signal: AbortSignal.timeout(30_000),
|
|
285
|
+
});
|
|
286
|
+
const startData = (await start.json().catch(() => null));
|
|
287
|
+
if (!start.ok || !startData?.jobId) {
|
|
288
|
+
const why = startData?.error ? ` — ${(0, credentials_1.redact)(startData.error)}` : '';
|
|
289
|
+
return { output: `ERROR: e2b exec HTTP ${start.status}${why}`, summary: 'e2b exec error' };
|
|
290
|
+
}
|
|
291
|
+
const jobId = startData.jobId;
|
|
292
|
+
const deadline = Date.now() + POLL_MAX_MS;
|
|
293
|
+
let last = '';
|
|
294
|
+
while (Date.now() < deadline) {
|
|
295
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
296
|
+
const pr = await fetch(GW_BASE + '/v1/e2b/job?id=' + encodeURIComponent(id) + '&job=' + encodeURIComponent(jobId), {
|
|
297
|
+
signal: AbortSignal.timeout(30_000),
|
|
298
|
+
headers: { 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
299
|
+
});
|
|
300
|
+
const pj = (await pr.json().catch(() => null));
|
|
301
|
+
if (!pj)
|
|
302
|
+
continue;
|
|
303
|
+
if (pj.status === 'done') {
|
|
304
|
+
const out = `exit ${pj.exitCode ?? '?'}\n${(pj.output || '').slice(0, OUT_CAP)}`;
|
|
305
|
+
return { output: out, summary: `e2b exec → exit ${pj.exitCode ?? '?'}`, data: { jobId, exitCode: pj.exitCode, output: pj.output } };
|
|
306
|
+
}
|
|
307
|
+
if (pj.status === 'starting' || pj.status === 'running') {
|
|
308
|
+
last = pj.output || last;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
return { output: `ERROR: e2b job error: ${pj.error || pj.status}`, summary: 'e2b job error', data: { jobId } };
|
|
312
|
+
}
|
|
313
|
+
return { output: `TIMEOUT menunggu job ${jobId}. Log terakhir:\n${last.slice(0, 4000)}`, summary: 'e2b exec polling timeout', data: { jobId } };
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
definition: {
|
|
318
|
+
name: 'webshare_proxies',
|
|
319
|
+
description: 'List the rotating proxies available to the agent (Webshare via gateway relay). Use to check availability/countries before e2b_run/e2b_cloud with proxy:true — the gateway injects the credentials automatically (never exposed). Kredensial asli di-mask; rotasi dipilih acak oleh gateway.',
|
|
320
|
+
parameters: { type: 'object', properties: {} },
|
|
321
|
+
},
|
|
322
|
+
async execute(_args, ctx) {
|
|
323
|
+
ctx.onActivity?.('e2b', 'webshare: list proxies');
|
|
324
|
+
const r = await fetchWebshareProxies();
|
|
325
|
+
if (r.error)
|
|
326
|
+
return { output: `ERROR: webshare relay — ${(0, credentials_1.redact)(r.error)}`, summary: 'webshare relay error' };
|
|
327
|
+
if (!r.proxies.length)
|
|
328
|
+
return { output: 'tidak ada proxy tersedia di akun Webshare (0 proxy).', summary: 'webshare: 0 proxy' };
|
|
329
|
+
const lines = r.proxies.map((p) => `${p.proxy_address}:${p.port} [${p.country_code}] ${p.valid ? 'valid' : 'INVALID'}`);
|
|
330
|
+
return {
|
|
331
|
+
output: `${r.count} proxy (kredensial otomatis di-inject gateway saat proxy:true):
|
|
332
|
+
${lines.join('\n')}`,
|
|
333
|
+
summary: `webshare: ${r.count} proxy`,
|
|
334
|
+
data: { count: r.count },
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
definition: {
|
|
340
|
+
name: 'e2b_download',
|
|
341
|
+
description: 'Download a file (artifact) from a persistent e2b cloud session — e.g. Playwright screenshots (/home/user/shots/*.png), logs, or build outputs. Returns the file content as base64 (prefix data with file type when writing locally, e.g. data:image/png;base64,).',
|
|
342
|
+
parameters: {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {
|
|
345
|
+
id: { type: 'string', description: 'Session id from e2b_cloud create' },
|
|
346
|
+
path: { type: 'string', description: 'Absolute path of the file in the sandbox' },
|
|
347
|
+
},
|
|
348
|
+
required: ['id', 'path'],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
async execute(args) {
|
|
352
|
+
const id = String(args.id || '');
|
|
353
|
+
const path = String(args.path || '').trim();
|
|
354
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
355
|
+
return { output: 'ERROR: invalid session id' };
|
|
356
|
+
if (!/^\/(home\/user\/|tmp\/)/.test(path) || path.includes('..'))
|
|
357
|
+
return { output: 'ERROR: path harus absolut di bawah /home/user/ atau /tmp/' };
|
|
358
|
+
const res = await fetch(GW_BASE + '/v1/e2b/file?id=' + encodeURIComponent(id) + '&path=' + encodeURIComponent(path), {
|
|
359
|
+
signal: AbortSignal.timeout(60_000),
|
|
360
|
+
headers: { 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
361
|
+
});
|
|
362
|
+
if (!res.ok) {
|
|
363
|
+
const t = await res.text().catch(() => '');
|
|
364
|
+
return { output: `ERROR: e2b download HTTP ${res.status}${t ? ' — ' + (0, credentials_1.redact)(t.slice(0, 200)) : ''}`, summary: 'e2b download error' };
|
|
365
|
+
}
|
|
366
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
367
|
+
let bin = '';
|
|
368
|
+
for (let i = 0; i < buf.length; i++)
|
|
369
|
+
bin += String.fromCharCode(buf[i]);
|
|
370
|
+
return {
|
|
371
|
+
output: `downloaded ${path} (${buf.length} bytes)\nbase64:\n${btoa(bin)}`,
|
|
372
|
+
summary: `e2b download ${path} (${buf.length}B)`,
|
|
373
|
+
data: { path, size: buf.length, base64: btoa(bin) },
|
|
374
|
+
};
|
|
375
|
+
},
|
|
376
|
+
},
|
|
143
377
|
];
|
|
144
378
|
}
|
package/package.json
CHANGED