zyn-ai 1.3.2 → 1.3.4
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 +86 -93
- package/package.json +1 -1
- package/src/cli/commands.js +151 -48
- package/src/cli/print.js +4 -2
- package/src/cli/runtime.js +3 -0
- package/src/config.js +7 -1
- package/src/core/agent.js +15 -4
- package/src/core/prompts.js +141 -32
- package/src/tools/index.js +140 -121
- package/src/tui/app.mjs +72 -21
- package/src/web/server.js +10 -5
package/src/core/prompts.js
CHANGED
|
@@ -3,20 +3,56 @@ const { buildSkillsPrompt } = require('./skills');
|
|
|
3
3
|
const { getToolPromptText, TOOL_DEFINITIONS } = require('../tools');
|
|
4
4
|
const { listProvidersFromModels, MODELS, DEFAULT_MODEL_KEY } = require('../config');
|
|
5
5
|
const { detectLanguage, normalizeLanguage, languageLabel } = require('../i18n');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
|
|
9
|
+
function getPlatformInfo() {
|
|
10
|
+
// Detectar SO real primero
|
|
11
|
+
let osName = 'Unknown';
|
|
12
|
+
|
|
13
|
+
if (process.platform === 'linux') {
|
|
14
|
+
try {
|
|
15
|
+
const release = fs.readFileSync('/etc/os-release', 'utf8');
|
|
16
|
+
const nameMatch = release.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
|
|
17
|
+
if (nameMatch) {
|
|
18
|
+
osName = nameMatch[1];
|
|
19
|
+
} else {
|
|
20
|
+
const idMatch = release.match(/^ID="?([^"\n]+)"?/m);
|
|
21
|
+
const verMatch = release.match(/^VERSION_ID="?([^"\n]+)"?/m);
|
|
22
|
+
osName = `Linux ${idMatch ? idMatch[1] : ''}${verMatch ? ' ' + verMatch[1] : ''}`.trim();
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
try {
|
|
26
|
+
const { execSync } = require('child_process');
|
|
27
|
+
osName = execSync('uname -o -r -m', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
28
|
+
} catch {
|
|
29
|
+
osName = `Linux ${os.release()} ${os.arch()}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else if (process.platform === 'darwin') {
|
|
33
|
+
try {
|
|
34
|
+
const { execSync } = require('child_process');
|
|
35
|
+
const ver = execSync('sw_vers -productVersion', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
36
|
+
osName = `macOS ${ver} (${os.arch()})`;
|
|
37
|
+
} catch {
|
|
38
|
+
osName = `macOS ${os.release()} (${os.arch()})`;
|
|
39
|
+
}
|
|
40
|
+
} else if (process.platform === 'win32') {
|
|
41
|
+
osName = `Windows ${os.release()} (${os.arch()})`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return osName;
|
|
45
|
+
}
|
|
6
46
|
|
|
7
47
|
const KNOWN_TOOLS = new Set([
|
|
8
48
|
...TOOL_DEFINITIONS.map(tool => tool.name),
|
|
9
49
|
'task_create', 'task_list', 'task_update', 'task_complete', 'task_delete', 'task_clear',
|
|
10
|
-
'git_secret_set', 'git_secret_list', 'git_secret_remove',
|
|
11
|
-
'git_clone_repo', 'git_api_request',
|
|
12
50
|
]);
|
|
13
51
|
|
|
14
52
|
|
|
15
53
|
function buildSystemPrompt(cwd, state = {}, options = {}) {
|
|
16
54
|
const language = normalizeLanguage(options.language || state.language || detectLanguage(options.input || '', state.language));
|
|
17
|
-
const platform =
|
|
18
|
-
: process.platform === 'darwin' ? 'macOS'
|
|
19
|
-
: process.platform;
|
|
55
|
+
const platform = getPlatformInfo();
|
|
20
56
|
const date = new Date().toLocaleDateString(language === 'es' ? 'es-ES' : 'en-US', {
|
|
21
57
|
year: 'numeric',
|
|
22
58
|
month: 'long',
|
|
@@ -39,10 +75,7 @@ function buildSystemPrompt(cwd, state = {}, options = {}) {
|
|
|
39
75
|
'Si la tarea requiere comprobar algo, primero intenta una herramienta real y espera el resultado antes de concluir.',
|
|
40
76
|
'No cierres con una conclusion si todavia no has probado nada.',
|
|
41
77
|
'Si una tarea dura demasiado, usa run_command con un timeoutMs adecuado y confirma el resultado real.',
|
|
42
|
-
'Para
|
|
43
|
-
'Usa exclusivamente tools registradas en "Tool use". No inventes nombres de tools ni aliases.',
|
|
44
|
-
'Para tareas empresariales, mantente acotado y determinista: entradas claras, salidas claras, sin razonamiento creativo salvo que el usuario lo pida.',
|
|
45
|
-
'Cuando aplique, entrega resumen ejecutivo corto + riesgos/banderas rojas + siguiente accion concreta.',
|
|
78
|
+
'Para operaciones de Git usa la herramienta git con action="api" o action="clone".',
|
|
46
79
|
'Para proyectos, usa combinaciones de tools según la fase: descubrir (list_dir/search_text), leer (read_file/fetch/webfetch), cambiar (write/replace), validar (run_command), documentar (final).',
|
|
47
80
|
'No te limites a una sola tool por costumbre; elige la mejor secuencia técnica para el objetivo.',
|
|
48
81
|
'Si el usuario pide logos, mockups o piezas visuales para un proyecto/frontend, usa create_canvas_image cuando corresponda, junto al resto de tools del flujo.',
|
|
@@ -57,20 +90,62 @@ function buildSystemPrompt(cwd, state = {}, options = {}) {
|
|
|
57
90
|
'If the task requires verification, try a real tool first and wait for its result before concluding.',
|
|
58
91
|
'Do not end with a conclusion if you have not tested anything yet.',
|
|
59
92
|
'If a task takes long, use run_command with an appropriate timeoutMs and verify the real result.',
|
|
60
|
-
'For
|
|
61
|
-
'Use only tools listed under "Tool use". Never invent tool names or aliases.',
|
|
62
|
-
'For business tasks, stay bounded and deterministic: clear inputs, clear outputs, no creative reasoning unless explicitly requested.',
|
|
63
|
-
'When relevant, provide a short executive summary + obvious red flags + next concrete action.',
|
|
93
|
+
'For Git operations use the git tool with action="api" or action="clone".',
|
|
64
94
|
'For project work, combine tools by phase: discover (list_dir/search_text), read (read_file/fetch/webfetch), change (write/replace), validate (run_command), then report.',
|
|
65
95
|
'Do not over-focus on a single tool by habit; choose the best technical sequence for the goal.',
|
|
66
96
|
'If the user asks for logos, mockups, or visual assets for a project/frontend, use create_canvas_image when appropriate together with the rest of the workflow.',
|
|
67
97
|
];
|
|
68
98
|
|
|
99
|
+
const toolUseEnforcement = language === 'es'
|
|
100
|
+
? [
|
|
101
|
+
'',
|
|
102
|
+
'# Formato obligatorio de respuesta',
|
|
103
|
+
'Solo existen DOS formatos de respuesta. NO inventes otros:',
|
|
104
|
+
'',
|
|
105
|
+
'FORMATO 1 — Para USAR una herramienta:',
|
|
106
|
+
'{"type":"tool","tool":"NOMBRE_EXACTO","args":{"clave":"valor"}}',
|
|
107
|
+
'',
|
|
108
|
+
'FORMATO 2 — Para responder AL USUARIO:',
|
|
109
|
+
'{"type":"final","content":"Tu respuesta aqui"}',
|
|
110
|
+
'',
|
|
111
|
+
'REGLAS ESTRICTAS:',
|
|
112
|
+
'- USA EXCLUSIVAMENTE los nombres de herramientas listados en "# Tool use".',
|
|
113
|
+
'- NO inventes herramientas como "code_interpreter", "python", "bash", "shell", etc.',
|
|
114
|
+
'- NO uses formatos como <invoke>, function calls, ni tool_use de otros sistemas.',
|
|
115
|
+
'- Si una herramienta falla, INTENTA con otra herramienta diferente.',
|
|
116
|
+
'- Si una herramienta falla 2 VECES seguidas, no la repitas. Cambia de estrategia o usa type=final.',
|
|
117
|
+
'- LIMITE: Maximo 8 herramientas por turno. Despues de 8 pasos, responde con type=final.',
|
|
118
|
+
'- Si no puedes completar la tarea, responde con type=final explicando honestamente por que.',
|
|
119
|
+
'- Cada respuesta debe ser UNICAMENTE el JSON. Sin texto antes ni despues.',
|
|
120
|
+
]
|
|
121
|
+
: [
|
|
122
|
+
'',
|
|
123
|
+
'# Strict response format',
|
|
124
|
+
'Only TWO response formats are allowed. Do NOT use others:',
|
|
125
|
+
'',
|
|
126
|
+
'FORMAT 1 — To USE a tool:',
|
|
127
|
+
'{"type":"tool","tool":"EXACT_NAME","args":{"key":"value"}}',
|
|
128
|
+
'',
|
|
129
|
+
'FORMAT 2 — To REPLY to user:',
|
|
130
|
+
'{"type":"final","content":"Your answer here"}',
|
|
131
|
+
'',
|
|
132
|
+
'STRICT RULES:',
|
|
133
|
+
'- ONLY use tool names listed in "# Tool use".',
|
|
134
|
+
'- Do NOT invent tools like "code_interpreter", "python", "bash", "shell", etc.',
|
|
135
|
+
'- Do NOT use <invoke>, function call, or tool_use formats from other systems.',
|
|
136
|
+
'- If a tool fails, TRY a different tool instead.',
|
|
137
|
+
'- If a tool fails 2 TIMES in a row, do not repeat it. Change strategy or use type=final.',
|
|
138
|
+
'- LIMIT: Maximum 8 tools per turn. After 8 steps, respond with type=final.',
|
|
139
|
+
'- If you cannot complete the task, use type=final and honestly explain why.',
|
|
140
|
+
'- Each response must be ONLY the JSON. No text before or after.',
|
|
141
|
+
];
|
|
142
|
+
|
|
69
143
|
const parts = [
|
|
70
144
|
skills,
|
|
71
145
|
'',
|
|
72
146
|
'# Tool use',
|
|
73
147
|
getToolPromptText(),
|
|
148
|
+
...toolUseEnforcement,
|
|
74
149
|
'',
|
|
75
150
|
'# Environment',
|
|
76
151
|
`- Working directory: ${cwd}`,
|
|
@@ -184,6 +259,9 @@ function classifyParsed(parsed) {
|
|
|
184
259
|
if (parsed?.type === 'final') {
|
|
185
260
|
return { type: 'final', content: typeof parsed.content === 'string' ? parsed.content : '' };
|
|
186
261
|
}
|
|
262
|
+
if (parsed?.tool && KNOWN_TOOLS.has(parsed.tool)) {
|
|
263
|
+
return { type: 'tool', tool: parsed.tool, args: parsed.args ?? {} };
|
|
264
|
+
}
|
|
187
265
|
return null;
|
|
188
266
|
}
|
|
189
267
|
|
|
@@ -206,6 +284,7 @@ const TOOL_ARG_KEYS = {
|
|
|
206
284
|
web_search: ['query', 'lang', 'limit'],
|
|
207
285
|
web_read: ['url'],
|
|
208
286
|
create_canvas_image: ['width', 'height', 'background', 'elements', 'format', 'outputPath'],
|
|
287
|
+
git: ['provider', 'action', 'method', 'path', 'body', 'headers', 'name', 'repoUrl', 'destination', 'branch', 'timeoutMs'],
|
|
209
288
|
};
|
|
210
289
|
|
|
211
290
|
const LONG_VALUE_ARG = {
|
|
@@ -216,7 +295,7 @@ const LONG_VALUE_ARG = {
|
|
|
216
295
|
};
|
|
217
296
|
|
|
218
297
|
function fuzzyExtractTool(text) {
|
|
219
|
-
const toolMatch = text.match(/"tool"
|
|
298
|
+
const toolMatch = text.match(/(?:"|')?tool(?:"|')?\s*:\s*"(\w+)"/i);
|
|
220
299
|
if (!toolMatch) return null;
|
|
221
300
|
|
|
222
301
|
const tool = toolMatch[1];
|
|
@@ -247,32 +326,57 @@ function findStringEnd(text, start) {
|
|
|
247
326
|
return -1;
|
|
248
327
|
}
|
|
249
328
|
|
|
329
|
+
function extractArgsContext(text) {
|
|
330
|
+
const argsMatch = text.match(/(?:"|')?args(?:"|')?\s*:\s*\{/i);
|
|
331
|
+
if (!argsMatch) return text;
|
|
332
|
+
|
|
333
|
+
let depth = 1;
|
|
334
|
+
let inStr = false;
|
|
335
|
+
let esc = false;
|
|
336
|
+
const start = argsMatch.index + argsMatch[0].length - 1;
|
|
337
|
+
|
|
338
|
+
for (let i = start + 1; i < text.length; i++) {
|
|
339
|
+
const ch = text[i];
|
|
340
|
+
if (esc) { esc = false; continue; }
|
|
341
|
+
if (ch === '\\' && inStr) { esc = true; continue; }
|
|
342
|
+
if (ch === '"') { inStr = !inStr; continue; }
|
|
343
|
+
if (inStr) continue;
|
|
344
|
+
if (ch === '{') depth++;
|
|
345
|
+
if (ch === '}') {
|
|
346
|
+
depth--;
|
|
347
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return text;
|
|
351
|
+
}
|
|
352
|
+
|
|
250
353
|
function extractLongValueTool(text, tool, longArg) {
|
|
354
|
+
const context = extractArgsContext(text);
|
|
251
355
|
const args = {};
|
|
252
356
|
const keys = TOOL_ARG_KEYS[tool] || [];
|
|
253
357
|
|
|
254
358
|
for (const key of keys) {
|
|
255
359
|
if (key === longArg) continue;
|
|
256
|
-
const m =
|
|
360
|
+
const m = context.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*"([^"]*?)"`));
|
|
257
361
|
if (m) args[key] = unescapeJsonString(m[1]);
|
|
258
|
-
const bm =
|
|
362
|
+
const bm = context.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*(true|false|\\d+)`));
|
|
259
363
|
if (bm) args[key] = bm[1] === 'true' ? true : bm[1] === 'false' ? false : Number(bm[1]);
|
|
260
364
|
}
|
|
261
365
|
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
if (
|
|
366
|
+
const longKeyRe = new RegExp(`(?:"|')?${longArg}(?:"|')?\\s*:`);
|
|
367
|
+
const longMatch = context.match(longKeyRe);
|
|
368
|
+
if (!longMatch) return null;
|
|
265
369
|
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
const valStart =
|
|
370
|
+
const colonPos = context.indexOf(':', longMatch.index + longMatch[0].length - 1);
|
|
371
|
+
if (colonPos === -1) return null;
|
|
372
|
+
const quotePos = context.indexOf('"', colonPos);
|
|
373
|
+
if (quotePos === -1) return null;
|
|
374
|
+
const valStart = quotePos + 1;
|
|
271
375
|
|
|
272
|
-
const valEnd = findStringEnd(
|
|
376
|
+
const valEnd = findStringEnd(context, valStart);
|
|
273
377
|
if (valEnd === -1 || valEnd <= valStart) return null;
|
|
274
378
|
|
|
275
|
-
const value =
|
|
379
|
+
const value = context.slice(valStart, valEnd);
|
|
276
380
|
if (!value.trim()) return null;
|
|
277
381
|
|
|
278
382
|
args[longArg] = unescapeJsonString(value);
|
|
@@ -284,9 +388,9 @@ function extractSimpleArgsTool(text, tool) {
|
|
|
284
388
|
const keys = TOOL_ARG_KEYS[tool] || [];
|
|
285
389
|
|
|
286
390
|
for (const key of keys) {
|
|
287
|
-
const strM = text.match(new RegExp(`"
|
|
391
|
+
const strM = text.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*"([^"]*?)"`));
|
|
288
392
|
if (strM) { args[key] = unescapeJsonString(strM[1]); continue; }
|
|
289
|
-
const numM = text.match(new RegExp(`"
|
|
393
|
+
const numM = text.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*(true|false|\\d+)`));
|
|
290
394
|
if (numM) {
|
|
291
395
|
const v = numM[1];
|
|
292
396
|
args[key] = v === 'true' ? true : v === 'false' ? false : Number(v);
|
|
@@ -327,7 +431,7 @@ function parseAgentResponse(raw) {
|
|
|
327
431
|
const fuzzy = fuzzyExtractTool(text);
|
|
328
432
|
if (fuzzy) return fuzzy;
|
|
329
433
|
|
|
330
|
-
return { type: 'final', content: text || raw.trim() };
|
|
434
|
+
return { type: 'final', content: text || (raw ? String(raw).trim() : '') };
|
|
331
435
|
}
|
|
332
436
|
|
|
333
437
|
function sanitizeArgsForModel(parsed) {
|
|
@@ -365,11 +469,15 @@ function buildConversationMessages(state, turnMessages, systemPrompt) {
|
|
|
365
469
|
}
|
|
366
470
|
|
|
367
471
|
function buildToolResultMessage(parsed, result) {
|
|
472
|
+
const maxResultChars = 8000;
|
|
473
|
+
const truncatedResult = typeof result === 'string' && result.length > maxResultChars
|
|
474
|
+
? `${result.slice(0, maxResultChars)}\n... [resultado truncado, ${result.length} caracteres totales]`
|
|
475
|
+
: result;
|
|
368
476
|
return [
|
|
369
477
|
`Herramienta: ${parsed.tool}`,
|
|
370
478
|
`Argumentos: ${JSON.stringify(sanitizeArgsForModel(parsed), null, 2)}`,
|
|
371
479
|
'Resultado:',
|
|
372
|
-
|
|
480
|
+
truncatedResult,
|
|
373
481
|
'',
|
|
374
482
|
'Responde con la siguiente accion concreta o con el resultado final.',
|
|
375
483
|
].join('\n');
|
|
@@ -377,9 +485,10 @@ function buildToolResultMessage(parsed, result) {
|
|
|
377
485
|
|
|
378
486
|
function buildToolErrorMessage(parsed, errorMessage) {
|
|
379
487
|
return [
|
|
380
|
-
`La herramienta ${parsed.tool}
|
|
488
|
+
`La herramienta "${parsed.tool}" NO existe.`,
|
|
381
489
|
`Error: ${errorMessage}`,
|
|
382
|
-
|
|
490
|
+
`Las unicas herramientas disponibles son: ${TOOL_DEFINITIONS.map(t => t.name).join(', ')}.`,
|
|
491
|
+
'Elige UNA de esas herramientas. Usa el formato exacto del nombre. No inventes herramientas.',
|
|
383
492
|
].join('\n');
|
|
384
493
|
}
|
|
385
494
|
|