agen-vektor 0.3.20 → 0.3.22
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 +2 -1
- package/dist/agent/agent.js +4 -0
- package/dist/tools/e2b.js +144 -0
- package/dist/tui/app.js +80 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -359,6 +359,7 @@ Every shell command is classified:
|
|
|
359
359
|
| `vector-tui.methatech.eu.org` (VectorHead Free gateway) | Only if you pick the free tier; the shared provider key stays server-side, users never see it |
|
|
360
360
|
| Brave / Exa / Serper | Only when you use the web-search tool with your own key |
|
|
361
361
|
| `vector-tui.methatech.eu.org/v1/web-search` | Web search when you have NO search key of your own — the query is relayed by the gateway; your own key, when set, always takes precedence and bypasses the relay |
|
|
362
|
+
| `vector-tui.methatech.eu.org/v1/e2b/run` | `e2b_run` tool — run a command in a disposable cloud sandbox (E2B VM) when the local machine is too weak/restricted; relayed by the gateway, no E2B key needed client-side |
|
|
362
363
|
| Raw model catalog (public JSON) | To populate the free-tier model list |
|
|
363
364
|
|
|
364
365
|
**Stored locally** (all in `~/.vector/`): `config.json` (preferences, no secrets), `credentials.json` (only keys *you* enter, file mode 0600), sessions, memory, logs. Nothing else is written anywhere.
|
|
@@ -408,7 +409,7 @@ npm run build
|
|
|
408
409
|
- **"model not found" on the free tier** — the free catalog rotates; run `/model` to pick from the live list.
|
|
409
410
|
- **"Rate limited (429) / cooling down"** — VectorHead retries automatically with backoff, and the free gateway cools down briefly instead of burning the shared key. Switch to another free model via `/model` and retry.
|
|
410
411
|
- **Nothing happens on `vector` in Termux** — make sure your terminal is a TTY and TERM is set (e.g. `export TERM=xterm-256color`). If glyphs look broken (█ everywhere, torn logo), ASCII safe mode is already default-on; desktop users can opt back in with `VECTOR_ASCII=0`.
|
|
411
|
-
- **Runs out of iterations** — raise `maxIterations`
|
|
412
|
+
- **Runs out of iterations** — open `/settings` in the TUI and press `+`/`-` (or PgUp/PgDn, or type a number) to raise `maxIterations` — persisted to `~/.vector/config.json` — or refine the request.
|
|
412
413
|
|
|
413
414
|
## Roadmap
|
|
414
415
|
|
package/dist/agent/agent.js
CHANGED
|
@@ -8,6 +8,7 @@ const search_1 = require("../tools/search");
|
|
|
8
8
|
const shell_1 = require("../tools/shell");
|
|
9
9
|
const git_1 = require("../tools/git");
|
|
10
10
|
const web_1 = require("../tools/web");
|
|
11
|
+
const e2b_1 = require("../tools/e2b");
|
|
11
12
|
const extras_1 = require("../tools/extras");
|
|
12
13
|
const apply_patch_1 = require("../tools/apply-patch");
|
|
13
14
|
const permissions_1 = require("../security/permissions");
|
|
@@ -59,6 +60,9 @@ class Agent {
|
|
|
59
60
|
(0, shell_1.createShellTool)(),
|
|
60
61
|
(0, git_1.createGitTool)(),
|
|
61
62
|
...(0, web_1.createWebTools)(),
|
|
63
|
+
// E2B cloud sandbox (via gateway relay): e2b_run / e2b_status —
|
|
64
|
+
// eksekusi di VM cloud utk perangkat lemah, zero-config (key di gateway).
|
|
65
|
+
...(0, e2b_1.createE2bTools)(),
|
|
62
66
|
// Freebuff-style extras: glob, read_subtree, task_completed,
|
|
63
67
|
// suggest_followups, render_ui.
|
|
64
68
|
...(0, extras_1.createExtrasTools)(),
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createE2bTools = createE2bTools;
|
|
4
|
+
const credentials_1 = require("../config/credentials");
|
|
5
|
+
const free_tier_1 = require("../config/free-tier");
|
|
6
|
+
const GW_BASE = free_tier_1.FREE_GATEWAY_URL.replace(/\/v1$/, '');
|
|
7
|
+
// Poll interval env-overridable (test memakai VECTOR_E2B_POLL_MS=10).
|
|
8
|
+
const POLL_INTERVAL_MS = Math.max(10, Number(process.env.VECTOR_E2B_POLL_MS) || 3_000);
|
|
9
|
+
const POLL_MAX_MS = 320_000; // > E2B_MAX_TIMEOUT_MS worker (300s) + margin
|
|
10
|
+
const OUT_CAP = 20_000;
|
|
11
|
+
async function postRun(command, timeoutMs) {
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const timer = setTimeout(() => controller.abort(), 30_000);
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch(GW_BASE + '/v1/e2b/run', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
signal: controller.signal,
|
|
18
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
19
|
+
body: JSON.stringify({ command, timeout_ms: timeoutMs }),
|
|
20
|
+
});
|
|
21
|
+
const data = (await res.json().catch(() => null));
|
|
22
|
+
return { res, data };
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function fetchStatus(id) {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timer = setTimeout(() => controller.abort(), 20_000);
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(GW_BASE + '/v1/e2b/status?id=' + encodeURIComponent(id), {
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
headers: { 'User-Agent': 'VectorHead-AI-Agent/0.1' },
|
|
35
|
+
});
|
|
36
|
+
const data = (await res.json().catch(() => null));
|
|
37
|
+
return { res, data };
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function formatResult(s) {
|
|
44
|
+
const exit = s.exitCode === null || s.exitCode === undefined ? '?' : s.exitCode;
|
|
45
|
+
let out = `exit ${exit}`;
|
|
46
|
+
if (s.stdout)
|
|
47
|
+
out += '\n' + s.stdout.slice(0, OUT_CAP);
|
|
48
|
+
if (s.stderr)
|
|
49
|
+
out += (s.stdout ? '\n[stderr]\n' : '') + s.stderr.slice(0, Math.min(OUT_CAP, 10_000));
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
async function pollUntilDone(id) {
|
|
53
|
+
const deadline = Date.now() + POLL_MAX_MS;
|
|
54
|
+
let last = null;
|
|
55
|
+
while (Date.now() < deadline) {
|
|
56
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
57
|
+
const { res, data } = await fetchStatus(id);
|
|
58
|
+
if (!res.ok || !data)
|
|
59
|
+
continue; // gesit — coba lagi sampai deadline
|
|
60
|
+
last = data;
|
|
61
|
+
if (data.status === 'done' || data.status === 'error')
|
|
62
|
+
return data;
|
|
63
|
+
}
|
|
64
|
+
return last;
|
|
65
|
+
}
|
|
66
|
+
function createE2bTools() {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
definition: {
|
|
70
|
+
name: 'e2b_run',
|
|
71
|
+
description: 'Run a shell command inside a disposable cloud sandbox (E2B VM via the VectorHead gateway relay) and wait for the result. Use when the local machine is too weak/restricted for a task — builds, installs, or tests that do not fit locally. Network is available in the sandbox. Returns exit code + stdout/stderr (this tool blocks until the command finishes; timeout default 120s, max 300s). Destructive commands (mkfs, fork bombs, pipe-to-shell) are rejected by the relay policy.',
|
|
72
|
+
parameters: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
command: { type: 'string', description: 'The shell command to run in the sandbox (bash -lc; use && to chain)' },
|
|
76
|
+
timeout_ms: { type: 'number', description: 'Timeout in ms (default 120000, max 300000)' },
|
|
77
|
+
},
|
|
78
|
+
required: ['command'],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
async execute(args, ctx) {
|
|
82
|
+
const command = String(args.command || '').trim();
|
|
83
|
+
if (!command)
|
|
84
|
+
return { output: 'ERROR: provide a "command"' };
|
|
85
|
+
const timeoutMs = Math.min(300_000, Math.max(5_000, Number(args.timeout_ms) || 120_000));
|
|
86
|
+
ctx.onActivity?.('e2b', `sandbox: ${command.slice(0, 80)}`);
|
|
87
|
+
const { res, data } = await postRun(command, timeoutMs);
|
|
88
|
+
if (!res.ok || !data || !data.id) {
|
|
89
|
+
const why = data?.error ? ` — ${(0, credentials_1.redact)(data.error)}` : '';
|
|
90
|
+
return { output: `ERROR: e2b relay HTTP ${res.status}${why}`, summary: 'e2b relay error' };
|
|
91
|
+
}
|
|
92
|
+
const done = await pollUntilDone(data.id);
|
|
93
|
+
if (!done) {
|
|
94
|
+
return {
|
|
95
|
+
output: `ERROR: e2b job ${data.id} belum selesai dan polling kehabisan waktu — cek manual: curl '${GW_BASE}/v1/e2b/status?id=${data.id}'`,
|
|
96
|
+
summary: 'e2b polling timeout',
|
|
97
|
+
data: { id: data.id },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (done.status === 'error') {
|
|
101
|
+
return {
|
|
102
|
+
output: `ERROR: e2b job failed${done.error ? ': ' + (0, credentials_1.redact)(done.error) : ''}`,
|
|
103
|
+
summary: 'e2b job error',
|
|
104
|
+
data: { id: data.id },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const out = formatResult(done);
|
|
108
|
+
return {
|
|
109
|
+
output: out,
|
|
110
|
+
summary: `e2b sandbox → exit ${done.exitCode ?? '?'}`,
|
|
111
|
+
data: { id: data.id, exitCode: done.exitCode, stdout: done.stdout, stderr: done.stderr },
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
definition: {
|
|
117
|
+
name: 'e2b_status',
|
|
118
|
+
description: 'Check the result of an E2B sandbox job by id (from e2b_run): status (running|done|error), exit code, stdout/stderr. Only needed when you kept an id and want the latest state without polling yourself.',
|
|
119
|
+
parameters: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
id: { type: 'string', description: 'Job id returned by e2b_run' },
|
|
123
|
+
},
|
|
124
|
+
required: ['id'],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
async execute(args) {
|
|
128
|
+
const id = String(args.id || '').trim();
|
|
129
|
+
if (!/^[A-Za-z0-9-]{8,80}$/.test(id))
|
|
130
|
+
return { output: 'ERROR: invalid job id' };
|
|
131
|
+
const { res, data } = await fetchStatus(id);
|
|
132
|
+
if (!res.ok || !data) {
|
|
133
|
+
const why = data?.error ? ` — ${(0, credentials_1.redact)(data.error)}` : '';
|
|
134
|
+
return { output: `ERROR: e2b status HTTP ${res.status}${why}`, summary: 'e2b status error' };
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
output: data.status === 'done' || data.status === 'error' ? formatResult(data) : `status: ${data.status}`,
|
|
138
|
+
summary: `e2b job ${id}: ${data.status}`,
|
|
139
|
+
data,
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
}
|
package/dist/tui/app.js
CHANGED
|
@@ -2373,7 +2373,6 @@ class App {
|
|
|
2373
2373
|
case 'help':
|
|
2374
2374
|
case 'commands':
|
|
2375
2375
|
case 'status':
|
|
2376
|
-
case 'settings':
|
|
2377
2376
|
if (ev.name === 'escape' || ev.name === 'enter' || ev.name === 'ctrl_c' || (ev.name === 'char' && ev.char === '?')) {
|
|
2378
2377
|
this.modal = { type: 'none' };
|
|
2379
2378
|
}
|
|
@@ -2392,6 +2391,74 @@ class App {
|
|
|
2392
2391
|
this.followBottom = this.scroll >= Math.max(0, all.length - this.chatHeight());
|
|
2393
2392
|
}
|
|
2394
2393
|
break;
|
|
2394
|
+
case 'settings':
|
|
2395
|
+
// /settings is mostly read-only, but Max iterations is LIVE-EDITABLE:
|
|
2396
|
+
// hitting the iteration limit mid-task used to require manual
|
|
2397
|
+
// ~/.vector/config.json surgery. +/- (and up/down) step the budget,
|
|
2398
|
+
// 0 and number keys type an exact value, Enter/commit keeps the
|
|
2399
|
+
// dialog open (backspace too — stray presses must not vaporize it),
|
|
2400
|
+
// Esc still closes. Changes persist via saveConfig immediately.
|
|
2401
|
+
{
|
|
2402
|
+
const cfg = this.agent.config;
|
|
2403
|
+
const step = (d) => {
|
|
2404
|
+
const next = Math.min(200, Math.max(1, cfg.maxIterations + d));
|
|
2405
|
+
if (next === cfg.maxIterations)
|
|
2406
|
+
return;
|
|
2407
|
+
cfg.maxIterations = next;
|
|
2408
|
+
try {
|
|
2409
|
+
(0, config_1.saveConfig)(cfg);
|
|
2410
|
+
}
|
|
2411
|
+
catch {
|
|
2412
|
+
/* read-only home — in-memory value still applies this session */
|
|
2413
|
+
}
|
|
2414
|
+
this.addSystem(`Max iterations: ${next}.`);
|
|
2415
|
+
};
|
|
2416
|
+
if (ev.name === 'escape') {
|
|
2417
|
+
this.modal = { type: 'none' };
|
|
2418
|
+
}
|
|
2419
|
+
else if (ev.name === 'enter' || ev.name === 'ctrl_c') {
|
|
2420
|
+
this.addSystem(`Max iterations: ${cfg.maxIterations} (tersimpan di ~/.vector/config.json).`);
|
|
2421
|
+
}
|
|
2422
|
+
else if (ev.name === 'up' || ev.name === 'char' && ev.char === '+') {
|
|
2423
|
+
step(+1);
|
|
2424
|
+
}
|
|
2425
|
+
else if (ev.name === 'down' || ev.name === 'char' && ev.char === '-') {
|
|
2426
|
+
step(-1);
|
|
2427
|
+
}
|
|
2428
|
+
else if (ev.name === 'pageup') {
|
|
2429
|
+
step(+10);
|
|
2430
|
+
}
|
|
2431
|
+
else if (ev.name === 'pagedown') {
|
|
2432
|
+
step(-10);
|
|
2433
|
+
}
|
|
2434
|
+
else if (ev.name === 'char' && ev.char === '0') {
|
|
2435
|
+
step(-cfg.maxIterations + 1);
|
|
2436
|
+
}
|
|
2437
|
+
else if (ev.name === 'char' && ev.char && /[0-9]/.test(ev.char)) {
|
|
2438
|
+
// Number keys type the exact value (not digit-append — no
|
|
2439
|
+
// visible field, so append would be invisible state).
|
|
2440
|
+
const next = Math.min(200, Math.max(1, Number(ev.char)));
|
|
2441
|
+
if (next !== cfg.maxIterations) {
|
|
2442
|
+
cfg.maxIterations = next;
|
|
2443
|
+
try {
|
|
2444
|
+
(0, config_1.saveConfig)(cfg);
|
|
2445
|
+
}
|
|
2446
|
+
catch {
|
|
2447
|
+
/* read-only home */
|
|
2448
|
+
}
|
|
2449
|
+
this.addSystem(`Max iterations: ${next}.`);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
else if (ev.name === 'char' && ev.char === '?') {
|
|
2453
|
+
this.modal = { type: 'none' };
|
|
2454
|
+
}
|
|
2455
|
+
else if (ev.name === 'char' && ev.char) {
|
|
2456
|
+
// Other letters: keep the old close-and-insert behavior.
|
|
2457
|
+
this.modal = { type: 'none' };
|
|
2458
|
+
this.input.insert(ev.char);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
break;
|
|
2395
2462
|
case 'provider':
|
|
2396
2463
|
await this.handleSelector(modal, ev, this.providerPickerOptions(), async (opt) => {
|
|
2397
2464
|
const c = this.agent.config;
|
|
@@ -3166,7 +3233,9 @@ class App {
|
|
|
3166
3233
|
label('Model', this.agent.config.model),
|
|
3167
3234
|
label('Theme', this.agent.config.theme || 'vectorhead'),
|
|
3168
3235
|
label('Mode', this.agent.config.permissionMode),
|
|
3169
|
-
|
|
3236
|
+
// LIVE-EDITABLE: +/- / ↑↓ / PgUp-PgDn step the budget, digits
|
|
3237
|
+
// type an exact value — persisted to ~/.vector/config.json.
|
|
3238
|
+
label('Max iterations', `${this.agent.config.maxIterations} ${theme_1.THEME.muted}(+/- / ↑↓ / PgUp-PgDn / angka)${theme_1.THEME.reset}`),
|
|
3170
3239
|
label('Max retries', String(this.agent.config.maxRetries)),
|
|
3171
3240
|
label('API URL', this.agent.config.apiUrl || '(default)'),
|
|
3172
3241
|
label('Key configured', (0, credentials_1.hasApiKey)(this.agent.config.provider) ? 'yes' : 'no'),
|
|
@@ -3184,7 +3253,7 @@ class App {
|
|
|
3184
3253
|
label('Env vars', 'VECTOR_API_KEY VECTOR_API_URL'),
|
|
3185
3254
|
label('', 'VECTOR_MODEL VECTOR_PROVIDER'),
|
|
3186
3255
|
],
|
|
3187
|
-
footer: 'Esc
|
|
3256
|
+
footer: 'Esc close · +/- ubah max iterations (tersimpan otomatis)',
|
|
3188
3257
|
}, rows, cols);
|
|
3189
3258
|
}
|
|
3190
3259
|
case 'status':
|
|
@@ -3216,9 +3285,16 @@ class App {
|
|
|
3216
3285
|
body: ['Fetching models from provider…', '', 'Esc to cancel'],
|
|
3217
3286
|
}, rows, cols);
|
|
3218
3287
|
}
|
|
3219
|
-
if (m.error) {
|
|
3288
|
+
if (m.error && m.options.length === 0) {
|
|
3220
3289
|
// Fetch failed (bad/missing URL or stored key) — show the real
|
|
3221
3290
|
// reason (e.g. "HTTP 401 …") instead of a misleading 1-item list.
|
|
3291
|
+
// BUT only when there is NOTHING selectable: if the provider def
|
|
3292
|
+
// declares models in config, those options were already merged in
|
|
3293
|
+
// (openModelModal always populates options from the fallback list
|
|
3294
|
+
// before setting error) and the picker must stay usable — endpoints
|
|
3295
|
+
// without GET /models (e.g. Cloudflare Workers AI compat: HTTP 405
|
|
3296
|
+
// "GET not supported") would otherwise brick /model entirely
|
|
3297
|
+
// (live VPS 2026-09-10).
|
|
3222
3298
|
return (0, components_1.renderModal)({
|
|
3223
3299
|
title: `Model (${(0, factory_1.displayProviderName)(this.agent.config)})`,
|
|
3224
3300
|
body: [m.error, '', 'Esc to cancel'],
|
package/package.json
CHANGED