agen-vektor 0.3.8 → 0.3.9

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 CHANGED
@@ -12,33 +12,42 @@
12
12
  ```
13
13
 
14
14
  ```
15
- VectorHead ● Ready custom · deepseek-ai/DeepSeek-V4-Flash
16
- Project: ~/my-project Mode: ask
15
+ VectorHead ● Ready deepseek-ai/DeepSeek-V4-Flash
17
16
  ───────────────────────────────────────────────────────────────────────────────
18
- │ Perbaiki error authentication pada project ini
19
- VectorHead akan memeriksa file auth, memperbaiki, lalu menjalankan test.
17
+ [09:41]
18
+ Perbaiki error authentication pada project ini
20
19
 
21
- ───────────────────────────────────────────────────────────────────────────────
22
- ⚙ shell · $ npm test
23
- ✓ Tests passed
20
+ # Cek Auth ← kuning (heading H1–H6, Freebuff palette)
21
+ VectorHead akan memeriksa file auth, memperbaiki, lalu menjalankan test.
22
+ [09:42]
23
+ $ npm test
24
+ ... (2 more lines above)
25
+ 3 tests passed
24
26
  ╭─────────────────────────────────────────────────────────────────────────────╮
27
+ │ │
25
28
  │ ▍ Enter a coding task or / for commands │
29
+ │ │
26
30
  ╰─────────────────────────────────────────────────────────────────────────────╯
27
31
  ● Ready ● ask
28
32
  ```
29
33
 
30
- A Freebuff-style terminal UI: chat history with a bold-white assistant line,
31
- compact agent-activity strip (⚙ tool calls), and a rounded input box with a
32
- block cursor and placeholder — all rendered incrementally with no flicker.
34
+ A Freebuff-style terminal UI with the EXACT Freebuff dark palette (`#9EFC62`
35
+ green accents, markdown headings H1–H6 all yellow `#facc15`, inline code
36
+ BOLD orange `#FF8534` on a `#374151` chip, links blue `#3B82F6`, fenced
37
+ code `// lang` header `#5b647a` on a `#374151` slab, plain prose — quoted
38
+ from [CodebuffAI/freebuff](https://github.com/CodebuffAI/freebuff)
39
+ theme-system.ts + markdown-renderer.tsx), a compact agent-activity strip
40
+ (⚙ tool calls), and a rounded input box with a block cursor — all rendered
41
+ incrementally with no flicker.
33
42
 
34
43
  ## Features
35
44
 
36
- - **Interactive TUI** — chat, agent status, tool execution, diff viewer, input. Keyboard driven, mouse-aware, works on 80×24 terminals and survives resize.
45
+ - **Interactive TUI** — chat, agent status, tool execution, diff viewer, input. Keyboard driven, mouse-aware, works on 80×24 terminals and survives resize. Scroll the chat with PgUp/PgDn, the mouse wheel, or ↑/↓ while the input line is empty.
37
46
  - **Agent loop** — plan → inspect → tool call → result → review → fix → retry until done (with an iteration cap).
38
47
  - **Tools** — read/write/edit files, list directories, search files, run shell commands, git, fetch web pages.
39
48
  - **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.
40
49
  - **Multi-provider** — OpenAI, Anthropic, Gemini, OpenRouter, Ollama, and any OpenAI-compatible custom endpoint.
41
- - **Sessions & memory** — conversations persist to `~/.vector/sessions/`; `/continue` resumes them.
50
+ - **Sessions & memory** — conversations persist to `~/.vector/sessions/`; a relaunch in the same project resumes the conversation automatically (agent keeps the last replies in context), `/continue` resumes any session, and `Ctrl+L`/`/clear` starts fresh.
42
51
  - **Context management** — only relevant context is sent to the model, with compaction (`/compact`).
43
52
 
44
53
  ## Installation
@@ -100,6 +109,7 @@ vector
100
109
  | `vector "task"` | Start TUI and run the task immediately |
101
110
  | `vector --model MODEL` | Set the model |
102
111
  | `vector --provider P` | Set provider: `openai`, `anthropic`, `gemini`, `openrouter`, `ollama`, `custom` |
112
+ | `vector --theme NAME` | Set theme: built-in or `~/.vector/themes/<name>.json` |
103
113
  | `vector --continue` | Continue the most recent session |
