zyn-ai 1.3.2 → 1.3.3

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
@@ -6,55 +6,42 @@
6
6
 
7
7
  <p align="center">
8
8
  <img src="https://img.shields.io/npm/v/zyn-ai?label=npm&color=%23CB3837" alt="NPM Version"/>
9
-
10
9
  <img src="https://img.shields.io/github/v/release/SoyMaycol/Zyn?include_prereleases&sort=semver" alt="Latest Release"/>
11
-
12
10
  <img src="https://img.shields.io/npm/dt/zyn-ai" alt="Downloads"/>
13
-
14
- <img src="https://img.shields.io/github/forks/SoyMaycol/Zyn" alt="Forks"/>
15
11
  </p>
16
12
 
17
13
  <p align="center">
18
- <b>Local terminal and web agent for multi-provider AI workflows.</b>
14
+ <b>Local AI agent for terminal, TUI, and web.</b>
19
15
  </p>
20
16
 
21
17
  <p align="center">
22
18
  <a href="https://github.com/SoyMaycol/Zyn">Official repository</a>
23
19
  </p>
24
20
 
25
- ## What Zyn is
21
+ ---
26
22
 
27
- Zyn is a local agent for terminal and web workflows. It supports multiple AI providers, persistent sessions, tools, memory, and collaborative multi-model reasoning. The project is designed to be direct, extensible, and practical for real development work.
23
+ ## What is Zyn
28
24
 
29
- ## Features
25
+ Zyn is a local AI agent designed for terminal and web usage. It supports persistent sessions, system tools, multiple AI providers, session exports, and configurable models.
30
26
 
31
- - English by default, with language switching from commands.
32
- - Interactive terminal mode and classic CLI mode.
33
- - Web mode for collaborative usage.
34
- - Multiple providers and custom models.
35
- - Modular skills system.
36
- - Persistent sessions, history, and transcript export.
37
- - Extensible architecture for tools and providers.
27
+ ---
38
28
 
39
29
  ## Requirements
40
30
 
41
- - Node.js 18 or newer
31
+ - Node.js 18+
42
32
  - npm
43
33
  - Internet connection for remote providers
44
34
  - Optional: Ollama for local models
45
35
 
46
- ## Install
47
-
48
- ### Global install
36
+ ---
49
37
 
50
- ```bash
51
- npm install zyn-ai -g
52
- ```
38
+ ## Installation
53
39
 
54
- Then run:
40
+ ### Global install
55
41
 
56
42
  ```bash
57
- Zyn
43
+ npm install -g zyn-ai
44
+ zyn
58
45
  ```
59
46
 
60
47
  ### Local development
@@ -63,79 +50,116 @@ Zyn
63
50
  git clone https://github.com/SoyMaycol/Zyn.git
64
51
  cd Zyn
65
52
  npm install
53
+ npm start
66
54
  ```
67
55
 
68
- ## Usage
56
+ ---
69
57
 
70
- ### Interactive terminal
58
+ ## Usage
71
59
 
72
60
  ```bash
73
- Zyn
61
+ zyn
62
+ zyn "Explain this project"
63
+ zyn --new
64
+ zyn --resume ID
74
65
  ```
75
66
 
76
- ### Direct prompt
67
+ ---
77
68
 
78
- ```bash
79
- Zyn "Explain this project"
80
- ```
69
+ ## Web mode
81
70
 
82
- ### Open the web version from the CLI
71
+ Inside Zyn:
83
72
 
84
- ```bash
73
+ ```text
85
74
  /web
75
+ /web 0.0.0.0:3000
86
76
  ```
87
77
 
88
78
  Or directly:
89
79
 
90
80
  ```bash
91
- node src/web/server.js
81
+ npm run web
92
82
  ```
93
83
 
94
- ### Help
84
+ ---
95
85
 
96
- Inside Zyn:
86
+ ## Language
97
87
 
98
- ```bash
99
- /help
100
- ```
88
+ Supported languages:
101
89
 
102
- ## Language selection
90
+ - `en`
91
+ - `es`
103
92
 
104
- Use the command below inside Zyn:
93
+ Commands:
105
94
 
106
95
  ```text
96
+ /lang
107
97
  /lang en
108
98
  /lang es
109
99
  ```
110
100
 
