agen-vektor 0.3.19 → 0.3.21
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 +1 -1
- package/dist/tools/shell.js +63 -8
- package/dist/tui/app.js +80 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -408,7 +408,7 @@ npm run build
|
|
|
408
408
|
- **"model not found" on the free tier** — the free catalog rotates; run `/model` to pick from the live list.
|
|
409
409
|
- **"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
410
|
- **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`
|
|
411
|
+
- **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
412
|
|
|
413
413
|
## Roadmap
|
|
414
414
|
|
package/dist/tools/shell.js
CHANGED
|
@@ -7,39 +7,94 @@ exports.createShellTool = createShellTool;
|
|
|
7
7
|
* Every command is classified by the command policy and gated by permissions.
|
|
8
8
|
*/
|
|
9
9
|
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
// After the shell itself exits, wait this long for the stdio pipes to drain
|
|
11
|
+
// (normal commands flush and close within milliseconds) before giving up.
|
|
12
|
+
// A command that ORPHANS a child holding the pipes (background job, server,
|
|
13
|
+
// watcher …) would otherwise keep the tool — and the whole agent loop —
|
|
14
|
+
// hanging until the grandchild dies ("sering nyangkut di shell", 2026-09-10).
|
|
15
|
+
const ORPHAN_GRACE_MS = 500;
|
|
10
16
|
function runShell(command, cwd, opts = {}) {
|
|
11
17
|
return new Promise((resolve) => {
|
|
12
18
|
const timeoutMs = opts.timeoutMs || 120_000;
|
|
19
|
+
// Own process group (detached) → the timeout / orphan guard can kill the
|
|
20
|
+
// whole tree (shell AND grandchildren), same policy as background.ts.
|
|
13
21
|
const child = (0, node_child_process_1.spawn)(command, {
|
|
14
22
|
cwd,
|
|
15
23
|
shell: '/bin/sh',
|
|
16
24
|
env: { ...process.env, ...opts.env },
|
|
17
25
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
26
|
+
detached: true,
|
|
18
27
|
});
|
|
19
28
|
let stdout = '';
|
|
20
29
|
let stderr = '';
|
|
21
30
|
let timedOut = false;
|
|
31
|
+
let settled = false;
|
|
32
|
+
let graceTimer = null;
|
|
33
|
+
const killTree = () => {
|
|
34
|
+
if (child.pid != null) {
|
|
35
|
+
try {
|
|
36
|
+
process.kill(-child.pid, 'SIGKILL'); // detached spawn ⇒ own process group
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* group already gone */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
child.kill('SIGKILL');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
/* already gone */
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const finish = (code) => {
|
|
50
|
+
if (settled)
|
|
51
|
+
return;
|
|
52
|
+
settled = true;
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
if (graceTimer)
|
|
55
|
+
clearTimeout(graceTimer);
|
|
56
|
+
resolve({ stdout, stderr, exitCode: code, timedOut });
|
|
57
|
+
};
|
|
22
58
|
const timer = setTimeout(() => {
|
|
23
59
|
timedOut = true;
|
|
24
|
-
child.kill(
|
|
60
|
+
killTree(); // was: child.kill() only — orphaned children kept the pipe open and the promise hanging past the timeout
|
|
61
|
+
// 'exit' fires once the shell dies and resolves immediately.
|
|
25
62
|
}, timeoutMs);
|
|
26
63
|
child.stdout.on('data', (d) => {
|
|
27
64
|
stdout += d.toString();
|
|
28
65
|
if (stdout.length > 2_000_000)
|
|
29
|
-
|
|
66
|
+
killTree();
|
|
30
67
|
});
|
|
31
68
|
child.stderr.on('data', (d) => {
|
|
32
69
|
stderr += d.toString();
|
|
33
70
|
if (stderr.length > 2_000_000)
|
|
34
|
-
|
|
71
|
+
killTree();
|
|
72
|
+
});
|
|
73
|
+
// 'exit' = the shell itself died; 'close' = shell AND stdio pipes closed.
|
|
74
|
+
// Resolve on whichever happens first with a small grace window after
|
|
75
|
+
// 'exit': a normal command flushes and closes well inside the window, so
|
|
76
|
+
// full output still arrives — but an orphaned grandchild holding the
|
|
77
|
+
// pipes can no longer hang the tool past it.
|
|
78
|
+
child.on('exit', (code) => {
|
|
79
|
+
if (timedOut) {
|
|
80
|
+
finish(code);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (graceTimer)
|
|
84
|
+
return;
|
|
85
|
+
graceTimer = setTimeout(() => {
|
|
86
|
+
killTree(); // stop the orphan so its output stops too
|
|
87
|
+
finish(code);
|
|
88
|
+
}, ORPHAN_GRACE_MS);
|
|
35
89
|
});
|
|
36
90
|
child.on('close', (code) => {
|
|
37
|
-
|
|
38
|
-
resolve({ stdout, stderr, exitCode: code, timedOut });
|
|
91
|
+
finish(code);
|
|
39
92
|
});
|
|
40
93
|
child.on('error', (e) => {
|
|
41
|
-
|
|
42
|
-
|
|
94
|
+
// set stderr BEFORE finish — the resolved object snapshots these strings
|
|
95
|
+
if (!stdout && !stderr)
|
|
96
|
+
stderr = `spawn error: ${e.message}`;
|
|
97
|
+
finish(-1);
|
|
43
98
|
});
|
|
44
99
|
});
|
|
45
100
|
}
|
|
@@ -52,7 +107,7 @@ function createShellTool() {
|
|
|
52
107
|
return {
|
|
53
108
|
definition: {
|
|
54
109
|
name: 'shell',
|
|
55
|
-
description: 'Run a shell command in the project directory. The command is classified for safety; risky commands require permission. Use for building, testing, installing, git, or any command execution. Long output is truncated. For git commits prefer the dedicated `git` tool and follow its commit-attribution guidance.',
|
|
110
|
+
description: 'Run a shell command in the project directory. The command is classified for safety; risky commands require permission. Use for building, testing, installing, git, or any command execution. Long output is truncated. Commands that keep running (dev servers, watchers, polling loops) must use run_in_background instead — a foreground shell is killed after its timeout. For git commits prefer the dedicated `git` tool and follow its commit-attribution guidance.',
|
|
56
111
|
parameters: {
|
|
57
112
|
type: 'object',
|
|
58
113
|
properties: {
|
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