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 +86 -93
- package/package.json +1 -1
- package/src/cli/commands.js +151 -48
- package/src/cli/print.js +4 -2
- package/src/config.js +7 -1
- package/src/core/agent.js +3 -4
- package/src/core/prompts.js +46 -9
- package/src/tools/index.js +140 -121
- package/src/tui/app.mjs +72 -21
- package/src/web/server.js +10 -5
package/src/tui/app.mjs
CHANGED
|
@@ -71,6 +71,8 @@ class UIStore extends EventEmitter {
|
|
|
71
71
|
this.spinner = null;
|
|
72
72
|
this.processing = false;
|
|
73
73
|
this.confirmRequest = null;
|
|
74
|
+
this.lastUserMessage = '';
|
|
75
|
+
this.inputDraft = '';
|
|
74
76
|
this.turnCount = 0;
|
|
75
77
|
this.messageQueue = [];
|
|
76
78
|
this.pendingExit = false;
|
|
@@ -129,6 +131,13 @@ class UIStore extends EventEmitter {
|
|
|
129
131
|
this.addItem({ type: 'event', kind, title, detail: detail || '' });
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
setInputDraft(text) {
|
|
135
|
+
const nextDraft = String(text || '');
|
|
136
|
+
if (nextDraft === this.inputDraft) return;
|
|
137
|
+
this.inputDraft = nextDraft;
|
|
138
|
+
this._emit();
|
|
139
|
+
}
|
|
140
|
+
|
|
132
141
|
requestConfirm(title, detail) {
|
|
133
142
|
return new Promise(resolve => {
|
|
134
143
|
this.confirmRequest = { title, detail, resolve };
|
|
@@ -163,6 +172,11 @@ function stripAnsi(str) {
|
|
|
163
172
|
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
164
173
|
}
|
|
165
174
|
|
|
175
|
+
function shortTextPreview(str, maxLen) {
|
|
176
|
+
const s = String(str || '').replace(/\n/g, ' ').trim();
|
|
177
|
+
return s.length > maxLen ? s.slice(0, maxLen - 3) + '...' : s;
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
function formatElapsed(ms) {
|
|
167
181
|
const s = Math.floor(ms / 1000);
|
|
168
182
|
if (s < 60) return s + 's';
|
|
@@ -527,7 +541,7 @@ function QueuedMessage({ text }) {
|
|
|
527
541
|
);
|
|
528
542
|
}
|
|
529
543
|
|
|
530
|
-
function ConfirmBar({ title, detail }) {
|
|
544
|
+
function ConfirmBar({ title, detail, lastMessage, draft }) {
|
|
531
545
|
const detailLines = (detail || '').split('\n').filter(l => l.trim()).slice(0, 10);
|
|
532
546
|
|
|
533
547
|
return h(Box, { flexDirection: 'column', paddingLeft: 3, marginTop: 1 },
|
|
@@ -549,6 +563,18 @@ function ConfirmBar({ title, detail }) {
|
|
|
549
563
|
),
|
|
550
564
|
)
|
|
551
565
|
: null,
|
|
566
|
+
lastMessage
|
|
567
|
+
? h(Box, { marginTop: 0, paddingLeft: 2 },
|
|
568
|
+
h(Text, { color: T.textGhost }, uiText('Your message: ', 'Tu mensaje: ')),
|
|
569
|
+
h(Text, { color: T.textDim, wrap: 'wrap' }, shortTextPreview(lastMessage, 80)),
|
|
570
|
+
)
|
|
571
|
+
: null,
|
|
572
|
+
draft
|
|
573
|
+
? h(Box, { marginTop: 0, paddingLeft: 2 },
|
|
574
|
+
h(Text, { color: T.textGhost }, uiText('Saved draft: ', 'Borrador guardado: ')),
|
|
575
|
+
h(Text, { color: T.textDim, wrap: 'wrap' }, shortTextPreview(draft, 80)),
|
|
576
|
+
)
|
|
577
|
+
: null,
|
|
552
578
|
h(Box, { marginTop: 0, paddingLeft: 2, gap: 2 },
|
|
553
579
|
h(Text, { color: T.green, bold: true }, '[y]'),
|
|
554
580
|
h(Text, { color: T.textMuted }, uiText('allow', 'permitir')),
|
|
@@ -609,15 +635,29 @@ function StaticItem({ item, width }) {
|
|
|
609
635
|
}
|
|
610
636
|
}
|
|
611
637
|
|
|
612
|
-
function InputBar({ onSubmit, processing, width = 100 }) {
|
|
613
|
-
const [value, setValue] = useState('');
|
|
614
|
-
const [cursor, setCursor] = useState(
|
|
638
|
+
function InputBar({ onSubmit, processing, width = 100, draft = '', onDraftChange }) {
|
|
639
|
+
const [value, setValue] = useState(draft || '');
|
|
640
|
+
const [cursor, setCursor] = useState((draft || '').length);
|
|
615
641
|
const [histIdx, setHistIdx] = useState(-1);
|
|
616
642
|
const [suggestIdx, setSuggestIdx] = useState(0);
|
|
617
643
|
const historyRef = useRef([]);
|
|
618
644
|
const savedRef = useRef('');
|
|
619
645
|
const lastPasteMetaRef = useRef(null);
|
|
620
646
|
|
|
647
|
+
useEffect(() => {
|
|
648
|
+
if (draft !== value) {
|
|
649
|
+
setValue(draft || '');
|
|
650
|
+
setCursor((draft || '').length);
|
|
651
|
+
setHistIdx(-1);
|
|
652
|
+
setSuggestIdx(0);
|
|
653
|
+
}
|
|
654
|
+
}, [draft]);
|
|
655
|
+
|
|
656
|
+
const updateValue = useCallback((next) => {
|
|
657
|
+
setValue(next);
|
|
658
|
+
if (onDraftChange) onDraftChange(next);
|
|
659
|
+
}, [onDraftChange]);
|
|
660
|
+
|
|
621
661
|
const showSuggestions = value.startsWith('/') && !value.includes(' ') && value.length > 0;
|
|
622
662
|
const suggestions = showSuggestions
|
|
623
663
|
? SLASH_COMMANDS.filter(c => ('/' + c.name).startsWith(value.toLowerCase()))
|
|
@@ -634,7 +674,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
634
674
|
}
|
|
635
675
|
historyRef.current.unshift(text);
|
|
636
676
|
if (historyRef.current.length > 100) historyRef.current.pop();
|
|
637
|
-
|
|
677
|
+
updateValue('');
|
|
638
678
|
setCursor(0);
|
|
639
679
|
setHistIdx(-1);
|
|
640
680
|
setSuggestIdx(0);
|
|
@@ -647,7 +687,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
647
687
|
const cmd = suggestions[suggestIdx] || suggestions[0];
|
|
648
688
|
if (cmd) {
|
|
649
689
|
const completed = `/${cmd.name} `;
|
|
650
|
-
|
|
690
|
+
updateValue(completed);
|
|
651
691
|
setCursor(completed.length);
|
|
652
692
|
setSuggestIdx(0);
|
|
653
693
|
}
|
|
@@ -671,7 +711,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
671
711
|
if (histIdx === -1) savedRef.current = value;
|
|
672
712
|
const next = Math.min(histIdx + 1, hist.length - 1);
|
|
673
713
|
setHistIdx(next);
|
|
674
|
-
|
|
714
|
+
updateValue(hist[next]);
|
|
675
715
|
setCursor(hist[next].length);
|
|
676
716
|
return;
|
|
677
717
|
}
|
|
@@ -679,13 +719,13 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
679
719
|
if (key.downArrow) {
|
|
680
720
|
if (histIdx <= 0) {
|
|
681
721
|
setHistIdx(-1);
|
|
682
|
-
|
|
722
|
+
updateValue(savedRef.current);
|
|
683
723
|
setCursor(savedRef.current.length);
|
|
684
724
|
return;
|
|
685
725
|
}
|
|
686
726
|
const next = histIdx - 1;
|
|
687
727
|
setHistIdx(next);
|
|
688
|
-
|
|
728
|
+
updateValue(historyRef.current[next]);
|
|
689
729
|
setCursor(historyRef.current[next].length);
|
|
690
730
|
return;
|
|
691
731
|
}
|
|
@@ -705,7 +745,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
705
745
|
|
|
706
746
|
if (key.ctrl && input === 'u') {
|
|
707
747
|
const after = value.slice(cursor);
|
|
708
|
-
|
|
748
|
+
updateValue(after);
|
|
709
749
|
setCursor(0);
|
|
710
750
|
return;
|
|
711
751
|
}
|
|
@@ -714,14 +754,14 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
714
754
|
const before = value.slice(0, cursor);
|
|
715
755
|
const after = value.slice(cursor);
|
|
716
756
|
const trimmed = before.replace(/\S+\s*$/, '');
|
|
717
|
-
|
|
757
|
+
updateValue(trimmed + after);
|
|
718
758
|
setCursor(trimmed.length);
|
|
719
759
|
return;
|
|
720
760
|
}
|
|
721
761
|
|
|
722
762
|
if (key.backspace || key.delete) {
|
|
723
763
|
if (cursor === 0) return;
|
|
724
|
-
|
|
764
|
+
updateValue(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
725
765
|
setCursor(c => Math.max(0, c - 1));
|
|
726
766
|
setSuggestIdx(0);
|
|
727
767
|
return;
|
|
@@ -730,7 +770,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
730
770
|
if (input && !key.ctrl && !key.meta) {
|
|
731
771
|
const normalizedInput = input.replace(/\r\n/g, '\n');
|
|
732
772
|
const safeInput = normalizedInput.includes('\n') ? normalizedInput.replace(/\n/g, ' ') : normalizedInput;
|
|
733
|
-
|
|
773
|
+
updateValue(value.slice(0, cursor) + safeInput + value.slice(cursor));
|
|
734
774
|
setCursor(c => c + safeInput.length);
|
|
735
775
|
setSuggestIdx(0);
|
|
736
776
|
}
|
|
@@ -790,7 +830,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
|
|
|
790
830
|
h(Text, {
|
|
791
831
|
color: selected ? T.accent : T.textMuted,
|
|
792
832
|
}, `/${cmd.name}`),
|
|
793
|
-
h(Text, { color: T.textGhost }, ` ${cmd.desc}`),
|
|
833
|
+
h(Text, { color: T.textGhost }, ` ${getTuiLang() === 'es' ? (cmd.descEs || cmd.desc) : cmd.desc}`),
|
|
794
834
|
);
|
|
795
835
|
}),
|
|
796
836
|
hasMore && windowStart + maxVisible < suggestions.length
|
|
@@ -877,13 +917,13 @@ function App({ store, state, onSubmit }) {
|
|
|
877
917
|
|
|
878
918
|
if (showConfirm) {
|
|
879
919
|
dynamicArea.push(
|
|
880
|
-
h(ConfirmBar, { key: 'confirm', title: store.confirmRequest.title, detail: store.confirmRequest.detail })
|
|
920
|
+
h(ConfirmBar, { key: 'confirm', title: store.confirmRequest.title, detail: store.confirmRequest.detail, lastMessage: store.lastUserMessage, draft: store.inputDraft })
|
|
881
921
|
);
|
|
882
922
|
}
|
|
883
923
|
|
|
884
924
|
if (showInput) {
|
|
885
925
|
dynamicArea.push(
|
|
886
|
-
h(InputBar, { key: 'input', onSubmit: handleInput, processing: store.processing, width })
|
|
926
|
+
h(InputBar, { key: 'input', onSubmit: handleInput, processing: store.processing, width, draft: store.inputDraft, onDraftChange: (text) => store.setInputDraft(text) })
|
|
887
927
|
);
|
|
888
928
|
}
|
|
889
929
|
|
|
@@ -954,6 +994,7 @@ export async function startTUI(options = {}) {
|
|
|
954
994
|
}
|
|
955
995
|
|
|
956
996
|
if (input.startsWith('/')) {
|
|
997
|
+
const commandName = input.split(' ')[0].slice(1).toLowerCase();
|
|
957
998
|
const lines = [];
|
|
958
999
|
const origLog = console.log;
|
|
959
1000
|
const origError = console.error;
|
|
@@ -972,12 +1013,21 @@ export async function startTUI(options = {}) {
|
|
|
972
1013
|
printSessions: printMod.printSessions,
|
|
973
1014
|
printStatus: printMod.printStatus,
|
|
974
1015
|
};
|
|
975
|
-
|
|
976
|
-
if (
|
|
977
|
-
const
|
|
978
|
-
if (
|
|
1016
|
+
|
|
1017
|
+
if (commandName === 'persona') {
|
|
1018
|
+
const handled = await handleLocalCommand(input, state, deps);
|
|
1019
|
+
if (handled && lines.length > 0) {
|
|
1020
|
+
const clean = lines.filter(l => l.trim()).join('\n');
|
|
1021
|
+
if (clean) store.addItem({ type: 'system', text: clean });
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
const handled = await handleLocalCommand(input, state, deps);
|
|
1025
|
+
if (handled && lines.length > 0) {
|
|
1026
|
+
const clean = lines.filter(l => l.trim()).join('\n');
|
|
1027
|
+
if (clean) store.addItem({ type: 'system', text: clean });
|
|
1028
|
+
}
|
|
1029
|
+
if (!handled) store.addEvent('warn', uiText('command not recognized', 'comando no reconocido'), input);
|
|
979
1030
|
}
|
|
980
|
-
if (!handled) store.addEvent('warn', uiText('command not recognized', 'comando no reconocido'), input);
|
|
981
1031
|
} catch (err) {
|
|
982
1032
|
store.addEvent('error', uiText('error', 'error'), err.message);
|
|
983
1033
|
} finally {
|
|
@@ -989,6 +1039,7 @@ export async function startTUI(options = {}) {
|
|
|
989
1039
|
|
|
990
1040
|
store.addItem({ type: 'divider' });
|
|
991
1041
|
store.addItem({ type: 'user', text: input });
|
|
1042
|
+
store.lastUserMessage = input;
|
|
992
1043
|
|
|
993
1044
|
const origError = console.error;
|
|
994
1045
|
console.error = () => {};
|
package/src/web/server.js
CHANGED
|
@@ -11,7 +11,8 @@ const { runWebAgent } = require('./webAgent');
|
|
|
11
11
|
const { MODELS, DEFAULT_MODEL_KEY, listProvidersFromModels, DEFAULT_LANGUAGE } = require('../config');
|
|
12
12
|
|
|
13
13
|
const app = express();
|
|
14
|
-
const
|
|
14
|
+
const HOST = process.env.HOST || process.env.ZYN_WEB_HOST || '127.0.0.1';
|
|
15
|
+
const PORT = Number(process.env.PORT || process.env.ZYN_WEB_PORT || 3000);
|
|
15
16
|
|
|
16
17
|
// Evitar crashes silenciosos
|
|
17
18
|
process.on('uncaughtException', (err) => {
|
|
@@ -31,7 +32,7 @@ app.use((req, res, next) => {
|
|
|
31
32
|
next();
|
|
32
33
|
});
|
|
33
34
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
34
|
-
// Persistir secreto de
|
|
35
|
+
// Persistir secreto de sesion en disco
|
|
35
36
|
const SECRET_FILE = path.join(__dirname, 'data', '.session-secret');
|
|
36
37
|
let sessionSecret;
|
|
37
38
|
try {
|
|
@@ -42,9 +43,13 @@ try {
|
|
|
42
43
|
fs.writeFileSync(SECRET_FILE, sessionSecret);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Asegurar que el directorio de sesiones existe
|
|
47
|
+
const SESSION_DIR = path.join(__dirname, 'data', 'sessions');
|
|
48
|
+
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
|
49
|
+
|
|
45
50
|
app.use(session({
|
|
46
51
|
store: new FileStore({
|
|
47
|
-
path:
|
|
52
|
+
path: SESSION_DIR,
|
|
48
53
|
ttl: 30 * 24 * 60 * 60,
|
|
49
54
|
retries: 0,
|
|
50
55
|
logFn: () => {},
|
|
@@ -294,6 +299,6 @@ app.get('/{*splat}', (req, res) => {
|
|
|
294
299
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
295
300
|
});
|
|
296
301
|
|
|
297
|
-
app.listen(PORT, () => {
|
|
298
|
-
console.log(`\n
|
|
302
|
+
app.listen(PORT, HOST, () => {
|
|
303
|
+
console.log(`\n \u25cf Zyn Web \u2192 http://${HOST}:${PORT}\n`);
|
|
299
304
|
});
|