104
114
  | `vector --session NAME` | Use/resume session `NAME` |
105
115
  | `vector --plan` | Show a plan before executing |
@@ -113,11 +123,14 @@ Type `/` inside VectorHead:
113
123
  | Command | Description |
114
124
  |---|---|
115
125
  | `/model` | Select a model |
126
+ | `/theme` | Select a theme (OpenCode-style) |
116
127
  | `/provider` | Select a provider |
128
+ | `/provider-remove` | Remove the stored provider key (and custom URL/name) so you can re-enter fresh values |
117
129
  | `/session` | Browse saved sessions |
118
130
  | `/settings` | Show configuration |
119
131
  | `/status` | Show agent status |
120
- | `/clear` | Clear the conversation |
132
+ | `/clear` | Clear the conversation (keeps the session) |
133
+ | `/new` | Start a fresh conversation — rotates to a brand-new session (the old one is kept on disk) |
121
134
  | `/continue` | Continue the current/last session |
122
135
  | `/diff` | Show diff viewer |
123
136
  | `/git` | Run `git status` |
@@ -138,7 +151,7 @@ export OPENROUTER_API_KEY=...
138
151
 
139
152
  Or inside VectorHead: `/provider` → pick provider → paste key (stored masked in `~/.vector/credentials.json`, permissions `0600`).
140
153
 
141
- Generic variables: `VECTOR_API_KEY`, `VECTOR_API_URL`, `VECTOR_MODEL`, `VECTOR_PROVIDER`, `VECTOR_HOME`.
154
+ Generic variables: `VECTOR_API_KEY`, `VECTOR_API_URL`, `VECTOR_MODEL`, `VECTOR_PROVIDER`, `VECTOR_THEME`, `VECTOR_HOME`, `VECTOR_AUTO_APPROVE` (=1 auto-approves ASK-level permission prompts, for automated runs/tests; dangerous actions still prompt).
142
155
 
143
156
  ### Custom API (OpenAI-compatible)
144
157
 
@@ -150,6 +163,128 @@ vector --provider custom --model deepseek-v3
150
163
 
151
164
  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.
152
165
 