111
- English is the default.
101
+ ---
102
+
103
+ ## Main Commands
112
104
 
113
- ## Main commands
105
+ ### Sessions
114
106
 
115
- - `/help` shows the full command list.
116
- - `/lang en` or `/lang es` changes the interface language.
117
- - `/model` views or changes the active model.
118
- - `/models` lists available models.
119
- - `/providers` lists detected providers.
120
- - `/skills` shows loaded skills.
121
- - `/tools` shows available tools.
122
- - `/web` opens the web version.
123
- - `/concuerdo` enables the group-model mode.
124
- - `/stop` stops the current agent turn.
125
- - `/reset` resets the current context.
107
+ | Command | Description |
108
+ |---|---|
109
+ | `/help` | Show available commands |
110
+ | `/status` | Show current status |
111
+ | `/history` | Show recent actions |
112
+ | `/memory` | Show memory summary |
113
+ | `/sessions` | List saved sessions |
114
+ | `/new` | Create a new session |
115
+ | `/resume <ID>` | Resume a session |
116
+ | `/title <text>` | Rename session |
126
117
 
127
- ## Providers
118
+ ### Configuration
128
119
 
129
- Zyn includes support for:
120
+ | Command | Description |
121
+ |---|---|
122
+ | `/model` | Show or change model |
123
+ | `/models` | List models |
124
+ | `/providers` | List providers |
125
+ | `/lang <en\|es>` | Change language |
126
+ | `/config show` | Show config |
127
+ | `/auto on\|off` | Toggle auto approval |
128
+ | `/cwd <path>` | Change working directory |
130
129
 
131
- - Qwen
132
- - Zen
133
- - Ollama
134
- - OpenAI-compatible providers
130
+ ### Tools
135
131
 
136
- Models can be extended from `data/models.json` or from the internal configuration.
132
+ | Command | Description |
133
+ |---|---|
134
+ | `/tools` | List tools |
135
+ | `/skills` | List skills |
136
+ | `/cwd` | Show working directory |
137
137
 
138
- ### Example model config
138
+ ### Web & Export
139
+
140
+ | Command | Description |
141
+ |---|---|
142
+ | `/web` | Start web interface |
143
+ | `/transcript` | Show transcript |
144
+ | `/export` | Export session |
145
+
146
+ ### Control
147
+
148
+ | Command | Description |
149
+ |---|---|
150
+ | `/stop` | Stop current task |
151
+ | `/reset` | Reset session |
152
+ | `/exit` | Exit Zyn |
153
+
154
+ In the TUI, press `ESC` twice to stop the current task.
155
+
156
+ ---
157
+
158
+ ## Models
159
+
160
+ Custom models can be added using `data/models.json`.
161
+
162
+ Example:
139
163
 
140
164
  ```json
141
165
  {
@@ -154,34 +178,3 @@ Models can be extended from `data/models.json` or from the internal configuratio
154
178
  }
155
179
  }
156
180
  ```
