agen-vektor 0.3.23 → 0.3.24
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 +199 -0
- 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
|
@@ -140,5 +140,204 @@ function createE2bTools() {
|
|
|
140
140
|
};
|
|
141
141
|
},
|
|
142
142
|
},
|
|
143
|
+
{
|
|
144
|
+
// ------------------------------------------------------------------
|
|
145
|
+
// CLOUD SESSION: sandbox persisten utk alur multi-langkah. Workflow
|
|
146
|
+
// browser testing (Chromium/Playwright headless DIJALANKAN DI SANDBOX):
|
|
147
|
+
// 1. e2b_cloud {action:'create'}
|
|
148
|
+
// 2. e2b_upload per file proyek (path absolut /home/user/...)
|
|
149
|
+
// 3. e2b_exec: npm install && npx playwright install --with-deps
|
|
150
|
+
// chromium (SEKALI per session — install tersimpan di sandbox)
|
|
151
|
+
// 4. e2b_exec: node test.mjs (script playwright; screenshot →
|
|
152
|
+
// /home/user/shots/...). ⚠️ chromium.launch WAJIB args:
|
|
153
|
+
// --no-sandbox --disable-dev-shm-usage --disable-gpu
|
|
154
|
+
// --js-flags=--jitless (tanpa itu → Target crashed: V8 CodeRange
|
|
155
|
+
// OOM di VM kecil; --single-process dilarang — exit 13)
|
|
156
|
+
// 5. poll e2b_exec (jobId) sampai status done → baca output/exit
|
|
157
|
+
// 6. e2b_download artefak (screenshot/log) bila perlu
|
|
158
|
+
// 7. e2b_cloud {action:'close'} — WAJIB di akhir (kredit shared)
|
|
159
|
+
// ------------------------------------------------------------------
|
|
160
|
+
definition: {
|
|
161
|
+
name: 'e2b_cloud',
|
|
162
|
+
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).',
|
|
163
|
+
parameters: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
action: { type: 'string', enum: ['create', 'close'], description: 'create = new persistent sandbox; close = kill it' },
|
|
167
|
+
id: { type: 'string', description: 'Session id (required for close)' },
|
|
168
|
+
ttl_s: { type: 'number', description: 'Session lifetime in seconds for create (default 1800, max 3600)' },
|
|
169
|
+
},
|
|
170
|
+
required: ['action'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
async execute(args, ctx) {
|
|
174
|
+
const action = String(args.action || '');
|
|
175
|
+
if (action === 'create') {
|
|
176
|
+
const ttl = Math.min(3600, Math.max(120, Number(args.ttl_s) || 1800));
|
|
177
|
+
ctx.onActivity?.('e2b', `cloud session create (${ttl}s)`);
|
|
178
|
+
const res = await fetch(GW_BASE + '/v1/e2b/session', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
181
|
+
body: JSON.stringify({ ttl_s: ttl }),
|
|
182
|
+
signal: AbortSignal.timeout(60_000),
|
|
183
|
+
});
|
|
184
|
+
const data = (await res.json().catch(() => null));
|
|
185
|
+
if (!res.ok || !data?.id) {
|
|
186
|
+
const why = data?.error ? ` — ${(0, credentials_1.redact)(data.error)}` : '';
|
|
187
|
+
return { output: `ERROR: e2b session HTTP ${res.status}${why}`, summary: 'e2b session error' };
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
output: `session created: ${data.id} (ttl ${data.ttl}s)\nNext: e2b_upload files → e2b_exec commands (poll jobId) → e2b_download artifacts → e2b_cloud close.\nSession id: ${data.id}`,
|
|
191
|
+
summary: `e2b session ${data.id}`,
|
|
192
|
+
data,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (action === 'close') {
|
|
196
|
+
const id = String(args.id || '');
|
|
197
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
198
|
+
return { output: 'ERROR: provide the session "id"' };
|
|
199
|
+
ctx.onActivity?.('e2b', `cloud session close ${id}`);
|
|
200
|
+
const res = await fetch(GW_BASE + '/v1/e2b/close?id=' + encodeURIComponent(id), { method: 'POST', signal: AbortSignal.timeout(30_000) });
|
|
201
|
+
if (!res.ok)
|
|
202
|
+
return { output: `ERROR: e2b close HTTP ${res.status}`, summary: 'e2b close error' };
|
|
203
|
+
return { output: `session ${id} closed (sandbox killed).`, summary: `e2b session ${id} closed` };
|
|
204
|
+
}
|
|
205
|
+
return { output: 'ERROR: action harus create|close' };
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
definition: {
|
|
210
|
+
name: 'e2b_upload',
|
|
211
|
+
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/.',
|
|
212
|
+
parameters: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {
|
|
215
|
+
id: { type: 'string', description: 'Session id from e2b_cloud create' },
|
|
216
|
+
path: { type: 'string', description: 'Absolute path in the sandbox, e.g. /home/user/app/package.json' },
|
|
217
|
+
content_b64: { type: 'string', description: 'File content encoded as base64' },
|
|
218
|
+
},
|
|
219
|
+
required: ['id', 'path', 'content_b64'],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
async execute(args) {
|
|
223
|
+
const id = String(args.id || '');
|
|
224
|
+
const path = String(args.path || '').trim();
|
|
225
|
+
const content = String(args.content_b64 || '');
|
|
226
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
227
|
+
return { output: 'ERROR: invalid session id' };
|
|
228
|
+
if (!/^\/(home\/user\/|tmp\/)/.test(path) || path.includes('..'))
|
|
229
|
+
return { output: 'ERROR: path harus absolut di bawah /home/user/ atau /tmp/' };
|
|
230
|
+
if (!content)
|
|
231
|
+
return { output: 'ERROR: content_b64 kosong' };
|
|
232
|
+
const res = await fetch(GW_BASE + '/v1/e2b/upload?id=' + encodeURIComponent(id), {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
235
|
+
body: JSON.stringify({ path, content_b64: content }),
|
|
236
|
+
signal: AbortSignal.timeout(120_000),
|
|
237
|
+
});
|
|
238
|
+
const data = (await res.json().catch(() => null));
|
|
239
|
+
if (!res.ok || !data?.ok) {
|
|
240
|
+
const why = data?.error ? ` — ${(0, credentials_1.redact)(data.error)}` : '';
|
|
241
|
+
return { output: `ERROR: e2b upload HTTP ${res.status}${why}`, summary: 'e2b upload error' };
|
|
242
|
+
}
|
|
243
|
+
return { output: `uploaded: ${path} (${data.size} bytes)`, summary: `e2b upload ${path}` };
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
definition: {
|
|
248
|
+
name: 'e2b_exec',
|
|
249
|
+
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).',
|
|
250
|
+
parameters: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
id: { type: 'string', description: 'Session id from e2b_cloud create' },
|
|
254
|
+
command: { type: 'string', description: 'Shell command to run in the sandbox (bash -lc)' },
|
|
255
|
+
},
|
|
256
|
+
required: ['id', 'command'],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
async execute(args, ctx) {
|
|
260
|
+
const id = String(args.id || '');
|
|
261
|
+
const command = String(args.command || '').trim();
|
|
262
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
263
|
+
return { output: 'ERROR: invalid session id' };
|
|
264
|
+
if (!command)
|
|
265
|
+
return { output: 'ERROR: provide a "command"' };
|
|
266
|
+
ctx.onActivity?.('e2b', `exec: ${command.slice(0, 80)}`);
|
|
267
|
+
const start = await fetch(GW_BASE + '/v1/e2b/exec?id=' + encodeURIComponent(id), {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
270
|
+
body: JSON.stringify({ command }),
|
|
271
|
+
signal: AbortSignal.timeout(30_000),
|
|
272
|
+
});
|
|
273
|
+
const startData = (await start.json().catch(() => null));
|
|
274
|
+
if (!start.ok || !startData?.jobId) {
|
|
275
|
+
const why = startData?.error ? ` — ${(0, credentials_1.redact)(startData.error)}` : '';
|
|
276
|
+
return { output: `ERROR: e2b exec HTTP ${start.status}${why}`, summary: 'e2b exec error' };
|
|
277
|
+
}
|
|
278
|
+
const jobId = startData.jobId;
|
|
279
|
+
const deadline = Date.now() + POLL_MAX_MS;
|
|
280
|
+
let last = '';
|
|
281
|
+
while (Date.now() < deadline) {
|
|
282
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
283
|
+
const pr = await fetch(GW_BASE + '/v1/e2b/job?id=' + encodeURIComponent(id) + '&job=' + encodeURIComponent(jobId), {
|
|
284
|
+
signal: AbortSignal.timeout(30_000),
|
|
285
|
+
headers: { 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
286
|
+
});
|
|
287
|
+
const pj = (await pr.json().catch(() => null));
|
|
288
|
+
if (!pj)
|
|
289
|
+
continue;
|
|
290
|
+
if (pj.status === 'done') {
|
|
291
|
+
const out = `exit ${pj.exitCode ?? '?'}\n${(pj.output || '').slice(0, OUT_CAP)}`;
|
|
292
|
+
return { output: out, summary: `e2b exec → exit ${pj.exitCode ?? '?'}`, data: { jobId, exitCode: pj.exitCode, output: pj.output } };
|
|
293
|
+
}
|
|
294
|
+
if (pj.status === 'starting' || pj.status === 'running') {
|
|
295
|
+
last = pj.output || last;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
return { output: `ERROR: e2b job error: ${pj.error || pj.status}`, summary: 'e2b job error', data: { jobId } };
|
|
299
|
+
}
|
|
300
|
+
return { output: `TIMEOUT menunggu job ${jobId}. Log terakhir:\n${last.slice(0, 4000)}`, summary: 'e2b exec polling timeout', data: { jobId } };
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
definition: {
|
|
305
|
+
name: 'e2b_download',
|
|
306
|
+
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,).',
|
|
307
|
+
parameters: {
|
|
308
|
+
type: 'object',
|
|
309
|
+
properties: {
|
|
310
|
+
id: { type: 'string', description: 'Session id from e2b_cloud create' },
|
|
311
|
+
path: { type: 'string', description: 'Absolute path of the file in the sandbox' },
|
|
312
|
+
},
|
|
313
|
+
required: ['id', 'path'],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
async execute(args) {
|
|
317
|
+
const id = String(args.id || '');
|
|
318
|
+
const path = String(args.path || '').trim();
|
|
319
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
320
|
+
return { output: 'ERROR: invalid session id' };
|
|
321
|
+
if (!/^\/(home\/user\/|tmp\/)/.test(path) || path.includes('..'))
|
|
322
|
+
return { output: 'ERROR: path harus absolut di bawah /home/user/ atau /tmp/' };
|
|
323
|
+
const res = await fetch(GW_BASE + '/v1/e2b/file?id=' + encodeURIComponent(id) + '&path=' + encodeURIComponent(path), {
|
|
324
|
+
signal: AbortSignal.timeout(60_000),
|
|
325
|
+
headers: { 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
326
|
+
});
|
|
327
|
+
if (!res.ok) {
|
|
328
|
+
const t = await res.text().catch(() => '');
|
|
329
|
+
return { output: `ERROR: e2b download HTTP ${res.status}${t ? ' — ' + (0, credentials_1.redact)(t.slice(0, 200)) : ''}`, summary: 'e2b download error' };
|
|
330
|
+
}
|
|
331
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
332
|
+
let bin = '';
|
|
333
|
+
for (let i = 0; i < buf.length; i++)
|
|
334
|
+
bin += String.fromCharCode(buf[i]);
|
|
335
|
+
return {
|
|
336
|
+
output: `downloaded ${path} (${buf.length} bytes)\nbase64:\n${btoa(bin)}`,
|
|
337
|
+
summary: `e2b download ${path} (${buf.length}B)`,
|
|
338
|
+
data: { path, size: buf.length, base64: btoa(bin) },
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
},
|
|
143
342
|
];
|
|
144
343
|
}
|
package/package.json
CHANGED