wormclaude 1.0.50 → 1.0.52
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/dist/theme.js +1 -1
- package/dist/tui.js +83 -29
- package/package.json +1 -1
package/dist/theme.js
CHANGED
package/dist/tui.js
CHANGED
|
@@ -64,6 +64,8 @@ export async function runTui() {
|
|
|
64
64
|
];
|
|
65
65
|
const displayItems = [{ kind: 'banner' }];
|
|
66
66
|
let inputBuf = '', busy = false, streamChars = 0, spin = 0;
|
|
67
|
+
let streamPreview = ''; // canlı akış önizlemesi (footer'da)
|
|
68
|
+
const PREVIEW_LINES = 6; // akışta sabit önizleme satırı (yükseklik stabil → titreme yok)
|
|
67
69
|
const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
|
|
68
70
|
// Giriş kutusunu genişliğe göre ÇOK SATIRA sar (uzun mesaj/kod sağa taşmaz, alta iner).
|
|
69
71
|
const MAX_INPUT_LINES = 10;
|
|
@@ -84,22 +86,15 @@ export async function runTui() {
|
|
|
84
86
|
return shown.map((ln, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(ln, theme.white) + (i === shown.length - 1 ? paint('▌', theme.greyDim) : ''));
|
|
85
87
|
};
|
|
86
88
|
// Footer yüksekliği DİNAMİK: izin=4; değilse (durum/menü) + çizgi + giriş-satırları + çizgi.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const statusLines = m.length ? Math.min(8, m.length) : 1;
|
|
93
|
-
return statusLines + 2 + inputBoxLines(Math.max(8, cols())).length;
|
|
94
|
-
};
|
|
95
|
-
// Yükseklik değiştiyse scroll-region'ı + içeriği yeniden bas; değişmediyse sadece footer'ı çiz.
|
|
96
|
-
const refresh = () => { const fh = footerHeight(); if (fh !== prevFH)
|
|
97
|
-
redrawAll();
|
|
98
|
-
else
|
|
99
|
-
drawFooter(); };
|
|
89
|
+
// SABİT rezerve footer alanı (scroll region bir kez ayarlanır → churn/duplicate yok). Footer bu
|
|
90
|
+
// alanın DİBİNE sabitlenir; küçükse üstü boşaltılır. Menü/önizleme/giriş bu sınıra göre kırpılır.
|
|
91
|
+
const FOOTER_MAX = () => Math.min(11, Math.max(5, rows() - 6));
|
|
92
|
+
// refresh = sadece footer'ı çiz (region sabit; içerik yeniden BASILMAZ → duplicate olmaz).
|
|
93
|
+
const refresh = () => drawFooter();
|
|
100
94
|
setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
|
|
101
|
-
// İzin (araç onayı) durumu
|
|
95
|
+
// İzin (araç onayı) + soru (AskUserQuestion) durumu
|
|
102
96
|
let perm = null;
|
|
97
|
+
let ask = null;
|
|
103
98
|
const allowAll = new Set(); // "Tümüne izin" denen araçlar (oturum boyu)
|
|
104
99
|
const inputHistory = []; // gönderilen mesajlar (↑/↓ ile geri çağrılır)
|
|
105
100
|
let histIdx = -1; // -1 = geçmişte gezinmiyor
|
|
@@ -163,16 +158,15 @@ export async function runTui() {
|
|
|
163
158
|
return [...L, ...R].map((r) => fit(cell(r), W)); // dar ekran → alt alta
|
|
164
159
|
return [0, 1, 2].map((i) => fit(padVis(cell(L[i]), colW) + cell(R[i]), W)); // geniş → 2 sütun
|
|
165
160
|
}
|
|
166
|
-
// ── Scroll region: üst
|
|
161
|
+
// ── Scroll region: üst=içerik, alt=SABİT rezerve footer alanı. İmleci koru (DECSTBM home'a alır). ──
|
|
167
162
|
function setRegion() {
|
|
168
|
-
const bottom = Math.max(1, rows() -
|
|
169
|
-
process.stdout.write(`\x1b[1;${bottom}r`);
|
|
163
|
+
const bottom = Math.max(1, rows() - FOOTER_MAX() - 1);
|
|
164
|
+
process.stdout.write(`\x1b7\x1b[1;${bottom}r\x1b8`); // save · region · restore (içerik imleci bozulmaz)
|
|
170
165
|
}
|
|
171
|
-
// ──
|
|
166
|
+
// ── Footer'ı rezerve alanın DİBİNE çiz; rezerve alanın geri kalanını boşalt (eski menü kalmaz). ──
|
|
172
167
|
function drawFooter() {
|
|
173
168
|
const W = Math.max(8, cols());
|
|
174
|
-
const
|
|
175
|
-
const start = rows() - fh + 1;
|
|
169
|
+
const R = rows(), FM = FOOTER_MAX();
|
|
176
170
|
const line = paint('─'.repeat(W), theme.red);
|
|
177
171
|
let body;
|
|
178
172
|
if (perm) {
|
|
@@ -182,10 +176,25 @@ export async function runTui() {
|
|
|
182
176
|
+ paint('[T] Tümüne izin', theme.grey) + paint(' · ', theme.greyDim) + paint('[H] Hayır', theme.grey);
|
|
183
177
|
body = [line, q, opts, line];
|
|
184
178
|
}
|
|
179
|
+
else if (ask) {
|
|
180
|
+
// Soru dialogu (AskUserQuestion): soru + numaralı seçenekler
|
|
181
|
+
const q = paint(' ? ', theme.redBright, true) + paint(ask.question, theme.white);
|
|
182
|
+
const opts = ask.options.map((o, i) => ' ' + paint((i === ask.sel ? '✶ ' : ' ') + (i + 1) + '. ' + o.label, i === ask.sel ? theme.redBright : theme.grey) + (o.description ? paint(' ' + o.description, theme.greyDim) : ''));
|
|
183
|
+
body = [line, q, ...opts, line];
|
|
184
|
+
}
|
|
185
185
|
else {
|
|
186
186
|
const inputLines = inputBoxLines(W); // çok-satırlı giriş (sarılmış)
|
|
187
187
|
const m = cmdMatches();
|
|
188
|
-
if (
|
|
188
|
+
if (busy && streamPreview) {
|
|
189
|
+
// CANLI akış önizlemesi (son PREVIEW_LINES satır, sabit yükseklik)
|
|
190
|
+
const pl = streamPreview.split('\n');
|
|
191
|
+
const tail = pl.slice(-PREVIEW_LINES);
|
|
192
|
+
while (tail.length < PREVIEW_LINES)
|
|
193
|
+
tail.push('');
|
|
194
|
+
const prev = tail.map((l, i) => (i === 0 ? paint('✶ ', theme.redBright, true) : ' ') + paint(l, theme.white));
|
|
195
|
+
body = [...prev, line, ...inputLines, line];
|
|
196
|
+
}
|
|
197
|
+
else if (m.length) {
|
|
189
198
|
// DİKEY slash menü — KAYAN pencere (8 satır, seçimi takip eder), seçili kırmızı.
|
|
190
199
|
const sel = Math.min(cmdSel, m.length - 1);
|
|
191
200
|
const WIN = 8;
|
|
@@ -204,14 +213,19 @@ export async function runTui() {
|
|
|
204
213
|
body = [status, line, ...inputLines, line];
|
|
205
214
|
}
|
|
206
215
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
216
|
+
if (body.length > FM)
|
|
217
|
+
body = body.slice(body.length - FM); // rezerve alanı aşma (giriş hep görünür)
|
|
218
|
+
const bodyStart = R - body.length + 1; // footer'ı ekranın DİBİNE sabitle
|
|
219
|
+
const reservedTop = R - FM + 1;
|
|
220
|
+
let out = '\x1b7'; // imleci kaydet (içerik alanı)
|
|
221
|
+
for (let r = reservedTop; r < bodyStart; r++)
|
|
222
|
+
out += `\x1b[${r};1H\x1b[2K`; // üst rezerve satırları boşalt
|
|
223
|
+
body.forEach((l, i) => { out += `\x1b[${bodyStart + i};1H\x1b[2K` + fit(l, W); });
|
|
224
|
+
out += '\x1b8'; // imleci geri yükle
|
|
210
225
|
process.stdout.write(out);
|
|
211
226
|
}
|
|
212
|
-
// ── Her şeyi yeni boyutta yeniden bas (
|
|
227
|
+
// ── Her şeyi yeni boyutta yeniden bas (SADECE açılış/resize/clear; footer değişiminde DEĞİL). ──
|
|
213
228
|
function redrawAll() {
|
|
214
|
-
prevFH = footerHeight(); // mevcut footer yüksekliğini kaydet (refresh karşılaştırması için)
|
|
215
229
|
setRegion();
|
|
216
230
|
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
217
231
|
const W = cols();
|
|
@@ -229,15 +243,17 @@ export async function runTui() {
|
|
|
229
243
|
async function runTurn(userText, displayText) {
|
|
230
244
|
busy = true;
|
|
231
245
|
streamChars = 0;
|
|
246
|
+
streamPreview = '';
|
|
232
247
|
if (displayText !== undefined)
|
|
233
248
|
printItem({ kind: 'user', text: displayText }); // skill/@mention: gösterim farklı
|
|
234
249
|
history.push({ role: 'user', content: userText });
|
|
235
|
-
const timer = setInterval(() => { spin++; if (busy && !perm)
|
|
250
|
+
const timer = setInterval(() => { spin++; if (busy && !perm && !ask)
|
|
236
251
|
refresh(); }, 120);
|
|
237
252
|
let lastSig = '', sameCount = 0, usedWeb = false, lastAnswer = '';
|
|
238
253
|
try {
|
|
239
254
|
for (let iter = 0; iter < 25; iter++) {
|
|
240
255
|
streamChars = 0;
|
|
256
|
+
streamPreview = '';
|
|
241
257
|
// Oto-compact: bağlam eşiği aşılınca otomatik özetle (uzun sohbet patlamasın)
|
|
242
258
|
if (shouldAutoCompact(history)) {
|
|
243
259
|
try {
|
|
@@ -251,11 +267,11 @@ export async function runTui() {
|
|
|
251
267
|
let answer = '';
|
|
252
268
|
let toolCalls = [];
|
|
253
269
|
for await (const ev of streamChat(history, allToolSchemas(), config)) {
|
|
254
|
-
//
|
|
255
|
-
// streamChars güncellenir; footer'ı 120ms'lik spinTimer çizer; yazma anında keypress çizer.
|
|
270
|
+
// CANLI akış: answer biriktir + streamPreview güncelle. drawFooter'ı 120ms timer çizer (titreme yok).
|
|
256
271
|
if (ev.type === 'text') {
|
|
257
272
|
answer += ev.text;
|
|
258
273
|
streamChars = answer.length;
|
|
274
|
+
streamPreview = cleanModelText(answer);
|
|
259
275
|
}
|
|
260
276
|
else if (ev.type === 'done')
|
|
261
277
|
toolCalls = ev.toolCalls || [];
|
|
@@ -263,6 +279,7 @@ export async function runTui() {
|
|
|
263
279
|
answer += `\n[hata: ${ev.error}]`;
|
|
264
280
|
}
|
|
265
281
|
answer = cleanModelText(answer).trim();
|
|
282
|
+
streamPreview = ''; // canlı önizlemeyi kapat → footer küçülür, formatlı cevap içeriğe basılır
|
|
266
283
|
const am = { role: 'assistant', content: answer || '' };
|
|
267
284
|
if (toolCalls.length)
|
|
268
285
|
am.tool_calls = toolCalls.map((t) => ({ id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' } }));
|
|
@@ -292,6 +309,7 @@ export async function runTui() {
|
|
|
292
309
|
perm = { label: toolLabel(c.name, args), name: c.name, resolve };
|
|
293
310
|
refresh();
|
|
294
311
|
}),
|
|
312
|
+
ask: (q) => new Promise((resolve) => { ask = { question: q.question, options: q.options.length ? q.options : [{ label: 'Tamam' }], sel: 0, resolve }; refresh(); }),
|
|
295
313
|
onResult: (c, _i, res) => { if ((c.name === 'WebSearch' || c.name === 'WebFetch') && res.ok)
|
|
296
314
|
usedWeb = true; printItem({ kind: 'tool', label: toolLabel(c.name, res.args), result: sanitizeOutput(res.output), ok: res.ok }); },
|
|
297
315
|
});
|
|
@@ -305,6 +323,8 @@ export async function runTui() {
|
|
|
305
323
|
clearInterval(timer);
|
|
306
324
|
busy = false;
|
|
307
325
|
perm = null;
|
|
326
|
+
ask = null;
|
|
327
|
+
streamPreview = '';
|
|
308
328
|
refresh();
|
|
309
329
|
// Web-öğrenme: web'de arayıp cevap ürettiyse {soru→cevap}'ı eğitim datasına ekle
|
|
310
330
|
if (usedWeb && lastAnswer) {
|
|
@@ -339,6 +359,40 @@ export async function runTui() {
|
|
|
339
359
|
catch { } });
|
|
340
360
|
let ctrlcAt = 0;
|
|
341
361
|
process.stdin.on('keypress', (str, key) => {
|
|
362
|
+
// Soru dialogu (AskUserQuestion) aktifse: ↑↓ / 1-9 seç, Enter onayla, Esc/Ctrl+C ilk seçenek
|
|
363
|
+
if (ask) {
|
|
364
|
+
const n = ask.options.length;
|
|
365
|
+
if (key && key.name === 'up') {
|
|
366
|
+
ask.sel = (ask.sel - 1 + n) % n;
|
|
367
|
+
refresh();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (key && key.name === 'down') {
|
|
371
|
+
ask.sel = (ask.sel + 1) % n;
|
|
372
|
+
refresh();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (str && /^[1-9]$/.test(str) && +str <= n) {
|
|
376
|
+
ask.sel = +str - 1;
|
|
377
|
+
refresh();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (key && key.name === 'return') {
|
|
381
|
+
const r = ask.resolve, v = ask.options[ask.sel]?.label || '';
|
|
382
|
+
ask = null;
|
|
383
|
+
r(v);
|
|
384
|
+
refresh();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (key && (key.name === 'escape' || (key.ctrl && key.name === 'c'))) {
|
|
388
|
+
const r = ask.resolve, v = ask.options[0]?.label || '';
|
|
389
|
+
ask = null;
|
|
390
|
+
r(v);
|
|
391
|
+
refresh();
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
342
396
|
// İzin dialogu aktifse: E=Evet, T=Tümüne izin, H/Esc=Hayır (Ctrl+C de Hayır)
|
|
343
397
|
if (perm) {
|
|
344
398
|
const k = (str || '').toLowerCase();
|