agen-vektor 0.1.0
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/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/agent/agent.js +90 -0
- package/dist/agent/context.js +106 -0
- package/dist/agent/loop.js +152 -0
- package/dist/agent/memory.js +87 -0
- package/dist/agent/planner.js +38 -0
- package/dist/agent/prompts.js +41 -0
- package/dist/agent/session.js +125 -0
- package/dist/cli/index.js +304 -0
- package/dist/cli/keyboard.js +283 -0
- package/dist/config/config.js +92 -0
- package/dist/config/credentials.js +127 -0
- package/dist/config/models.js +47 -0
- package/dist/providers/anthropic.js +276 -0
- package/dist/providers/custom.js +24 -0
- package/dist/providers/factory.js +58 -0
- package/dist/providers/gemini.js +223 -0
- package/dist/providers/ollama.js +195 -0
- package/dist/providers/openai-compat.js +296 -0
- package/dist/providers/openai.js +17 -0
- package/dist/providers/openrouter.js +23 -0
- package/dist/providers/provider.js +39 -0
- package/dist/security/command-policy.js +99 -0
- package/dist/security/permissions.js +66 -0
- package/dist/tools/filesystem.js +297 -0
- package/dist/tools/git.js +66 -0
- package/dist/tools/registry.js +39 -0
- package/dist/tools/search.js +184 -0
- package/dist/tools/shell.js +108 -0
- package/dist/tools/web.js +76 -0
- package/dist/tui/app.js +913 -0
- package/dist/tui/chat.js +76 -0
- package/dist/tui/components.js +85 -0
- package/dist/tui/diff.js +105 -0
- package/dist/tui/input.js +121 -0
- package/dist/tui/statusbar.js +32 -0
- package/dist/utils/logger.js +96 -0
- package/dist/utils/paths.js +90 -0
- package/dist/utils/terminal.js +191 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VECTOR contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# VECTOR
|
|
2
|
+
|
|
3
|
+
**AI Coding Agent for Linux & Termux** — a terminal-native coding agent that inspects your project, plans, edits code, runs commands and tests, and iterates until the task is done.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
7
|
+
│ VECTOR ● Ready openrouter · deepseek/deepseek-chat │
|
|
8
|
+
│ Project: ~/my-project Mode: ask │
|
|
9
|
+
├──────────────────────────────────────────────────────────────┤
|
|
10
|
+
│ │
|
|
11
|
+
│ YOU │
|
|
12
|
+
│ > Perbaiki error authentication pada project ini │
|
|
13
|
+
│ │
|
|
14
|
+
│ VECTOR │
|
|
15
|
+
│ ● Inspecting project... │
|
|
16
|
+
│ ● Searching authentication files │
|
|
17
|
+
│ │
|
|
18
|
+
│ TOOL │
|
|
19
|
+
│ $ npm test │
|
|
20
|
+
│ │
|
|
21
|
+
│ ✓ Tests passed │
|
|
22
|
+
│ │
|
|
23
|
+
├──────────────────────────────────────────────────────────────┤
|
|
24
|
+
│ > _ │
|
|
25
|
+
├──────────────────────────────────────────────────────────────┤
|
|
26
|
+
│ ENTER Send │ TAB Commands │ CTRL+C Stop │ ? Help │
|
|
27
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Interactive TUI** — chat, agent status, tool execution, diff viewer, input. Keyboard driven, mouse-aware, works on 80×24 terminals and survives resize.
|
|
33
|
+
- **Agent loop** — plan → inspect → tool call → result → review → fix → retry until done (with an iteration cap).
|
|
34
|
+
- **Tools** — read/write/edit files, list directories, search files, run shell commands, git, fetch web pages.
|
|
35
|
+
- **Security first** — command policy classifies every shell command (SAFE / ASK / DANGEROUS / BLOCKED), permission prompts before risky actions, `--yolo` mode with warnings, and API keys are never printed or persisted to sessions.
|
|
36
|
+
- **Multi-provider** — OpenAI, Anthropic, Gemini, OpenRouter, Ollama, and any OpenAI-compatible custom endpoint.
|
|
37
|
+
- **Sessions & memory** — conversations persist to `~/.vector/sessions/`; `/continue` resumes them.
|
|
38
|
+
- **Context management** — only relevant context is sent to the model, with compaction (`/compact`).
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Requirements: **Node.js 20+** and **npm** (Termux: `pkg install nodejs`).
|
|
43
|
+
|
|
44
|
+
### From source
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/clickmamaheti-prog/vector-agent.git
|
|
48
|
+
cd vector-agent
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
npm link # makes `vector` available on PATH
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
vector
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### From npm (once published)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install -g vector-cli
|
|
64
|
+
vector
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Termux (Android)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pkg update && pkg upgrade
|
|
71
|
+
pkg install nodejs git
|
|
72
|
+
npm install -g vector-cli
|
|
73
|
+
vector
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> VECTOR has **zero runtime dependencies** and does not assume `systemd`, `sudo`, `apt`, or a desktop GUI — everything runs in a plain terminal.
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd my-project
|
|
82
|
+
vector "Perbaiki error authentication pada project ini"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
or start the TUI and type your task:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
vector
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Command line
|
|
92
|
+
|
|
93
|
+
| Command | Description |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `vector` | Start the interactive terminal UI |
|
|
96
|
+
| `vector "task"` | Start TUI and run the task immediately |
|
|
97
|
+
| `vector --model MODEL` | Set the model |
|
|
98
|
+
| `vector --provider P` | Set provider: `openai`, `anthropic`, `gemini`, `openrouter`, `ollama`, `custom` |
|
|
99
|
+
| `vector --continue` | Continue the most recent session |
|
|
100
|
+
| `vector --session NAME` | Use/resume session `NAME` |
|
|
101
|
+
| `vector --plan` | Show a plan before executing |
|
|
102
|
+
| `vector --yolo` | Skip permission prompts for ask-level actions (with warning) |
|
|
103
|
+
| `vector --version` / `--help` | Version / help |
|
|
104
|
+
|
|
105
|
+
## TUI commands
|
|
106
|
+
|
|
107
|
+
Type `/` inside VECTOR:
|
|
108
|
+
|
|
109
|
+
| Command | Description |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `/model` | Select a model |
|
|
112
|
+
| `/provider` | Select a provider |
|
|
113
|
+
| `/session` | Browse saved sessions |
|
|
114
|
+
| `/settings` | Show configuration |
|
|
115
|
+
| `/status` | Show agent status |
|
|
116
|
+
| `/clear` | Clear the conversation |
|
|
117
|
+
| `/continue` | Continue the current/last session |
|
|
118
|
+
| `/diff` | Show diff viewer |
|
|
119
|
+
| `/git` | Run `git status` |
|
|
120
|
+
| `/compact` | Compact context (truncate old messages) |
|
|
121
|
+
| `/yolo` | Toggle permission mode |
|
|
122
|
+
| `/exit` | Quit |
|
|
123
|
+
|
|
124
|
+
## Provider configuration
|
|
125
|
+
|
|
126
|
+
Set an API key via environment variable:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export OPENAI_API_KEY=sk-...
|
|
130
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
131
|
+
export GEMINI_API_KEY=...
|
|
132
|
+
export OPENROUTER_API_KEY=...
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Or inside VECTOR: `/provider` → pick provider → paste key (stored masked in `~/.vector/credentials.json`, permissions `0600`).
|
|
136
|
+
|
|
137
|
+
Generic variables: `VECTOR_API_KEY`, `VECTOR_API_URL`, `VECTOR_MODEL`, `VECTOR_PROVIDER`, `VECTOR_HOME`.
|
|
138
|
+
|
|
139
|
+
### Custom API (OpenAI-compatible)
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
export VECTOR_API_URL=https://example.com/v1
|
|
143
|
+
export VECTOR_API_KEY=your-key
|
|
144
|
+
vector --provider custom --model deepseek-v3
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The provider speaks `/v1/chat/completions` (and `/models`). Anthropic and Gemini protocols for custom endpoints are documented as adapters; OpenAI-compatible endpoints are fully supported.
|
|
148
|
+
|
|
149
|
+
## Security model
|
|
150
|
+
|
|
151
|
+
Every shell command is classified:
|
|
152
|
+
|
|
153
|
+
- **SAFE** — read-only commands run without prompting (`ls`, `cat`, `git status`, …)
|
|
154
|
+
- **ASK** — prompts for confirmation (`npm install`, `npm test`, `git commit`, …)
|
|
155
|
+
- **DANGEROUS** — prompts with a warning (`rm -rf`, `chmod`, `git push`, …)
|
|
156
|
+
- **BLOCKED** — refused outright (`rm -rf /`, `mkfs`, `shutdown`, forced pushes, …)
|
|
157
|
+
|
|
158
|
+
`--yolo` auto-approves ASK-level actions but still prompts for DANGEROUS and refuses BLOCKED ones. API keys are never printed to the terminal, logs, diffs, or session history.
|
|
159
|
+
|
|
160
|
+
## Architecture
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
src/
|
|
164
|
+
├── cli/ argument parsing, keyboard, bootstrap
|
|
165
|
+
├── tui/ terminal UI: chat, input, statusbar, diff, components
|
|
166
|
+
├── agent/ agent loop, planner, context, memory, prompts, session
|
|
167
|
+
├── providers/ AIProvider interface + OpenAI/Anthropic/Gemini/OpenRouter/Ollama/Custom
|
|
168
|
+
├── tools/ tool registry + filesystem/shell/search/git/web
|
|
169
|
+
├── security/ command policy + permission manager
|
|
170
|
+
├── config/ config, credentials, model catalog
|
|
171
|
+
└── utils/ terminal (ANSI), logger, paths
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Providers and tools are pluggable: adding one never touches the agent core.
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm run dev # run from source with tsx
|
|
180
|
+
npm run build # compile TypeScript
|
|
181
|
+
npm test # run unit tests (node:test)
|
|
182
|
+
npm run typecheck # tsc --noEmit
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Testing
|
|
186
|
+
|
|
187
|
+
Unit tests cover providers, the agent loop, the tool registry, filesystem tools, the command policy, config, credentials, sessions, context manager, keyboard, diff, and CLI helpers.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npm test
|
|
191
|
+
npm run build
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Troubleshooting
|
|
195
|
+
|
|
196
|
+
- **"No API key configured"** — set the relevant `*_API_KEY` env var or run `/provider` in the TUI.
|
|
197
|
+
- **"401" / auth failed** — check the key, or for OpenRouter use `OPENROUTER_API_KEY`.
|
|
198
|
+
- **Nothing happens on `vector` in Termux** — make sure your terminal is a TTY and TERM is set (e.g. `export TERM=xterm-256color`).
|
|
199
|
+
- **Runs out of iterations** — raise `maxIterations` in `~/.vector/config.json` or refine the request.
|
|
200
|
+
- **Rate limited (429)** — VECTOR retries automatically with backoff; wait and retry.
|
|
201
|
+
|
|
202
|
+
## Roadmap
|
|
203
|
+
|
|
204
|
+
- Web search tool (provider-backed) behind the `web` abstraction
|
|
205
|
+
- Interactive diff approval before edits in safe mode
|
|
206
|
+
- Persistent permission rules per project
|
|
207
|
+
- `npm publish` as `vector-cli`
|
|
208
|
+
- Windows support (WSL-focused)
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Agent = void 0;
|
|
4
|
+
const factory_1 = require("../providers/factory");
|
|
5
|
+
const registry_1 = require("../tools/registry");
|
|
6
|
+
const filesystem_1 = require("../tools/filesystem");
|
|
7
|
+
const search_1 = require("../tools/search");
|
|
8
|
+
const shell_1 = require("../tools/shell");
|
|
9
|
+
const git_1 = require("../tools/git");
|
|
10
|
+
const web_1 = require("../tools/web");
|
|
11
|
+
const permissions_1 = require("../security/permissions");
|
|
12
|
+
const config_1 = require("../config/config");
|
|
13
|
+
const loop_1 = require("./loop");
|
|
14
|
+
class Agent {
|
|
15
|
+
config;
|
|
16
|
+
provider;
|
|
17
|
+
tools;
|
|
18
|
+
permissions;
|
|
19
|
+
cwd;
|
|
20
|
+
constructor(opts) {
|
|
21
|
+
this.config = (0, config_1.effectiveConfig)(opts.config);
|
|
22
|
+
this.cwd = opts.cwd || process.cwd();
|
|
23
|
+
this.provider = (0, factory_1.createProvider)(this.config);
|
|
24
|
+
this.permissions = new permissions_1.PermissionManager({
|
|
25
|
+
mode: this.config.permissionMode,
|
|
26
|
+
ask: opts.askPermission,
|
|
27
|
+
});
|
|
28
|
+
this.tools = new registry_1.ToolRegistry();
|
|
29
|
+
this.tools.registerMany([
|
|
30
|
+
...(0, filesystem_1.createFilesystemTools)(),
|
|
31
|
+
...(0, search_1.createSearchTools)(),
|
|
32
|
+
(0, shell_1.createShellTool)(),
|
|
33
|
+
(0, git_1.createGitTool)(),
|
|
34
|
+
(0, web_1.createWebTool)(),
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
async run(request, signal) {
|
|
38
|
+
return (0, loop_1.runAgentLoop)(request, {
|
|
39
|
+
provider: this.provider,
|
|
40
|
+
tools: this.tools,
|
|
41
|
+
permissions: this.permissions,
|
|
42
|
+
config: this.config,
|
|
43
|
+
cwd: this.cwd,
|
|
44
|
+
signal,
|
|
45
|
+
}, this.callbacks);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Run with optional prior session messages (non-interactive continue).
|
|
49
|
+
* If sessionMessages is provided, the task is appended after the history.
|
|
50
|
+
*/
|
|
51
|
+
async runWithContext(request, session, cb = {}) {
|
|
52
|
+
this.setCallbacks(cb);
|
|
53
|
+
if (!session)
|
|
54
|
+
return this.run(request);
|
|
55
|
+
// Replay history into the loop by prefixing the task request.
|
|
56
|
+
const context = session.messages
|
|
57
|
+
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
58
|
+
.slice(-30)
|
|
59
|
+
.map((m) => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content.slice(0, 2000)}`)
|
|
60
|
+
.join('\n\n');
|
|
61
|
+
return this.run(`[Previous conversation summary]\n${context}\n\n---\n\nTASK:\n${request}`);
|
|
62
|
+
}
|
|
63
|
+
/** Rebuild the provider (e.g. after changing provider id). */
|
|
64
|
+
rebuildProvider() {
|
|
65
|
+
this.provider = (0, factory_1.createProvider)(this.config);
|
|
66
|
+
}
|
|
67
|
+
callbacks = {};
|
|
68
|
+
setCallbacks(cb) {
|
|
69
|
+
this.callbacks = {
|
|
70
|
+
onStatus: cb.onStatus,
|
|
71
|
+
onDelta: cb.onDelta,
|
|
72
|
+
onToolCall: cb.onToolCall,
|
|
73
|
+
onToolResult: cb.onToolResult,
|
|
74
|
+
onIteration: cb.onIteration,
|
|
75
|
+
onFinal: cb.onFinal,
|
|
76
|
+
onError: cb.onError,
|
|
77
|
+
onActivity: cb.onActivity,
|
|
78
|
+
};
|
|
79
|
+
// Permissions callback must be live-updated as well
|
|
80
|
+
this.permissions.setMode(this.config.permissionMode);
|
|
81
|
+
}
|
|
82
|
+
setPermissionAsk(ask) {
|
|
83
|
+
this.permissions = new permissions_1.PermissionManager({ mode: this.config.permissionMode, ask });
|
|
84
|
+
}
|
|
85
|
+
setMode(mode) {
|
|
86
|
+
this.config.permissionMode = mode;
|
|
87
|
+
this.permissions.setMode(mode);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.Agent = Agent;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectProject = detectProject;
|
|
37
|
+
exports.buildProjectContext = buildProjectContext;
|
|
38
|
+
exports.listRootEntries = listRootEntries;
|
|
39
|
+
/**
|
|
40
|
+
* Context Manager — builds a compact, relevant project context.
|
|
41
|
+
*
|
|
42
|
+
* Strategy: never send the whole project. Detect the manifest/project type,
|
|
43
|
+
* read small key files, and keep a bounded message history.
|
|
44
|
+
*/
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const MANIFESTS = [
|
|
48
|
+
{ file: 'package.json', type: 'Node.js', maxBytes: 8000 },
|
|
49
|
+
{ file: 'tsconfig.json', type: 'TypeScript', maxBytes: 8000 },
|
|
50
|
+
{ file: 'requirements.txt', type: 'Python', maxBytes: 8000 },
|
|
51
|
+
{ file: 'pyproject.toml', type: 'Python', maxBytes: 8000 },
|
|
52
|
+
{ file: 'Cargo.toml', type: 'Rust', maxBytes: 8000 },
|
|
53
|
+
{ file: 'go.mod', type: 'Go', maxBytes: 8000 },
|
|
54
|
+
{ file: 'composer.json', type: 'PHP', maxBytes: 8000 },
|
|
55
|
+
{ file: 'Makefile', type: 'Make', maxBytes: 8000 },
|
|
56
|
+
{ file: 'pom.xml', type: 'Java', maxBytes: 8000 },
|
|
57
|
+
{ file: 'Gemfile', type: 'Ruby', maxBytes: 8000 },
|
|
58
|
+
{ file: 'mix.exs', type: 'Elixir', maxBytes: 8000 },
|
|
59
|
+
];
|
|
60
|
+
function detectProject(root) {
|
|
61
|
+
const info = { root, type: null, manifest: null, hasGit: false };
|
|
62
|
+
info.hasGit = fs.existsSync(path.join(root, '.git'));
|
|
63
|
+
for (const m of MANIFESTS) {
|
|
64
|
+
const p = path.join(root, m.file);
|
|
65
|
+
try {
|
|
66
|
+
if (fs.existsSync(p)) {
|
|
67
|
+
const stat = fs.statSync(p);
|
|
68
|
+
if (stat.size > 0 && stat.size <= m.maxBytes) {
|
|
69
|
+
info.manifest = { name: m.file, content: fs.readFileSync(p, 'utf8') };
|
|
70
|
+
info.type = m.type;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* ignore */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return info;
|
|
80
|
+
}
|
|
81
|
+
function buildProjectContext(root) {
|
|
82
|
+
const info = detectProject(root);
|
|
83
|
+
const lines = [];
|
|
84
|
+
lines.push(`Project root: ${root}`);
|
|
85
|
+
lines.push(`Project type: ${info.type || 'unknown'}`);
|
|
86
|
+
lines.push(`Git repository: ${info.hasGit ? 'yes' : 'no'}`);
|
|
87
|
+
if (info.manifest) {
|
|
88
|
+
lines.push(`\n--- ${info.manifest.name} ---\n${info.manifest.content}`);
|
|
89
|
+
}
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
function listRootEntries(root, limit = 25) {
|
|
93
|
+
try {
|
|
94
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
95
|
+
const IGNORE = new Set(['.git', 'node_modules', 'dist', 'build', '.cache', '.env', '.next', 'vendor']);
|
|
96
|
+
const lines = entries
|
|
97
|
+
.filter((e) => !IGNORE.has(e.name) && !e.name.startsWith('.'))
|
|
98
|
+
.sort((a, b) => (a.isDirectory() === b.isDirectory() ? a.name.localeCompare(b.name) : a.isDirectory() ? -1 : 1))
|
|
99
|
+
.slice(0, limit)
|
|
100
|
+
.map((e) => (e.isDirectory() ? `${e.name}/` : e.name));
|
|
101
|
+
return lines.length > 0 ? `Top-level entries:\n${lines.join('\n')}` : '(empty directory)';
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return '(cannot read directory)';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAgentLoop = runAgentLoop;
|
|
4
|
+
exports.compactMessages = compactMessages;
|
|
5
|
+
exports.estimateContextSize = estimateContextSize;
|
|
6
|
+
/**
|
|
7
|
+
* Agent loop — the core execution engine.
|
|
8
|
+
*
|
|
9
|
+
* USER PROMPT → context → LLM → tool call? → execute → result → LLM → ... → final
|
|
10
|
+
*
|
|
11
|
+
* The loop keeps going until the model produces a final answer (no tool
|
|
12
|
+
* calls) or the iteration limit is reached. Each tool result is fed back
|
|
13
|
+
* to the model so it can react, fix errors, and retry.
|
|
14
|
+
*/
|
|
15
|
+
const provider_1 = require("../providers/provider");
|
|
16
|
+
const context_1 = require("./context");
|
|
17
|
+
const prompts_1 = require("./prompts");
|
|
18
|
+
async function runAgentLoop(userRequest, opts, callbacks = {}) {
|
|
19
|
+
const { provider, tools, permissions, config, cwd, signal } = opts;
|
|
20
|
+
const maxIterations = config.maxIterations;
|
|
21
|
+
// Seed conversation with system + project context + task
|
|
22
|
+
const messages = [
|
|
23
|
+
{ role: 'system', content: prompts_1.SYSTEM_PROMPT },
|
|
24
|
+
{
|
|
25
|
+
role: 'user',
|
|
26
|
+
content: `${(0, context_1.buildProjectContext)(cwd)}\n\n${(0, context_1.listRootEntries)(cwd)}\n\n---\n\nTASK:\n${userRequest}`,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
let iterations = 0;
|
|
30
|
+
let toolCalls = 0;
|
|
31
|
+
let aborted = false;
|
|
32
|
+
let stopped = false;
|
|
33
|
+
let finalContent = '';
|
|
34
|
+
const checkAbort = () => {
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
aborted = true;
|
|
37
|
+
throw new Error('aborted');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
while (iterations < maxIterations) {
|
|
41
|
+
try {
|
|
42
|
+
checkAbort();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
aborted = true;
|
|
46
|
+
callbacks.onStatus?.('Stopped');
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
iterations++;
|
|
50
|
+
callbacks.onIteration?.(iterations, maxIterations);
|
|
51
|
+
callbacks.onStatus?.(iterations === 1 ? 'Thinking' : 'Thinking…');
|
|
52
|
+
let result;
|
|
53
|
+
try {
|
|
54
|
+
result = await (0, provider_1.withRetry)(() => provider.chat({ messages, model: config.model, tools: tools.definitions(), signal }), { retries: config.maxRetries, signal });
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (err.name === 'AbortError' || err.message === 'aborted') {
|
|
58
|
+
aborted = true;
|
|
59
|
+
callbacks.onStatus?.('Stopped');
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
if (err instanceof provider_1.ProviderError && err.status === 401) {
|
|
63
|
+
callbacks.onError?.(new Error('API authentication failed (401). Check your API key.'));
|
|
64
|
+
callbacks.onStatus?.('Auth error');
|
|
65
|
+
stopped = true;
|
|
66
|
+
finalContent = `⚠️ API authentication failed. Run \`vector /provider\` to configure your API key.`;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
if (err instanceof provider_1.ProviderError && err.status === 429) {
|
|
70
|
+
callbacks.onError?.(new Error('Rate limited (429) by the provider.'));
|
|
71
|
+
callbacks.onStatus?.('Rate limited');
|
|
72
|
+
stopped = true;
|
|
73
|
+
finalContent = '⚠️ Rate limited by the provider. Wait a moment and try again.';
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
callbacks.onError?.(err);
|
|
77
|
+
callbacks.onStatus?.('Error');
|
|
78
|
+
stopped = true;
|
|
79
|
+
finalContent = `⚠️ Error: ${err.message}`;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
const hasToolCalls = result.toolCalls.length > 0;
|
|
83
|
+
// Streaming-free path: collect the assistant message and execute tools.
|
|
84
|
+
messages.push({
|
|
85
|
+
role: 'assistant',
|
|
86
|
+
content: result.content,
|
|
87
|
+
toolCalls: result.toolCalls,
|
|
88
|
+
});
|
|
89
|
+
if (hasToolCalls) {
|
|
90
|
+
try {
|
|
91
|
+
for (const tc of result.toolCalls) {
|
|
92
|
+
checkAbort();
|
|
93
|
+
toolCalls++;
|
|
94
|
+
callbacks.onToolCall?.(tc.name, tc.arguments);
|
|
95
|
+
callbacks.onStatus?.(`Running ${tc.name}`);
|
|
96
|
+
const toolResult = await tools.execute(tc.name, tc.arguments, {
|
|
97
|
+
cwd,
|
|
98
|
+
permissions,
|
|
99
|
+
onActivity: callbacks.onActivity,
|
|
100
|
+
maxOutput: 30_000,
|
|
101
|
+
});
|
|
102
|
+
callbacks.onToolResult?.(tc.name, toolResult.output);
|
|
103
|
+
messages.push({
|
|
104
|
+
role: 'tool',
|
|
105
|
+
toolCallId: tc.id,
|
|
106
|
+
name: tc.name,
|
|
107
|
+
content: toolResult.output.slice(0, 40_000),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
aborted = true;
|
|
113
|
+
callbacks.onStatus?.('Stopped');
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// No tool calls → final answer
|
|
119
|
+
finalContent = result.content;
|
|
120
|
+
callbacks.onStatus?.('Completed');
|
|
121
|
+
callbacks.onFinal?.(result);
|
|
122
|
+
return { content: finalContent, iterations, toolCalls, stopped, aborted, messages };
|
|
123
|
+
}
|
|
124
|
+
if (iterations >= maxIterations) {
|
|
125
|
+
callbacks.onStatus?.('Iteration limit');
|
|
126
|
+
if (!finalContent) {
|
|
127
|
+
finalContent = `⚠️ Reached the maximum of ${maxIterations} iterations without completing the task. Run /settings to raise the limit or refine the request.`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { content: finalContent, iterations, toolCalls, stopped, aborted, messages };
|
|
131
|
+
}
|
|
132
|
+
/** Compact conversation history into a summary to reduce token usage. */
|
|
133
|
+
function compactMessages(messages, summarize) {
|
|
134
|
+
return summarize(messages).then((summary) => [
|
|
135
|
+
{ role: 'system', content: prompts_1.SYSTEM_PROMPT },
|
|
136
|
+
{
|
|
137
|
+
role: 'user',
|
|
138
|
+
content: `[Previous conversation summarized]\n${summary}`,
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
function estimateContextSize(messages) {
|
|
143
|
+
let total = 0;
|
|
144
|
+
for (const m of messages) {
|
|
145
|
+
total += m.content.length;
|
|
146
|
+
if (m.toolCalls) {
|
|
147
|
+
for (const tc of m.toolCalls)
|
|
148
|
+
total += tc.arguments.length;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return total;
|
|
152
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadMemory = loadMemory;
|
|
37
|
+
exports.saveNote = saveNote;
|
|
38
|
+
exports.getNote = getNote;
|
|
39
|
+
exports.allNotes = allNotes;
|
|
40
|
+
exports.projectKey = projectKey;
|
|
41
|
+
/**
|
|
42
|
+
* Memory — persistent key-value notes per project, stored in ~/.vector/memory/.
|
|
43
|
+
* The agent can save/load small facts across sessions (e.g. "tests run with npm test").
|
|
44
|
+
*/
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const paths_1 = require("../utils/paths");
|
|
48
|
+
function fileFor(projectKey) {
|
|
49
|
+
return path.join((0, paths_1.getMemoryDir)(), `${(0, paths_1.sanitizeName)(projectKey)}.json`);
|
|
50
|
+
}
|
|
51
|
+
function loadMemory(projectKey) {
|
|
52
|
+
try {
|
|
53
|
+
const p = fileFor(projectKey);
|
|
54
|
+
if (fs.existsSync(p)) {
|
|
55
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* ignore */
|
|
60
|
+
}
|
|
61
|
+
return { notes: {}, updatedAt: new Date().toISOString() };
|
|
62
|
+
}
|
|
63
|
+
function saveNote(projectKey, key, value) {
|
|
64
|
+
const mem = loadMemory(projectKey);
|
|
65
|
+
mem.notes[key] = value;
|
|
66
|
+
mem.updatedAt = new Date().toISOString();
|
|
67
|
+
fs.mkdirSync((0, paths_1.getMemoryDir)(), { recursive: true });
|
|
68
|
+
fs.writeFileSync(fileFor(projectKey), JSON.stringify(mem, null, 2));
|
|
69
|
+
}
|
|
70
|
+
function getNote(projectKey, key) {
|
|
71
|
+
return loadMemory(projectKey).notes[key];
|
|
72
|
+
}
|
|
73
|
+
function allNotes(projectKey) {
|
|
74
|
+
return loadMemory(projectKey).notes;
|
|
75
|
+
}
|
|
76
|
+
/** Derive a stable project key from the project path. */
|
|
77
|
+
function projectKey(cwd) {
|
|
78
|
+
const base = path.basename(cwd) || 'root';
|
|
79
|
+
return base + '-' + hashStr(cwd).slice(0, 8);
|
|
80
|
+
}
|
|
81
|
+
function hashStr(s) {
|
|
82
|
+
let h = 0;
|
|
83
|
+
for (let i = 0; i < s.length; i++) {
|
|
84
|
+
h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
|
|
85
|
+
}
|
|
86
|
+
return Math.abs(h).toString(36);
|
|
87
|
+
}
|