157
-
158
- ## Web collaboration
159
-
160
- The web version is designed for cross-review between multiple models. That helps one model correct or contrast what another generated, which is useful when consistency matters.
161
-
162
- ## Skills
163
-
164
- The skills system breaks the agent behavior into focused pieces:
165
-
166
- - `core`
167
- - `reasoning`
168
- - `methodology`
169
- - `thinking`
170
- - `tools`
171
- - `web-agent`
172
- - `debugging`
173
- - `frontend_design`
174
- - `code-style`
175
- - `domains`
176
- - `testing`
177
-
178
- Each skill can evolve without breaking the rest of the project.
179
-
180
- ## License
181
-
182
- This project includes an attribution-friendly license. Keep the credits, the repository link, and the license notices when redistributing or deriving the project.
183
-
184
- ## Credits
185
-
186
- - Project: [SoyMaycol/Zyn](https://github.com/SoyMaycol/Zyn)
187
- - Base authorship: Maycol and Ado
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyn-ai",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Production-ready AI agent for CLI and web with real tool execution and automation",
5
5
  "author": "Maycol",
6
6
  "keywords": [
@@ -13,35 +13,39 @@ const { resolveInputPath } = require('../utils/pathUtils');
13
13
  const { printTools } = require('../tools');
14
14
 
15
15
  const SLASH_COMMANDS = [
16
- { name: 'help', desc: 'full help' },
17
- { name: 'status', desc: 'current status' },
18
- { name: 'history', desc: 'recent actions' },
19
- { name: 'memory', desc: 'memory summary' },
20
- { name: 'session', desc: 'current session' },
21
- { name: 'sessions', desc: 'list sessions' },
22
- { name: 'new', desc: 'new session' },
23
- { name: 'resume', desc: 'resume session' },
24
- { name: 'title', desc: 'rename session' },
25
- { name: 'model', desc: 'view/change model' },
26
- { name: 'models', desc: 'list models' },
27
- { name: 'providers', desc: 'list providers' },
28
- { name: 'git', desc: 'configure git credentials' },
29
- { name: 'persona', desc: 'set response tone/personality' },
30
- { name: 'lang', desc: 'change language' },
31
- { name: 'language', desc: 'change language' },
32
- { name: 'auto', desc: 'auto-approval' },
33
- { name: 'concuerdo', desc: 'group model mode' },
34
- { name: 'tools', desc: 'tools' },
35
- { name: 'skills', desc: 'agent skills' },
36
- { name: 'config', desc: 'view/change session settings' },
37
- { name: 'web', desc: 'open web version' },
38
- { name: 'stop', desc: 'stop agent' },
39
- { name: 'abort', desc: 'stop agent' },
40
- { name: 'reset', desc: 'reset context' },
41
- { name: 'cwd', desc: 'working directory' },
42
- { name: 'transcript', desc: 'view transcript' },
43
- { name: 'export', desc: 'export to txt' },
44
- { name: 'exit', desc: 'exit' },
16
+ { name: 'help', desc: 'full help', descEs: 'ayuda completa' },
17
+ { name: 'status', desc: 'current status', descEs: 'estado actual' },
18
+ { name: 'history', desc: 'recent actions', descEs: 'acciones recientes' },
19
+ { name: 'memory', desc: 'memory summary', descEs: 'resumen de memoria' },
20
+ { name: 'summary', desc: 'memory summary', descEs: 'resumen de memoria' },
21
+ { name: 'session', desc: 'current session', descEs: 'sesión actual' },
22
+ { name: 'sessions', desc: 'list sessions', descEs: 'listar sesiones' },
23
+ { name: 'new', desc: 'new session', descEs: 'nueva sesión' },
24
+ { name: 'resume', desc: 'resume session', descEs: 'reanudar sesión' },
25
+ { name: 'title', desc: 'rename session', descEs: 'renombrar sesión' },
26
+ { name: 'rename', desc: 'rename session', descEs: 'renombrar sesión' },
27
+ { name: 'model', desc: 'view/change model', descEs: 'ver/cambiar modelo' },
28
+ { name: 'models', desc: 'list models', descEs: 'listar modelos' },
29
+ { name: 'providers', desc: 'list providers', descEs: 'listar proveedores' },
30
+ { name: 'git', desc: 'configure git credentials', descEs: 'configurar credenciales git' },
31
+ { name: 'persona', desc: 'set response tone/personality', descEs: 'definir tono/persona' },
32
+ { name: 'lang', desc: 'change language', descEs: 'cambiar idioma' },
33
+ { name: 'language', desc: 'change language', descEs: 'cambiar idioma' },
34
+ { name: 'auto', desc: 'auto-approval', descEs: 'auto-aprobación' },
35
+ { name: 'concuerdo', desc: 'group model mode', descEs: 'modo de grupo' },
36
+ { name: 'tools', desc: 'tools', descEs: 'herramientas' },
37
+ { name: 'skills', desc: 'agent skills', descEs: 'skills del agente' },
38
+ { name: 'config', desc: 'view/change session settings', descEs: 'ver/cambiar configuración' },
39
+ { name: 'web', desc: 'open web version', descEs: 'abrir versión web' },
40
+ { name: 'stop', desc: 'stop agent', descEs: 'detener agente' },
41
+ { name: 'abort', desc: 'stop agent', descEs: 'detener agente' },
42
+ { name: 'reset', desc: 'reset context', descEs: 'reiniciar contexto' },
43
+ { name: 'clear', desc: 'reset context', descEs: 'reiniciar contexto' },
44
+ { name: 'cwd', desc: 'working directory', descEs: 'directorio de trabajo' },
45
+ { name: 'transcript', desc: 'view transcript', descEs: 'ver transcripción' },
46
+ { name: 'export', desc: 'export to txt', descEs: 'exportar a txt' },
47
+ { name: 'exit', desc: 'exit', descEs: 'salir' },
48
+ { name: 'quit', desc: 'exit', descEs: 'salir' },
45
49
  ];
46
50
 
47
51
  function parseSlashCommand(input) {
@@ -57,6 +61,7 @@ function printHelp(state = {}) {
57
61
  const { paint } = require('./print');
58
62
  const lang = normalizeLanguage(state.language || DEFAULT_LANGUAGE);
59
63
  const m = (value) => paint(value, 'dim');
64
+ const b = (value) => paint(value, 'cyan');
60
65
  const providers = listProvidersFromModels(MODELS);
61
66
 
62
67
  console.log('');
@@ -68,15 +73,77 @@ function printHelp(state = {}) {
68
73
  console.log(` zyn --new ${m(t(lang, 'newSession'))}`);
69
74
  console.log(` zyn --resume ID ${m(t(lang, 'resumeSession'))}`);
70
75
  console.log('');
71
- console.log(` ${m(t(lang, 'commands'))}`);
72
- for (const cmd of SLASH_COMMANDS) {
73
- console.log(` /${cmd.name.padEnd(14)} ${m(cmd.desc)}`);
74
- }
76
+
77
+ // Sessions
78
+ console.log(` ${paint('── Sessions ──', 'dim')}`);
79
+ console.log(` ${b('/help')} Show this help`);
80
+ console.log(` ${b('/status')} Show current status`);
81
+ console.log(` ${b('/history')} Recent actions (last 20)`);
82
+ console.log(` ${b('/memory')} Agent memory summary`);
83
+ console.log(` ${b('/summary')} Alias of /memory`);
84
+ console.log(` ${b('/session')} Current session info`);
85
+ console.log(` ${b('/sessions')} List all saved sessions`);
86
+ console.log(` ${b('/new')} Create a new session`);
87
+ console.log(` ${b('/resume <ID>')} Resume an existing session`);
88
+ console.log(` ${b('/title <text>')} Rename current session`);
89
+ console.log(` ${b('/rename <text>')} Alias of /title`);
90
+ console.log('');
91
+
92
+ // Configuration
93
+ console.log(` ${paint('── Configuration ──', 'dim')}`);
94
+ console.log(` ${b('/model')} Show active model`);
95
+ console.log(` ${b('/model <key>')} Change active model`);
96
+ console.log(` ${b('/models')} List available models`);
97
+ console.log(` ${b('/providers')} List detected providers`);
98
+ console.log(` ${b('/lang')} Show current language`);
99
+ console.log(` ${b('/lang <en|es>')} Change language`);
100
+ console.log(` ${b('/language <en|es>')} Alias of /lang`);
101
+ console.log(` ${b('/auto')} Show auto-approval status`);
102
+ console.log(` ${b('/auto on')} Enable auto-approval`);
103
+ console.log(` ${b('/auto off')} Disable auto-approval`);
104
+ console.log(` ${b('/concuerdo')} Toggle group model mode`);
105
+ console.log(` ${b('/persona set <text>')} Set response persona/tone`);
106
+ console.log(` ${b('/persona show')} Show active persona`);
107
+ console.log(` ${b('/persona reset')} Reset to default persona`);
108
+ console.log(` ${b('/config show')} Show session config`);
109
+ console.log(` ${b('/config lang <en|es>')} Change language from config`);
110
+ console.log(` ${b('/config model <key>')} Change model from config`);
111
+ console.log(` ${b('/config auto on|off')} Toggle auto from config`);
112
+ console.log(` ${b('/config group on|off')} Toggle group mode from config`);
113
+ console.log(` ${b('/config cwd <path>')} Change working dir from config`);
114
+ console.log('');
115
+
116
+ // Tools and Git
117
+ console.log(` ${paint('── Tools and Git ──', 'dim')}`);
118
+ console.log(` ${b('/tools')} List available agent tools`);
119
+ console.log(` ${b('/skills')} List loaded skills`);
120
+ console.log(` ${b('/git set <provider> <token>')} Configure git credentials`);
121
+ console.log(` ${b('/git set <provider> <token> [user] [apiBaseUrl:URL] [cloneBaseUrl:URL] [name:X]')}`);
122
+ console.log(` ${b('/git list')} List configured git profiles`);
123
+ console.log(` ${b('/git remove <provider> [name]')} Remove git credentials`);
124
+ console.log(` ${b('/cwd')} Show current working directory`);
125
+ console.log(` ${b('/cwd <path>')} Change working directory`);
126
+ console.log('');
127
+
128
+ // Web and export
129
+ console.log(` ${paint('── Web and Export ──', 'dim')}`);
130
+ console.log(` ${b('/web')} Open web version`);
131
+ console.log(` ${b('/web <host:port>')} Open web version on custom host:port`);
132
+ console.log(` ${b('/transcript')} View full session transcript`);
133
+ console.log(` ${b('/export')} Export session to txt`);
134
+ console.log(` ${b('/export <path>')} Export session to specific path`);
75
135
  console.log('');
76
- console.log(` /config lang en|es ${m('change session language')}`);
77
- console.log(` /config model KEY ${m('change active model')}`);
78
- console.log(` /config show ${m('show current config')}`);
136
+
137
+ // Control
138
+ console.log(` ${paint('── Control ──', 'dim')}`);
139
+ console.log(` ${b('/stop')} Stop current agent turn`);
140
+ console.log(` ${b('/abort')} Alias of /stop`);
141
+ console.log(` ${b('/reset')} Reset context (clear history)`);
142
+ console.log(` ${b('/clear')} Alias of /reset`);
143
+ console.log(` ${b('/exit')} Exit Zyn`);
144
+ console.log(` ${b('/quit')} Alias of /exit`);
79
145
  console.log('');
146
+
80
147
  console.log(` ${m(t(lang, 'escTwice'))}`);
81
148
  console.log(` ${m(t(lang, 'escTwiceDesc'))}`);
82
149
  console.log('');
@@ -123,15 +190,16 @@ function printConfig(state) {
123
190
  console.log('');
124
191
  }
125
192
 
126
- async function startWebVersion() {
193
+ async function startWebVersion(host = '127.0.0.1', port = 3000) {
127
194
  const serverPath = path.join(__dirname, '..', 'web', 'server.js');
128
195
  const child = spawn(process.execPath, [serverPath], {
129
196
  detached: true,
130
197
  stdio: 'ignore',
131
198
  windowsHide: true,
199
+ env: { ...process.env, HOST: host, PORT: String(port) },
132
200
  });
133
201
  child.unref();
134
- return 'http://127.0.0.1:3000';
202
+ return `http://${host}:${port}`;
135
203
  }
136
204
 
137
205
  async function handleLocalCommand(input, state, deps) {
@@ -200,27 +268,50 @@ async function handleLocalCommand(input, state, deps) {
200
268
  if (commandName === 'git') {
201
269
  const [sub, ...rest] = args.split(' ').filter(Boolean);
202
270
  if (!sub || sub === 'help') {
203
- console.log('Uso: /git list | /git set <provider> <token> [username] | /git remove <provider>');
271
+ console.log('Uso: /git list');
272
+ console.log(' /git set <provider> <token> [username] [apiBaseUrl] [cloneBaseUrl] [name]');
273
+ console.log(' /git remove <provider> [name]');
274
+ console.log('');
275
+ console.log('Proveedores: github, gitlab, custom');
276
+ console.log('Para custom: apiBaseUrl y cloneBaseUrl son obligatorios para configurar la URL');
277
+ console.log('name: identificador para multiples perfiles custom');
278
+ console.log('');
279
+ console.log('Ejemplos:');
280
+ console.log(' /git set github ghp_xxxxx');
281
+ console.log(' /git set custom glpat_xxxxx - apiBaseUrl:https://git.empresa.com/api/v4 cloneBaseUrl:https://git.empresa.com name:empresa');
204
282
  return true;
205
283
  }
206
284
  if (sub === 'list') {
207
285
  const secrets = listGitSecrets();
208
286
  if (!secrets.length) console.log('No hay credenciales git guardadas.');
209
- else secrets.forEach(s => console.log(`${s.key} user:${s.username || '-'} api:${s.apiBaseUrl || '-'}`));
287
+ else {
288
+ for (const s of secrets) {
289
+ console.log(`${s.key} user:${s.username || '-'} api:${s.apiBaseUrl || '-'} clone:${s.cloneBaseUrl || '-'}`);
290
+ }
291
+ }
210
292
  return true;
211
293
  }
212
294
  if (sub === 'set') {
295
+ if (rest.length < 2) throw new Error('Uso: /git set <provider> <token> [username] [apiBaseUrl] [cloneBaseUrl] [name]');
213
296
  const [provider, token, username] = rest;
214
- if (!provider || !token) throw new Error('Uso: /git set <provider> <token> [username]');
215
- upsertGitSecret(provider, { provider, token, username });
216
- console.log(`Credencial guardada para ${provider}`);
297
+ let apiBaseUrl = '';
298
+ let cloneBaseUrl = '';
299
+ let name = '';
300
+ for (const part of rest.slice(3)) {
301
+ if (part.startsWith('apiBaseUrl:')) apiBaseUrl = part.slice('apiBaseUrl:'.length);
302
+ else if (part.startsWith('cloneBaseUrl:')) cloneBaseUrl = part.slice('cloneBaseUrl:'.length);
303
+ else if (part.startsWith('name:')) name = part.slice('name:'.length);
304
+ }
305
+ upsertGitSecret(provider, { provider, token, username: username || '', apiBaseUrl: apiBaseUrl || '', cloneBaseUrl: cloneBaseUrl || '', name });
306
+ console.log(`Credencial guardada para ${provider}${name ? `:${name}` : ''}`);
217
307
  return true;
218
308
  }
219
309
  if (sub === 'remove') {
220
- const [provider] = rest;
221
- if (!provider) throw new Error('Uso: /git remove <provider>');
222
- const removed = removeGitSecret(provider);
223
- console.log(removed ? `Credencial eliminada: ${provider}` : `No existe credencial para ${provider}`);
310
+ const [provider, namePart] = rest;
311
+ if (!provider) throw new Error('Uso: /git remove <provider> [name]');
312
+ const name = (namePart || '').startsWith('name:') ? namePart.slice('name:'.length) : '';
313
+ const removed = removeGitSecret(provider, name);
314
+ console.log(removed ? `Credencial eliminada: ${provider}${name ? `:${name}` : ''}` : `No existe credencial para ${provider}`);
224
315
  return true;
225
316
  }
226
317
  throw new Error('Subcomando git no reconocido. Usa /git help');
@@ -451,7 +542,19 @@ async function handleLocalCommand(input, state, deps) {
451
542
  }
452
543
 
453
544
  if (commandName === 'web') {
454
- const url = await startWebVersion();
545
+ let host = '127.0.0.1';
546
+ let port = 3000;
547
+ if (args) {
548
+ const parts = args.split(/[\s:]+/).filter(Boolean);
549
+ for (const part of parts) {
550
+ if (/^\d{1,5}$/.test(part)) {
551
+ port = Math.min(65535, Math.max(1, Number(part)));
552
+ } else if (/^[\d.]+$/.test(part) || part === 'localhost' || part === '0.0.0.0') {
553
+ host = part;
554
+ }
555
+ }
556
+ }
557
+ const url = await startWebVersion(host, port);
455
558
  console.log(`Web version started at ${url}`);
456
559
  return true;
457
560
  }
package/src/cli/print.js CHANGED
@@ -57,7 +57,7 @@ function wrapLines(text, maxWidth, indent = '') {
57
57
  continue;
58
58
  }
59
59
 
60
- const words = rawLine.split(/(\s+)/);
60
+ const words = rawLine.split(/(\s+)/).filter(Boolean);
61
61
  let line = '';
62
62
  let lineLen = indentLen;
63
63
 
@@ -97,7 +97,9 @@ function wrapLines(text, maxWidth, indent = '') {
97
97
  lineLen += word.length;
98
98
  }
99
99
 
100
- lines.push(indent + line);
100
+ if (line || rawLine.trim()) {
101
+ lines.push(indent + line);
102
+ }
101
103
  }
102
104
 
103
105
  return lines;
package/src/config.js CHANGED
@@ -96,7 +96,7 @@ const QWEN_PASSWORD = process.env.ZYN_QWEN_PASSWORD || 'zyzz1234';
96
96
 
97
97
  const MAX_TOOL_STEPS = Number.POSITIVE_INFINITY;
98
98
  const MAX_OUTPUT_CHARS = 12000;
99
- const MAX_FILE_LINES = 500;
99
+ const MAX_FILE_LINES = 5000;
100
100
  const ACTION_LOG_LIMIT = 40;
101
101
  const REQUEST_TIMEOUT_MS = Number(process.env.ZYN_REQUEST_TIMEOUT_MS || 180000);
102
102
  const MAX_HISTORY_CHARS = 24000;
@@ -108,6 +108,9 @@ const PERSISTENT_CONFIG_FILE = path.join(SESSION_ROOT, 'persistent-config.json')
108
108
  const TRANSCRIPTS_DIR = path.join(SESSION_ROOT, 'transcripts');
109
109
  const EXPORTS_DIR = path.join(SESSION_ROOT, 'exports');
110
110
  const THINK_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
111
+ const USER_DATA_ROOT = path.join(os.homedir(), '.zyn');
112
+ const TASKS_FILE = path.join(USER_DATA_ROOT, 'tasks.json');
113
+ const PROVIDERS_FILE = path.join(DATA_ROOT, 'providers.json');
111
114
 
112
115
  function listProvidersFromModels(models = MODELS) {
113
116
  const grouped = new Map();
@@ -148,12 +151,15 @@ module.exports = {
148
151
  MAX_TOOL_STEPS,
149
152
  MODELS,
150
153
  MODELS_FILE,
154
+ PROVIDERS_FILE,
151
155
  QWEN_EMAIL,
152
156
  QWEN_PASSWORD,
153
157
  REQUEST_TIMEOUT_MS,
154
158
  SESSION_ROOT,
155
159
  SESSIONS_DIR,
160
+ TASKS_FILE,
156
161
  THINK_FRAMES,
157
162
  TRANSCRIPTS_DIR,
163
+ USER_DATA_ROOT,
158
164
  listProvidersFromModels,
159
165
  };
package/src/core/agent.js CHANGED
@@ -251,7 +251,6 @@ async function runAgentTurn(input, state, ui, options = {}) {
251
251
  const toolPathUsage = new Map();
252
252
  let step = 0;
253
253
  const turnLanguage = detectLanguage(input, state.language);
254
- state.language = turnLanguage;
255
254
 
256
255
  while (true) {
257
256
  if (signal?.aborted) {
@@ -270,7 +269,7 @@ async function runAgentTurn(input, state, ui, options = {}) {
270
269
  const messages = buildConversationMessages(
271
270
  state,
272
271
  turnMessages,
273
- buildSystemPrompt(state.cwd, state, { input, language: detectLanguage(input, state.language) }),
272
+ buildSystemPrompt(state.cwd, state, { input, language: turnLanguage }),
274
273
  );
275
274
 
276
275
  const primaryPromise = requestModel(messages, state, ui, {
@@ -419,7 +418,7 @@ async function runAgentTurn(input, state, ui, options = {}) {
419
418
  const key = `${parsed.tool}:${targetPath}`;
420
419
  const nextCount = (toolPathUsage.get(key) || 0) + 1;
421
420
  toolPathUsage.set(key, nextCount);
422
- if (nextCount >= 3 && ['write_file', 'append_file', 'replace_in_file'].includes(parsed.tool)) {
421
+ if (nextCount >= 20 && ['write_file', 'append_file', 'replace_in_file'].includes(parsed.tool)) {
423
422
  ui.logEvent(state, 'warn', state.language === 'es' ? 'Posible loop detectado' : 'Possible loop detected', `${parsed.tool} → ${targetPath} x${nextCount}`);
424
423
  turnMessages.push({
425
424
  role: 'user',
@@ -433,7 +432,7 @@ async function runAgentTurn(input, state, ui, options = {}) {
433
432
  }
434
433
  if (fingerprint === lastFingerprint) {
435
434
  repeatCount += 1;
436
- if (repeatCount >= 2) {
435
+ if (repeatCount >= 19) {
437
436
  ui.logEvent(state, 'warn', 'Loop detectado', `${parsed.tool} repetido ${repeatCount + 1}x`);
438
437
  turnMessages.push({
439
438
  role: 'user',