zyn-ai 1.3.3 → 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/package.json +1 -1
- package/src/cli/runtime.js +3 -0
- package/src/core/agent.js +12 -0
- package/src/core/prompts.js +97 -25
package/package.json
CHANGED
package/src/cli/runtime.js
CHANGED
|
@@ -82,6 +82,9 @@ async function runSinglePrompt(prompt, options = {}) {
|
|
|
82
82
|
try {
|
|
83
83
|
const loaded = await loadOrCreateSessionState(rl, options);
|
|
84
84
|
state = loaded.state;
|
|
85
|
+
if (!rl) {
|
|
86
|
+
state.autoApprove = true;
|
|
87
|
+
}
|
|
85
88
|
const { resumed } = loaded;
|
|
86
89
|
if (process.stdout.isTTY) {
|
|
87
90
|
await printWelcome();
|
package/src/core/agent.js
CHANGED
|
@@ -2,6 +2,7 @@ const {
|
|
|
2
2
|
DEFAULT_MODEL_KEY,
|
|
3
3
|
KEEP_RECENT_MESSAGES,
|
|
4
4
|
MAX_HISTORY_CHARS,
|
|
5
|
+
MAX_TOOL_STEPS,
|
|
5
6
|
MODELS,
|
|
6
7
|
REQUEST_TIMEOUT_MS,
|
|
7
8
|
} = require('../config');
|
|
@@ -487,6 +488,17 @@ async function runAgentTurn(input, state, ui, options = {}) {
|
|
|
487
488
|
}
|
|
488
489
|
|
|
489
490
|
step += 1;
|
|
491
|
+
if (step >= MAX_TOOL_STEPS && MAX_TOOL_STEPS !== Number.POSITIVE_INFINITY) {
|
|
492
|
+
const limitMsg = state.language === 'es'
|
|
493
|
+
? `Se alcanzó el límite de ${MAX_TOOL_STEPS} pasos. No se pueden ejecutar más herramientas en este turno. Responde con un resumen de lo que lograste.`
|
|
494
|
+
: `Reached the limit of ${MAX_TOOL_STEPS} steps. No more tools can be executed this turn. Reply with a summary of what was accomplished.`;
|
|
495
|
+
turnMessages.push({ role: 'assistant', content: limitMsg });
|
|
496
|
+
state.history.push(...turnMessages);
|
|
497
|
+
await appendTranscriptEntry(state.sessionId, { type: 'assistant', content: limitMsg });
|
|
498
|
+
ui.logEvent(state, 'warn', state.language === 'es' ? 'Límite de pasos alcanzado' : 'Step limit reached');
|
|
499
|
+
await persistSessionState(state, ui);
|
|
500
|
+
return { content: limitMsg, rendered: false };
|
|
501
|
+
}
|
|
490
502
|
}
|
|
491
503
|
}
|
|
492
504
|
|
package/src/core/prompts.js
CHANGED
|
@@ -76,9 +76,6 @@ function buildSystemPrompt(cwd, state = {}, options = {}) {
|
|
|
76
76
|
'No cierres con una conclusion si todavia no has probado nada.',
|
|
77
77
|
'Si una tarea dura demasiado, usa run_command con un timeoutMs adecuado y confirma el resultado real.',
|
|
78
78
|
'Para operaciones de Git usa la herramienta git con action="api" o action="clone".',
|
|
79
|
-
'Usa exclusivamente tools registradas en "Tool use". No inventes nombres de tools ni aliases.',
|
|
80
|
-
'Mantente acotado y determinista: entradas claras, salidas claras, sin razonamiento creativo salvo que el usuario lo pida.',
|
|
81
|
-
'Cuando aplique, entrega resumen ejecutivo corto + riesgos/banderas rojas + siguiente accion concreta.',
|
|
82
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).',
|
|
83
80
|
'No te limites a una sola tool por costumbre; elige la mejor secuencia técnica para el objetivo.',
|
|
84
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.',
|
|
@@ -94,19 +91,61 @@ function buildSystemPrompt(cwd, state = {}, options = {}) {
|
|
|
94
91
|
'Do not end with a conclusion if you have not tested anything yet.',
|
|
95
92
|
'If a task takes long, use run_command with an appropriate timeoutMs and verify the real result.',
|
|
96
93
|
'For Git operations use the git tool with action="api" or action="clone".',
|
|
97
|
-
'Use only tools listed under "Tool use". Never invent tool names or aliases.',
|
|
98
|
-
'Stay bounded and deterministic: clear inputs, clear outputs, no creative reasoning unless explicitly requested.',
|
|
99
|
-
'When relevant, provide a short executive summary + obvious red flags + next concrete action.',
|
|
100
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.',
|
|
101
95
|
'Do not over-focus on a single tool by habit; choose the best technical sequence for the goal.',
|
|
102
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.',
|
|
103
97
|
];
|
|
104
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
|
+
|
|
105
143
|
const parts = [
|
|
106
144
|
skills,
|
|
107
145
|
'',
|
|
108
146
|
'# Tool use',
|
|
109
147
|
getToolPromptText(),
|
|
148
|
+
...toolUseEnforcement,
|
|
110
149
|
'',
|
|
111
150
|
'# Environment',
|
|
112
151
|
`- Working directory: ${cwd}`,
|
|
@@ -220,6 +259,9 @@ function classifyParsed(parsed) {
|
|
|
220
259
|
if (parsed?.type === 'final') {
|
|
221
260
|
return { type: 'final', content: typeof parsed.content === 'string' ? parsed.content : '' };
|
|
222
261
|
}
|
|
262
|
+
if (parsed?.tool && KNOWN_TOOLS.has(parsed.tool)) {
|
|
263
|
+
return { type: 'tool', tool: parsed.tool, args: parsed.args ?? {} };
|
|
264
|
+
}
|
|
223
265
|
return null;
|
|
224
266
|
}
|
|
225
267
|
|
|
@@ -253,7 +295,7 @@ const LONG_VALUE_ARG = {
|
|
|
253
295
|
};
|
|
254
296
|
|
|
255
297
|
function fuzzyExtractTool(text) {
|
|
256
|
-
const toolMatch = text.match(/"tool"
|
|
298
|
+
const toolMatch = text.match(/(?:"|')?tool(?:"|')?\s*:\s*"(\w+)"/i);
|
|
257
299
|
if (!toolMatch) return null;
|
|
258
300
|
|
|
259
301
|
const tool = toolMatch[1];
|
|
@@ -284,32 +326,57 @@ function findStringEnd(text, start) {
|
|
|
284
326
|
return -1;
|
|
285
327
|
}
|
|
286
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
|
+
|
|
287
353
|
function extractLongValueTool(text, tool, longArg) {
|
|
354
|
+
const context = extractArgsContext(text);
|
|
288
355
|
const args = {};
|
|
289
356
|
const keys = TOOL_ARG_KEYS[tool] || [];
|
|
290
357
|
|
|
291
358
|
for (const key of keys) {
|
|
292
359
|
if (key === longArg) continue;
|
|
293
|
-
const m =
|
|
360
|
+
const m = context.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*"([^"]*?)"`));
|
|
294
361
|
if (m) args[key] = unescapeJsonString(m[1]);
|
|
295
|
-
const bm =
|
|
362
|
+
const bm = context.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*(true|false|\\d+)`));
|
|
296
363
|
if (bm) args[key] = bm[1] === 'true' ? true : bm[1] === 'false' ? false : Number(bm[1]);
|
|
297
364
|
}
|
|
298
365
|
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
366
|
+
const longKeyRe = new RegExp(`(?:"|')?${longArg}(?:"|')?\\s*:`);
|
|
367
|
+
const longMatch = context.match(longKeyRe);
|
|
368
|
+
if (!longMatch) return null;
|
|
302
369
|
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
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;
|
|
308
375
|
|
|
309
|
-
const valEnd = findStringEnd(
|
|
376
|
+
const valEnd = findStringEnd(context, valStart);
|
|
310
377
|
if (valEnd === -1 || valEnd <= valStart) return null;
|
|
311
378
|
|
|
312
|
-
const value =
|
|
379
|
+
const value = context.slice(valStart, valEnd);
|
|
313
380
|
if (!value.trim()) return null;
|
|
314
381
|
|
|
315
382
|
args[longArg] = unescapeJsonString(value);
|
|
@@ -321,9 +388,9 @@ function extractSimpleArgsTool(text, tool) {
|
|
|
321
388
|
const keys = TOOL_ARG_KEYS[tool] || [];
|
|
322
389
|
|
|
323
390
|
for (const key of keys) {
|
|
324
|
-
const strM = text.match(new RegExp(`"
|
|
391
|
+
const strM = text.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*"([^"]*?)"`));
|
|
325
392
|
if (strM) { args[key] = unescapeJsonString(strM[1]); continue; }
|
|
326
|
-
const numM = text.match(new RegExp(`"
|
|
393
|
+
const numM = text.match(new RegExp(`(?:"|')?${key}(?:"|')?\\s*:\\s*(true|false|\\d+)`));
|
|
327
394
|
if (numM) {
|
|
328
395
|
const v = numM[1];
|
|
329
396
|
args[key] = v === 'true' ? true : v === 'false' ? false : Number(v);
|
|
@@ -364,7 +431,7 @@ function parseAgentResponse(raw) {
|
|
|
364
431
|
const fuzzy = fuzzyExtractTool(text);
|
|
365
432
|
if (fuzzy) return fuzzy;
|
|
366
433
|
|
|
367
|
-
return { type: 'final', content: text || raw.trim() };
|
|
434
|
+
return { type: 'final', content: text || (raw ? String(raw).trim() : '') };
|
|
368
435
|
}
|
|
369
436
|
|
|
370
437
|
function sanitizeArgsForModel(parsed) {
|
|
@@ -402,11 +469,15 @@ function buildConversationMessages(state, turnMessages, systemPrompt) {
|
|
|
402
469
|
}
|
|
403
470
|
|
|
404
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;
|
|
405
476
|
return [
|
|
406
477
|
`Herramienta: ${parsed.tool}`,
|
|
407
478
|
`Argumentos: ${JSON.stringify(sanitizeArgsForModel(parsed), null, 2)}`,
|
|
408
479
|
'Resultado:',
|
|
409
|
-
|
|
480
|
+
truncatedResult,
|
|
410
481
|
'',
|
|
411
482
|
'Responde con la siguiente accion concreta o con el resultado final.',
|
|
412
483
|
].join('\n');
|
|
@@ -414,9 +485,10 @@ function buildToolResultMessage(parsed, result) {
|
|
|
414
485
|
|
|
415
486
|
function buildToolErrorMessage(parsed, errorMessage) {
|
|
416
487
|
return [
|
|
417
|
-
`La herramienta ${parsed.tool}
|
|
488
|
+
`La herramienta "${parsed.tool}" NO existe.`,
|
|
418
489
|
`Error: ${errorMessage}`,
|
|
419
|
-
|
|
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.',
|
|
420
492
|
].join('\n');
|
|
421
493
|
}
|
|
422
494
|
|