166
+ ### Multiple custom providers (OpenCode-style)
167
+
168
+ Any number of OpenAI-compatible providers can be defined side by side in
169
+ `~/.vector/config.json` under `providers` — the same shape OpenCode uses in
170
+ its `provider` section (minus the `npm` field, since every custom endpoint
171
+ here is OpenAI-compatible):
172
+
173
+ ```jsonc
174
+ {
175
+ "provider": "bitdeer", // ACTIVE provider id
176
+ "model": "deepseek-ai/DeepSeek-V4-Flash",
177
+ "providers": {
178
+ "bitdeer": {
179
+ "name": "BitDeer",
180
+ "options": {
181
+ "baseURL": "https://api-inference.bitdeer.ai/v1",
182
+ "apiKey": "{env:BITDEER_API_KEY}" // or a plain key, or omit and store via /provider
183
+ },
184
+ "models": {
185
+ "deepseek-ai/DeepSeek-V4-Flash": { "name": "DeepSeek V4 Flash" }
186
+ }
187
+ },
188
+ "groq2": {
189
+ "name": "Groq 2",
190
+ "options": { "baseURL": "https://groq2.example.com/v1" }
191
+ },
192
+ "agentrouter": {
193
+ "name": "AgentRouter",
194
+ "model": "deepseek-v4-flash", // default model when this provider is active
195
+ "options": {
196
+ "baseURL": "https://agentrouter.org/v1",
197
+ // Gateways that filter by client (AgentRouter only accepts official
198
+ // AI clients) need a spoofed User-Agent on every request:
199
+ "headers": {
200
+ "User-Agent": "codex_cli_rs/0.101.0 (Mac OS 26.0.1; arm64) Apple_Terminal/464",
201
+ "Originator": "codex_cli_rs"
202
+ }
203
+ }
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ - `provider` is the active id — a built-in, the legacy `custom` slot, or any
210
+ key of `providers`. Keys are **per provider id**: store each under its own
211
+ id in `~/.vector/credentials.json` (0600) via `/provider`, inline them in
212
+ `options.apiKey`, or reference an env var with `"{env:NAME}"`.
213
+ - `options.headers` adds extra HTTP headers to every request of that provider
214
+ (e.g. a `User-Agent` required by a client-filtering gateway like
215
+ AgentRouter). Values may be plain strings or `"{env:NAME}"` templates;
216
+ headers referencing a missing env var are dropped.
217
+ - `model` is the **default model** for that provider: when the provider
218
+ becomes active (`/provider`, `--provider`, or `VECTOR_PROVIDER`), the active
219
+ model automatically follows it unless an explicit `--model` / `VECTOR_MODEL`
220
+ was given. Handy when each provider speaks its own model ids (e.g.
221
+ AgentRouter uses `deepseek-v4-flash` while BitDeer uses
222
+ `deepseek-ai/DeepSeek-V4-Flash`).
223
+ - `/provider` lists built-ins + every configured custom and offers
224
+ **➕ Add custom provider** (id → name → base URL → key). `/model` then
225
+ auto-discovers models from the endpoint's `/models` route and falls back
226
+ to the `models` map above.
227
+ - The old single-custom setup (`provider: "custom"` + `apiUrl`/`customName`
228
+ + key under `custom`) keeps working unchanged.
229
+
230
+ Switch at runtime with `/provider`, or per-run with
231
+ `VECTOR_PROVIDER=bitdeer vector …` / `--provider bitdeer`.
232
+
233
+ ## Themes
234
+
235
+ VectorHead adopts [OpenCode's theme system](https://opencode.ai/docs/themes/):
236
+ select a built-in theme, or drop your own JSON files into
237
+ `~/.vector/themes/` (same files OpenCode uses work verbatim).
238
+
239
+ ```bash
240
+ vector --theme tokyonight # one-off
241
+ # or persist it:
242
+ echo '{ "theme": "nord" }' >> ~/.vector/config.json # (merge manually)
243
+ # or inside the TUI: /theme
244
+ # or env: export VECTOR_THEME=matrix
245
+ ```
246
+
247
+ Built-ins: `vectorhead` (default, the Exact Freebuff dark palette),
248
+ `nord`, `tokyonight`, `gruvbox`, `catppuccin`, `one-dark`, `matrix`.
249
+
250
+ ### Custom themes
251
+
252
+ A theme is a JSON file with an optional `defs` block of reusable colors
253
+ plus a semantic `theme` block. Color values may be hex (`#88C0D0`), ANSI
254
+ palette indexes (`3` for 0–255), references (`"nord8"` or `"primary"`),
255
+ dark/light variants (`{ "dark": "#000", "light": "#fff" }`), or
256
+ `"none"` (terminal default).
257
+
258
+ ```json
259
+ {
260
+ "defs": { "brand": "#9EFC62" },
261
+ "theme": {
262
+ "primary": "brand",
263
+ "text": "#f1f5f9",
264
+ "textMuted": "#acb3bf",
265
+ "background": "#0b0f14",
266
+ "backgroundPanel": "#141a21",
267
+ "border": "#2b3542",
268
+ "error": "#ff6b6b",
269
+ "warning": "#ffa500",
270
+ "success": "#9EFC62"
271
+ }
272
+ }
273
+ ```
274
+
275
+ Save it as `~/.vector/themes/my-theme.json`, then `/theme` (or
276
+ `vector --theme my-theme`) to use it. Semantic keys adopted from OpenCode
277
+ and their effect here: `primary` (brand accent), `accent`/`markdownHeading`
278
+ (headings, prompt), `text`, `textMuted`, `textFaint`, `markdownCode`,
279
+ `border`/`borderActive`, `error`/`warning`/`success`/`info`, `diffAdded`/
280
+ `diffRemoved`/`diffContext`, `backgroundPanel`/`backgroundElement`.
281
+ Unknown keys and unresolvable colors are ignored, so partial themes keep
282
+ the default palette for anything you don't override.
283
+
284
+ > Hex colors are emitted as truecolor (24-bit) escapes — terminals need
285
+ > `COLORTERM=truecolor` for full fidelity (most modern terminals). ANSI
286
+ > palette indexes and `vectorhead` work everywhere.
287
+
153
288
  ## Security model
154
289
 
155
290
  Every shell command is classified:
