zyn-ai 1.3.5 → 1.3.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zyn-ai",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "description": "Production-ready AI agent for CLI and web with real tool execution and automation",
5
5
  "author": "Maycol",
6
6
  "keywords": [
package/src/cli/print.js CHANGED
@@ -112,11 +112,11 @@ function pushAction(state, kind, title, detail = '') {
112
112
  }
113
113
 
114
114
  const EVENT_SYMBOLS = {
115
- info: { sym: '·', color: C.gray },
115
+ info: { sym: '', color: C.gray },
116
116
  think: { sym: '○', color: C.gray },
117
- tool: { sym: '', color: C.purple },
117
+ tool: { sym: '', color: C.purple },
118
118
  ok: { sym: '✓', color: C.green },
119
- warn: { sym: '', color: C.yellow },
119
+ warn: { sym: '', color: C.yellow },
120
120
  error: { sym: '✗', color: C.red },
121
121
  };
122
122
 
@@ -44,6 +44,23 @@ function getPlatformInfo() {
44
44
  return osName;
45
45
  }
46
46
 
47
+
48
+ const TOOL_ALIASES = {
49
+ bash: 'run_command',
50
+ shell: 'run_command',
51
+ terminal: 'run_command',
52
+ execute_command: 'run_command',
53
+ command: 'run_command',
54
+ run_terminal_command: 'run_command',
55
+ };
56
+
57
+ function normalizeToolName(name) {
58
+ const raw = String(name || '').trim();
59
+ if (!raw) return raw;
60
+ const lower = raw.toLowerCase();
61
+ return TOOL_ALIASES[lower] || lower;
62
+ }
63
+
47
64
  const KNOWN_TOOLS = new Set([
48
65
  ...TOOL_DEFINITIONS.map(tool => tool.name),
49
66
  'task_create', 'task_list', 'task_update', 'task_complete', 'task_delete', 'task_clear',
@@ -238,7 +255,7 @@ function extractXmlTool(text) {
238
255
  );
239
256
  if (!invokeMatch) return null;
240
257
 
241
- const tool = invokeMatch[1];
258
+ const tool = normalizeToolName(invokeMatch[1]);
242
259
  if (!KNOWN_TOOLS.has(tool)) return null;
243
260
 
244
261
  const rawArgs = invokeMatch[2].trim();
@@ -257,13 +274,15 @@ function extractXmlTool(text) {
257
274
 
258
275
  function classifyParsed(parsed) {
259
276
  if (parsed?.type === 'tool' && parsed.tool) {
260
- return { type: 'tool', tool: parsed.tool, args: parsed.args ?? {} };
277
+ const normalized = normalizeToolName(parsed.tool);
278
+ if (KNOWN_TOOLS.has(normalized)) return { type: 'tool', tool: normalized, args: parsed.args ?? {} };
261
279
  }
262
280
  if (parsed?.type === 'final') {
263
281
  return { type: 'final', content: typeof parsed.content === 'string' ? parsed.content : '' };
264
282
  }
265
- if (parsed?.tool && KNOWN_TOOLS.has(parsed.tool)) {
266
- return { type: 'tool', tool: parsed.tool, args: parsed.args ?? {} };
283
+ if (parsed?.tool) {
284
+ const normalized = normalizeToolName(parsed.tool);
285
+ if (KNOWN_TOOLS.has(normalized)) return { type: 'tool', tool: normalized, args: parsed.args ?? {} };
267
286
  }
268
287
  return null;
269
288
  }
@@ -335,7 +354,7 @@ function fuzzyExtractTool(text) {
335
354
  const toolMatch = text.match(/(?:"|')?tool(?:"|')?\s*:\s*"(\w+)"/i);
336
355
  if (!toolMatch) return null;
337
356
 
338
- const tool = toolMatch[1];
357
+ const tool = normalizeToolName(toolMatch[1]);
339
358
  if (!KNOWN_TOOLS.has(tool)) return null;
340
359
 
341
360
  const longArg = LONG_VALUE_ARG[tool];
@@ -529,7 +548,7 @@ function buildToolResultMessage(parsed, result) {
529
548
 
530
549
  function buildToolErrorMessage(parsed, errorMessage) {
531
550
  return [
532
- `La herramienta "${parsed.tool}" NO existe.`,
551
+ `La herramienta "${parsed.tool}" no se pudo ejecutar (nombre inválido o no disponible en este modo).`,
533
552
  `Error: ${errorMessage}`,
534
553
  `Las unicas herramientas disponibles son: ${TOOL_DEFINITIONS.map(t => t.name).join(', ')}.`,
535
554
  'Elige UNA de esas herramientas. Usa el formato exacto del nombre. No inventes herramientas.',
package/src/tui/app.mjs CHANGED
@@ -28,13 +28,12 @@ function uiText(en, es) {
28
28
  return getTuiLang() === 'es' ? es : en;
29
29
  }
30
30
  const MAX_THINKING_LINES = 20;
31
- const MAX_PASTE_PREVIEW = 200;
32
31
  const SPIN_MS = 80;
33
32
 
34
33
  const SPIN_FRAMES = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f'];
35
34
 
36
35
  const T = {
37
- bg: '#0d0d0d',
36
+ bg: '#212823',
38
37
  surface: '#1a1a1a',
39
38
  surfaceHi: '#222222',
40
39
  text: '#ffffff',
@@ -78,6 +77,8 @@ class UIStore extends EventEmitter {
78
77
  this.messageQueue = [];
79
78
  this.pendingExit = false;
80
79
  this.lastEscapeAt = 0;
80
+ this.submittedHistory = [];
81
+ this.submittedRedo = [];
81
82
  this._idCounter = 0;
82
83
  this._scheduled = false;
83
84
  }
@@ -159,6 +160,34 @@ class UIStore extends EventEmitter {
159
160
  this._emit();
160
161
  }
161
162
 
163
+
164
+ pushSubmittedMessage(text) {
165
+ const value = String(text || '');
166
+ if (!value) return;
167
+ this.submittedHistory.unshift(value);
168
+ if (this.submittedHistory.length > 200) this.submittedHistory.pop();
169
+ this.submittedRedo = [];
170
+ this._emit();
171
+ }
172
+
173
+ undoSubmittedMessage() {
174
+ if (this.submittedHistory.length === 0) return null;
175
+ const value = this.submittedHistory.shift();
176
+ this.submittedRedo.unshift(value);
177
+ this.setInputDraft(value);
178
+ this.addEvent('info', uiText('message restored', 'mensaje restaurado'), shortTextPreview(value, 120));
179
+ return value;
180
+ }
181
+
182
+ redoSubmittedMessage() {
183
+ if (this.submittedRedo.length === 0) return null;
184
+ const value = this.submittedRedo.shift();
185
+ this.submittedHistory.unshift(value);
186
+ this.setInputDraft(value);
187
+ this.addEvent('info', uiText('message reapplied', 'mensaje reaplicado'), shortTextPreview(value, 120));
188
+ return value;
189
+ }
190
+
162
191
  _emit() {
163
192
  if (this._scheduled) return;
164
193
  this._scheduled = true;
@@ -466,15 +495,19 @@ function EventLine({ kind, title, detail }) {
466
495
  };
467
496
  const { sym, color } = cfg[kind] || cfg.info;
468
497
 
469
- const compactTitle = String(title || '').replace(/\s{2,}/g, ' ').trim();
470
- const compactDetail = String(detail || '').replace(/\s{2,}/g, ' ').trim();
498
+ const cleanTitle = String(title || '').trim();
499
+ const detailLines = String(detail || '').split('\n').map(line => line.replace(/\t/g, ' ').replace(/\s+$/g, '')).filter(Boolean);
471
500
 
472
501
  return h(Box, { paddingLeft: 3, flexDirection: 'column' },
473
502
  h(Box, { gap: 1 },
474
503
  h(Text, { color }, sym),
475
- h(Text, { color: kind === 'tool' ? T.textDim : T.textMuted, wrap: 'wrap' }, compactTitle),
504
+ h(Text, { color: kind === 'tool' ? T.textDim : T.textMuted, wrap: 'wrap' }, cleanTitle),
476
505
  ),
477
- compactDetail ? h(Box, { paddingLeft: 2 }, h(Text, { color: T.textGhost, wrap: 'wrap' }, compactDetail)) : null,
506
+ detailLines.length
507
+ ? h(Box, { paddingLeft: 2, flexDirection: 'column' },
508
+ ...detailLines.map((line, idx) => h(Text, { key: String(idx), color: T.textGhost, wrap: 'wrap' }, line)),
509
+ )
510
+ : null,
478
511
  );
479
512
  }
480
513
 
@@ -675,8 +708,12 @@ function InputBar({ onSubmit, processing, width = 100, draft = '', onDraftChange
675
708
  }, [onDraftChange]);
676
709
 
677
710
  const showSuggestions = value.startsWith('/') && !value.includes(' ') && value.length > 0;
711
+ const localSlash = [
712
+ { name: 'undo', desc: 'restore previous message', descEs: 'restaurar mensaje anterior' },
713
+ { name: 'redo', desc: 'reapply restored message', descEs: 'reaplicar mensaje restaurado' },
714
+ ];
678
715
  const suggestions = showSuggestions
679
- ? SLASH_COMMANDS.filter(c => ('/' + c.name).startsWith(value.toLowerCase()))
716
+ ? [...SLASH_COMMANDS, ...localSlash].filter(c => ('/' + c.name).startsWith(value.toLowerCase()))
680
717
  : [];
681
718
 
682
719
  useInput((input, key) => {
@@ -766,6 +803,16 @@ function InputBar({ onSubmit, processing, width = 100, draft = '', onDraftChange
766
803
  return;
767
804
  }
768
805
 
806
+ if (key.ctrl && input === 'z') {
807
+ onSubmit('/undo');
808
+ return;
809
+ }
810
+
811
+ if (key.ctrl && input === 'y') {
812
+ onSubmit('/redo');
813
+ return;
814
+ }
815
+
769
816
  if (key.ctrl && input === 'w') {
770
817
  const before = value.slice(0, cursor);
771
818
  const after = value.slice(cursor);
@@ -775,7 +822,7 @@ function InputBar({ onSubmit, processing, width = 100, draft = '', onDraftChange
775
822
  return;
776
823
  }
777
824
 
778
- if (key.backspace || key.delete) {
825
+ if (key.backspace) {
779
826
  if (cursor === 0) return;
780
827
  updateValue(value.slice(0, cursor - 1) + value.slice(cursor));
781
828
  setCursor(c => Math.max(0, c - 1));
@@ -783,8 +830,18 @@ function InputBar({ onSubmit, processing, width = 100, draft = '', onDraftChange
783
830
  return;
784
831
  }
785
832
 
833
+ if (key.delete) {
834
+ if (cursor >= value.length) return;
835
+ updateValue(value.slice(0, cursor) + value.slice(cursor + 1));
836
+ setSuggestIdx(0);
837
+ return;
838
+ }
839
+
786
840
  if (input && !key.ctrl && !key.meta) {
787
- const normalizedInput = input.replace(/\r\n/g, '\n');
841
+ const normalizedInput = input
842
+ .replace(/\u001b\[200~/g, '')
843
+ .replace(/\u001b\[201~/g, '')
844
+ .replace(/\r\n/g, '\n');
788
845
  const safeInput = normalizedInput.includes('\n') ? normalizedInput.replace(/\n/g, ' ') : normalizedInput;
789
846
  updateValue(value.slice(0, cursor) + safeInput + value.slice(cursor));
790
847
  setCursor(c => c + safeInput.length);
@@ -1009,6 +1066,16 @@ export async function startTUI(options = {}) {
1009
1066
  return;
1010
1067
  }
1011
1068
 
1069
+ if (input === '/undo') {
1070
+ store.undoSubmittedMessage();
1071
+ return;
1072
+ }
1073
+
1074
+ if (input === '/redo') {
1075
+ store.redoSubmittedMessage();
1076
+ return;
1077
+ }
1078
+
1012
1079
  if (input.startsWith('/')) {
1013
1080
  const commandName = input.split(' ')[0].slice(1).toLowerCase();
1014
1081
  const lines = [];
@@ -1085,15 +1152,6 @@ export async function startTUI(options = {}) {
1085
1152
  let appInstance = null;
1086
1153
 
1087
1154
  const handleSubmit = async (input, meta = null) => {
1088
- const pasteLen = Number(meta?.length || 0);
1089
- if (pasteLen > 0 || (typeof input === 'string' && input.length > MAX_PASTE_PREVIEW)) {
1090
- const shown = input.length;
1091
- store.addEvent(
1092
- 'warn',
1093
- `[ Pasted Text of ${shown} Characters ]`,
1094
- input.slice(0, MAX_PASTE_PREVIEW) + '...'
1095
- );
1096
- }
1097
1155
  if (input.startsWith('/') && store.processing) {
1098
1156
  await processInput(input);
1099
1157
  return;
@@ -1104,6 +1162,9 @@ export async function startTUI(options = {}) {
1104
1162
  return;
1105
1163
  }
1106
1164
 
1165
+ store.pushSubmittedMessage(input);
1166
+ store.setInputDraft('');
1167
+
1107
1168
  store.processing = true;
1108
1169
  store.turnCount += 1;
1109
1170
  store._emit();