@@ -8,9 +8,14 @@ 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 extras_1 = require("../tools/extras");
12
+ const apply_patch_1 = require("../tools/apply-patch");
11
13
  const permissions_1 = require("../security/permissions");
12
14
  const config_1 = require("../config/config");
13
15
  const loop_1 = require("./loop");
16
+ const rules_1 = require("./rules");
17
+ const skills_1 = require("./skills");
18
+ const skills_tool_1 = require("../tools/skills-tool");
14
19
  class Agent {
15
20
  config;
16
21
  provider;
@@ -20,6 +25,11 @@ class Agent {
20
25
  constructor(opts) {
21
26
  this.config = (0, config_1.effectiveConfig)(opts.config);
22
27
  this.cwd = opts.cwd || process.cwd();
28
+ // Seed Hermes-style SOUL.md/MEMORY.md/USER.md/SKILL.md on first run
29
+ // (never overwrites existing user files — see src/agent/rules.ts).
30
+ (0, rules_1.ensureRuleFiles)();
31
+ // Seed the bundled skill catalog into ~/.vector/skills/ (never overwrites).
32
+ (0, skills_1.seedBundledSkills)();
23
33
  this.provider = (0, factory_1.createProvider)(this.config);
24
34
  this.permissions = new permissions_1.PermissionManager({
25
35
  mode: this.config.permissionMode,
@@ -31,7 +41,14 @@ class Agent {
31
41
  ...(0, search_1.createSearchTools)(),
32
42
  (0, shell_1.createShellTool)(),
33
43
  (0, git_1.createGitTool)(),
34
- (0, web_1.createWebTool)(),
44
+ ...(0, web_1.createWebTools)(),
45
+ // Freebuff-style extras: glob, read_subtree, task_completed,
46
+ // suggest_followups, render_ui.
47
+ ...(0, extras_1.createExtrasTools)(),
48
+ // Unified-diff editing (Freebuff/Codebuff apply_patch format).
49
+ (0, apply_patch_1.createApplyPatchTool)(),
50
+ // Hermes-style progressive-disclosure skills (list_skills / read_skill).
51
+ ...(0, skills_tool_1.createSkillTools)(),
35
52
  ]);
36
53
  }
37
54
  async run(request, signal) {
@@ -79,6 +96,7 @@ class Agent {
79
96
  this.callbacks = {
80
97
  onStatus: cb.onStatus,
81
98
  onDelta: cb.onDelta,
99
+ onThinking: cb.onThinking,
82
100
  onToolCall: cb.onToolCall,
83
101
  onToolResult: cb.onToolResult,
84
102
  onIteration: cb.onIteration,
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * Light small-talk detection — casual greetings, thanks, and questions about
4
+ * the agent itself do NOT need project context. Treating these as "light"
5
+ * saves tokens and, more importantly, stops the agent from inspecting or
6
+ * listing the whole project just because the user said "hallo" (it should
7
+ * simply answer back instead of reading the directory).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.isLightRequest = isLightRequest;
11
+ const LIGHT_PHRASES = new Set([
12
+ // greetings (id + en)
13
+ 'hi', 'hii', 'hai', 'haii', 'hei', 'hey', 'heyy', 'hello', 'hallo', 'helo',
14
+ 'halo', 'halow', 'hola', 'yo', 'yoo', 'yow', 'sup', 'wassup', 'whats up',
15
+ "what's up", 'hiya', 'howdy', 'selamat pagi', 'selamat siang', 'selamat sore',
16
+ 'selamat malam', 'good morning', 'good afternoon', 'good evening',
17
+ 'apa kabar', 'apakabar', 'kabar', 'kabar baik', 'how are you',
18
+ 'how are you doing', 'hows it going', "how's it going",
19
+ // thanks / short acknowledgements
20
+ 'thanks', 'thank you', 'thankyou', 'thx', 'ty', 'makasih', 'makasi',
21
+ 'mkasih', 'mksh', 'terima kasih', 'trims', 'sip', 'siap', 'mantap',
22
+ 'bagus', 'nice', 'oke', 'ok', 'okay', 'okey', 'okee',
23
+ // questions about the agent itself (no project needed)
24
+ 'kamu siapa', 'siapa kamu', 'kamu sapa', 'who are you',
25
+ 'kamu model apa', 'model apa kamu', 'model kamu apa', 'kamu pakai model apa',
26
+ 'kamu provider apa', 'provider apa kamu', 'provider kamu apa', 'kamu pakai provider apa',
27
+ 'kamu bisa apa', 'bisa apa', 'apa yang bisa kamu lakukan', 'apa yang kamu bisa',
28
+ 'what can you do', 'can you help me', 'bisa bantu', 'boleh bantu',
29
+ 'mau tanya', 'boleh tanya',
30
+ // skills / capabilities questions (agent, NOT the repository)
31
+ 'apa skill mu', 'skill mu apa', 'skill kamu apa', 'apa skill kamu',
32
+ 'apa aja skill mu', 'apa aja skill kamu', 'apa saja skill mu', 'apa saja skill kamu',
33
+ 'skill apa aja', 'skill apa saja', 'skills apa', 'list skill', 'list skills',
34
+ 'daftar skill', 'daftar skills', 'apa skills mu', 'skills kamu',
35
+ 'keahlian mu apa', 'apa keahlian mu', 'apa aja keahlian mu', 'apa keahlian kamu',
36
+ 'keahlian kamu apa', 'kemampuan mu apa', 'apa kemampuan mu', 'apa aja kemampuan mu',
37
+ 'apa kemampuan kamu', 'kemampuan kamu apa', 'bisa apa aja', 'bisa apa saja',
38
+ 'bisa ngapain', 'bisa ngapain aja', 'ngapain aja', 'kamu ngapain aja',
39
+ 'bisa bantu apa', 'bisa membantu apa', 'bisa bantu apa saja',
40
+ 'what skills do you have', 'list your skills', 'what are your skills',
41
+ 'what are your capabilities', 'what are your abilities', 'your capabilities',
42
+ // what project are we on (identity of the workspace — brief answer, no
43
+ // full-project inspection / tool wall)
44
+ 'apa project kita', 'project kita apa', 'project apa ini', 'apa project ini',
45
+ 'ini project apa', 'project ini apa', 'apa proyek kita', 'proyek kita apa',
46
+ 'proyek apa ini', 'apa proyek ini', 'ini proyek apa', 'proyek ini apa',
47
+ 'project kamu apa', 'kamu project apa', 'apa project kamu', 'project apa ya ini',
48
+ ]);
49
+ const MAX_LEN = 80;
50
+ /** True for short casual messages that deserve a direct answer, no tools. */
51
+ function isLightRequest(text) {
52
+ const t = text.trim();
53
+ if (!t || t.length > MAX_LEN)
54
+ return false;
55
+ // Normalize: lowercase, drop trailing punctuation/emoji/symbols.
56
+ const norm = t
57
+ .toLowerCase()
58
+ .replace(/[\s!.,;:?]+$/, '')
59
+ .replace(/[^\x00-\x7F\s]/g, '')
60
+ .replace(/[\s!.,;:?]+$/, '')
61
+ .trim();
62
+ if (norm.length === 0)
63
+ return true; // emoji-only / punctuation-only message
64
+ return LIGHT_PHRASES.has(norm);
65
+ }
@@ -14,7 +14,10 @@ exports.estimateContextSize = estimateContextSize;
14
14
  */
15
15
  const provider_1 = require("../providers/provider");
16
16
  const context_1 = require("./context");
17
+ const light_1 = require("./light");
17
18
  const prompts_1 = require("./prompts");
19
+ const rules_1 = require("./rules");
20
+ const skills_1 = require("./skills");
18
21
  async function runAgentLoop(userRequest, opts, callbacks = {}) {
19
22
  const { provider, tools, permissions, config, cwd, signal } = opts;
20
23
  const maxIterations = config.maxIterations;
@@ -24,11 +27,36 @@ async function runAgentLoop(userRequest, opts, callbacks = {}) {
24
27
  // the folder listing on every turn makes the agent re-explain/echo it.
25
28
  const history = (opts.history ?? []).filter((m) => m.role !== 'system');
26
29
  const isContinuation = history.length > 0;
27
- const context = isContinuation
30
+ // Casual small talk ("hallo", "hi", "apa kabar", "kamu siapa", …) gets a
31
+ // plain, direct answer: no project context, no folder listing, no skills
32
+ // catalog. The model must not read the whole directory just to greet back.
33
+ const isLight = (0, light_1.isLightRequest)(userRequest);
34
+ const context = isContinuation || isLight
28
35
  ? ''
29
36
  : `${(0, context_1.buildProjectContext)(cwd)}\n\n${(0, context_1.listRootEntries)(cwd)}\n\n---\n\n`;
37
+ // Hermes-style agent rules: SOUL.md (identity) first, then AGENTS.md
38
+ // project rules, then bounded MEMORY.md / USER.md / SKILL.md. Empty
39
+ // sections are skipped, so nothing is added until the user/agent fills it.
40
+ const rulesText = (0, rules_1.rulesToPrompt)((0, rules_1.loadRules)(cwd));
41
+ // Level 0 of the skills catalog (names + one-line descriptions). Cheap
42
+ // enough to always include; full instructions load on demand via the
43
+ // skill/read_skill tools. Scans ALL roots — including the project's
44
+ // .agents/skills (`npx skills add` layout) — Freebuff parity.
45
+ const skillsCatalog = (0, skills_1.skillsCatalogText)((0, skills_1.listSkills)(undefined, cwd));
30
46
  const messages = [
31
- { role: 'system', content: prompts_1.SYSTEM_PROMPT },
47
+ // Slot #1: identity (SOUL.md or the built-in VectorHead persona).
48
+ { role: 'system', content: rulesText || prompts_1.SYSTEM_PROMPT },
49
+ // Core behavior rules (only when SOUL.md is active we still need the
50
+ // operation rules; when rulesText is empty SYSTEM_PROMPT already covers it).
51
+ ...(rulesText ? [{ role: 'system', content: prompts_1.SYSTEM_PROMPT }] : []),
52
+ // Identity: the model must know its own name/provider to answer
53
+ // "kamu model apa / provider mana" truthfully instead of guessing.
54
+ { role: 'system', content: (0, prompts_1.identityPrompt)(config.model, config.provider) },
55
+ // Skills catalog (Level 0) injected on the first turn; on continuation
56
+ // the model already saw it and can still list_skills at any time.
57
+ ...(!isContinuation && !isLight && skillsCatalog
58
+ ? [{ role: 'system', content: skillsCatalog }]
59
+ : []),
32
60
  ...history,
33
61
  { role: 'user', content: `${context}TASK:\n${userRequest}` },
34
62
  ];
@@ -37,12 +65,81 @@ async function runAgentLoop(userRequest, opts, callbacks = {}) {
37
65
  let aborted = false;
38
66
  let stopped = false;
39
67
  let finalContent = '';
68
+ let lastReasoning = '';
40
69
  const checkAbort = () => {
41
70
  if (signal?.aborted) {
42
71
  aborted = true;
43
72
  throw new Error('aborted');
44
73
  }
45
74
  };
75
+ /**
76
+ * One model call. Streams when the provider supports it so tokens appear
77
+ * live in the TUI (Freebuff-style) instead of a silent freeze while the
78
+ * model thinks (BitDeer cold starts can be 30–120s). Falls back to chat
79
+ * for providers that cannot stream.
80
+ */
81
+ async function doCall() {
82
+ const params = { messages, model: config.model, tools: tools.definitions(), signal };
83
+ if (!provider.capabilities().streaming) {
84
+ const r = await provider.chat(params);
85
+ // Non-streaming providers deliver reasoning only in the result —
86
+ // normalize it into onThinking (once) so the TUI behaves the same.
87
+ if (r.reasoning) {
88
+ lastReasoning = r.reasoning;
89
+ callbacks.onThinking?.(r.reasoning);
90
+ }
91
+ return r;
92
+ }
93
+ let content = '';
94
+ const toolStream = [];
95
+ let final = null;
96
+ for await (const ev of provider.stream(params)) {
97
+ if (ev.type === 'delta' && ev.delta) {
98
+ content += ev.delta;
99
+ callbacks.onDelta?.(ev.delta);
100
+ }
101
+ else if (ev.type === 'delta' && ev.reasoning) {
102
+ // Model thinking (reasoning_content) — surfaced live to the TUI's
103
+ // thinking card, kept OUT of the visible answer text.
104
+ lastReasoning += ev.reasoning;
105
+ callbacks.onThinking?.(ev.reasoning);
106
+ }
107
+ else if (ev.type === 'tool_call' && ev.toolCall) {
108
+ // The same tool call id can be yielded repeatedly (partial chunks);
109
+ // keep the latest complete copy.
110
+ const call = ev.toolCall;
111
+ const idx = toolStream.findIndex((t) => t.id === call.id);
112
+ if (idx >= 0)
113
+ toolStream[idx] = call;
114
+ else
115
+ toolStream.push(call);
116
+ }
117
+ else if (ev.type === 'error') {
118
+ throw new provider_1.ProviderError(ev.error || 'Streaming error from provider.', 0, false);
119
+ }
120
+ else if (ev.type === 'done' && ev.result) {
121
+ final = ev.result;
122
+ }
123
+ }
124
+ if (final) {
125
+ if (final.reasoning && !lastReasoning) {
126
+ // Reasoning appeared ONLY in the final result (no deltas were
127
+ // streamed): emit once so the TUI still gets the thinking card.
128
+ lastReasoning = final.reasoning;
129
+ callbacks.onThinking?.(final.reasoning);
130
+ }
131
+ if (lastReasoning && !final.reasoning)
132
+ final.reasoning = lastReasoning;
133
+ lastReasoning = '';
134
+ return final;
135
+ }
136
+ return {
137
+ content,
138
+ reasoning: lastReasoning || undefined,
139
+ toolCalls: toolStream,
140
+ stopReason: toolStream.length > 0 ? 'tool_calls' : 'stop',
141
+ };
142
+ }
46
143
  while (iterations < maxIterations) {
47
144
  try {
48
145
  checkAbort();
@@ -57,7 +154,7 @@ async function runAgentLoop(userRequest, opts, callbacks = {}) {
57
154
  callbacks.onStatus?.(iterations === 1 ? 'Thinking' : 'Thinking…');
58
155
  let result;
59
156
  try {
60
- result = await (0, provider_1.withRetry)(() => provider.chat({ messages, model: config.model, tools: tools.definitions(), signal }), { retries: config.maxRetries, signal });
157
+ result = await (0, provider_1.withRetry)(doCall, { retries: config.maxRetries, signal });
61
158
  }
62
159
  catch (err) {
63
160
  if (err.name === 'AbortError' || err.message === 'aborted') {
@@ -105,7 +202,7 @@ async function runAgentLoop(userRequest, opts, callbacks = {}) {
105
202
  onActivity: callbacks.onActivity,
106
203
  maxOutput: 30_000,
107
204
  });
108
- callbacks.onToolResult?.(tc.name, toolResult.output);
205
+ callbacks.onToolResult?.(tc.name, toolResult.output, toolResult.data);
109
206
  messages.push({
110
207
  role: 'tool',
111
208
  toolCallId: tc.id,
@@ -124,7 +221,8 @@ async function runAgentLoop(userRequest, opts, callbacks = {}) {
124
221
  // No tool calls → final answer
125
222
  finalContent = result.content;
126
223
  callbacks.onStatus?.('Completed');
127
- callbacks.onFinal?.(result);
224
+ callbacks.onFinal?.({ ...result, reasoning: lastReasoning || result.reasoning });
225
+ lastReasoning = '';
128
226
  return { content: finalContent, iterations, toolCalls, stopped, aborted, messages };
129
227
  }
130
228
  if (iterations >= maxIterations) {
@@ -136,9 +234,12 @@ async function runAgentLoop(userRequest, opts, callbacks = {}) {
136
234
  return { content: finalContent, iterations, toolCalls, stopped, aborted, messages };
137
235
  }
138
236
  /** Compact conversation history into a summary to reduce token usage. */
139
- function compactMessages(messages, summarize) {
237
+ function compactMessages(messages, summarize, config) {
140
238
  return summarize(messages).then((summary) => [
141
239
  { role: 'system', content: prompts_1.SYSTEM_PROMPT },
240
+ ...(config
241
+ ? [{ role: 'system', content: (0, prompts_1.identityPrompt)(config.model, config.provider) }]
242
+ : []),
142
243
  {
143
244
  role: 'user',
144
245
  content: `[Previous conversation summarized]\n${summary}`,
@@ -2,8 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generatePlan = generatePlan;
4
4
  const context_1 = require("./context");
5
+ const light_1 = require("./light");
5
6
  const PLAN_PROMPT = `You are a planning engine. Given the user's task and the project context, produce a concise numbered plan of the steps the agent should take. Output ONLY the numbered steps, one per line. No preamble, no markdown fences.`;
6
7
  async function generatePlan(provider, userRequest, cwd, model, signal) {
8
+ // Casual small talk: answer directly — don't spend an LLM call (or the
9
+ // project context) on a greeting.
10
+ if ((0, light_1.isLightRequest)(userRequest)) {
11
+ return { steps: [userRequest], raw: '' };
12
+ }
7
13
  const projectCtx = (0, context_1.buildProjectContext)(cwd);
8
14
  const entries = (0, context_1.listRootEntries)(cwd);
9
15
  const messages = [
@@ -4,6 +4,7 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SYSTEM_PROMPT = void 0;
7
+ exports.identityPrompt = identityPrompt;
7
8
  exports.taskPrompt = taskPrompt;
8
9
  exports.continuationPrompt = continuationPrompt;
9
10
  exports.SYSTEM_PROMPT = `You are VectorHead, an AI coding agent operating inside a terminal.
@@ -24,7 +25,44 @@ Rules:
24
25
  6. Never print API keys, tokens, or secrets. If you find a secret, say so without revealing it.
25
26
  7. When you need more information than you have, use tools to get it instead of asking the user.
26
27
  8. Answer in the same language the user used.
27
- 9. Keep responses concise but complete: state what you did, why, and how it was verified.`;
28
+ 9. Keep responses concise but complete: state what you did, why, and how it was verified.
29
+ 10. Reusable procedures are saved as skills. Run list_skills to see what's installed
30
+ (names + descriptions), then read_skill with the skill name to load the full
31
+ instructions the moment you actually follow one. Use a matching skill when a
32
+ task clearly fits one instead of improvising from scratch. NEVER print the
33
+ installed-skills list or list_skills output back into your reply — it is only
34
+ an internal index; use it to pick and load a skill, nothing more.
35
+ 11. When the user only greets you or asks about yourself (e.g. "hallo", "hi",
36
+ "apa kabar", "terima kasih", "kamu siapa?", "kamu model apa", "kamu bisa
37
+ apa?"), reply briefly and directly. Do NOT inspect the project, list files,
38
+ or call any tool for such messages — a greeting is not a task.
39
+ 12. Whenever you receive a task that needs work (inspect, search, edit, run
40
+ anything), FIRST open your reply with one very short acknowledgment line
41
+ about what you are about to do — e.g. "Oke, saya akan cari dulu.",
42
+ "Baik, saya periksa dulu.", "Siap, saya kerjakan.", "Ok, let me look
43
+ first." — and only then start using tools. This way the user sees you
44
+ respond immediately even before the first tool runs. Keep it to one line;
45
+ do not repeat or pad it. For pure greetings/identity questions (rule 11)
46
+ skip the acknowledgment and answer directly.
47
+ 13. Live task checklist (write_todos tool): the user watches a live ☐/☑
48
+ checklist while you work, so show it whenever a request needs 2+ tool
49
+ calls. Hard rule: after your one-line acknowledgment, your FIRST tool
50
+ call MUST be write_todos with the FULL step list as
51
+ { task, completed: false } items; then after each step finishes, call
52
+ write_todos again with the full updated list (finished steps
53
+ completed: true). Example: for "buat file lalu baca isinya" first call
54
+ write_todos with ["Buat file", "Baca isinya"] before calling write_file.
55
+ The ONLY exceptions — greetings, plain questions, small talk, and
56
+ requests needing just ONE tool call: answer those directly with no
57
+ write_todos.`;
58
+ /**
59
+ * Identity line injected into the system prompt so the model can answer
60
+ * "which model/provider are you?" truthfully (it has no other way to know).
61
+ */
62
+ function identityPrompt(model, provider) {
63
+ return `You are currently running as the model "${model}" via the "${provider}" provider (OpenAI-compatible API unless stated otherwise).
64
+ If the user asks which model you are or which provider backs you, answer with exactly this — e.g. "Saya dijalankan oleh model deepseek-ai/DeepSeek-V4-Flash lewat provider custom". Do not invent a different model name.`;
65
+ }
28
66
  function taskPrompt(userRequest, cwd) {
29
67
  return `Working directory: ${cwd}
30
68