wormclaude 1.0.38 → 1.0.40
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 +115 -27
- package/package.json +1 -1
package/dist/theme.js
CHANGED
package/dist/tui.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import readline from 'node:readline';
|
|
7
7
|
import stringWidth from 'string-width';
|
|
8
8
|
import { loadConfig, streamChat, fetchAccount } from './api.js';
|
|
9
|
+
import { allToolSchemas, executeToolCalls, toolLabel, setToolConfig } from './tools.js';
|
|
10
|
+
import { sanitizeOutput } from './errorsan.js';
|
|
9
11
|
import { itemAnsi } from './ansi.js';
|
|
10
12
|
import { theme, VERSION } from './theme.js';
|
|
11
13
|
import { cleanModelText } from './textclean.js';
|
|
@@ -39,11 +41,22 @@ const vis = (s) => stringWidth(s.replace(/\x1b\[[0-9;]*m/g, ''));
|
|
|
39
41
|
export async function runTui() {
|
|
40
42
|
const config = loadConfig();
|
|
41
43
|
let account = { plan: '', email: '', name: '' };
|
|
42
|
-
|
|
44
|
+
// Ortam bağlamı (Windows/cwd) — model DOĞRU yol/komut kullansın (yoksa /home/user gibi yanlış yol yazar).
|
|
45
|
+
const _plat = process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'macOS' : 'Linux';
|
|
46
|
+
const _winNote = process.platform === 'win32'
|
|
47
|
+
? ' This is WINDOWS. Write files to the CURRENT directory (relative paths) or under C:\\Users\\... — NEVER use /home/user or other Unix paths. The Bash tool runs via cmd.exe (use Windows commands).'
|
|
48
|
+
: ' Use POSIX paths.';
|
|
49
|
+
const history = [
|
|
50
|
+
{ role: 'system', content: `ENVIRONMENT: The user machine runs ${_plat}. Current working directory: ${process.cwd()}.${_winNote}` },
|
|
51
|
+
];
|
|
43
52
|
const displayItems = [{ kind: 'banner' }];
|
|
44
53
|
let inputBuf = '', busy = false, streamChars = 0, spin = 0;
|
|
45
54
|
const SPIN = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
|
|
46
|
-
const FOOTER_H = 4; // kutu (üst çizgi +
|
|
55
|
+
const FOOTER_H = 4; // kutu (üst çizgi + giriş/dialog + alt çizgi) + ipucu satırı
|
|
56
|
+
setToolConfig(config); // araçlar (Write/Bash/Edit…) aynı config'i kullansın
|
|
57
|
+
// İzin (araç onayı) durumu
|
|
58
|
+
let perm = null;
|
|
59
|
+
const allowAll = new Set(); // "Tümüne izin" denen araçlar (oturum boyu)
|
|
47
60
|
// ── Claude tarzı başlık (model/plan/mail/cwd) — responsive ──
|
|
48
61
|
function headerLines(W) {
|
|
49
62
|
const a = paint(' WormClaude ', theme.greyDim) + paint('v' + VERSION, theme.red, true)
|
|
@@ -61,18 +74,28 @@ export async function runTui() {
|
|
|
61
74
|
function drawFooter() {
|
|
62
75
|
const W = Math.max(8, cols());
|
|
63
76
|
const start = rows() - FOOTER_H + 1;
|
|
64
|
-
let shown = inputBuf;
|
|
65
|
-
const avail = W - 3;
|
|
66
|
-
if (vis(shown) > avail)
|
|
67
|
-
shown = '…' + shown.slice(-(avail - 1));
|
|
68
|
-
// İki uzun KIRMIZI çizgi arasında giriş (köşesiz → resize'da bozulmaz). Altında ipucu.
|
|
69
77
|
const line = paint('─'.repeat(W), theme.red);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
let body;
|
|
79
|
+
if (perm) {
|
|
80
|
+
// İzin dialogu: araç adı + Evet/Tümü/Hayır
|
|
81
|
+
const q = paint(' İZİN ', theme.redBright, true) + paint(perm.label, theme.white) + paint(' çalıştırılsın mı?', theme.grey);
|
|
82
|
+
const opts = paint(' [E] Evet', theme.redBright, true) + paint(' · ', theme.greyDim)
|
|
83
|
+
+ paint('[T] Tümüne izin', theme.grey) + paint(' · ', theme.greyDim) + paint('[H] Hayır', theme.grey);
|
|
84
|
+
body = [line, q, opts, line];
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
let shown = inputBuf;
|
|
88
|
+
const avail = W - 3;
|
|
89
|
+
if (vis(shown) > avail)
|
|
90
|
+
shown = '…' + shown.slice(-(avail - 1));
|
|
91
|
+
const inputLine = paint('❯ ', theme.redBright, true) + paint(shown, theme.white) + paint('▌', theme.greyDim);
|
|
92
|
+
const hint = busy
|
|
93
|
+
? paint(`${SPIN[spin % SPIN.length]} çalışıyor… ${streamChars ? streamChars + ' karakter' : ''}`, theme.grey)
|
|
94
|
+
: paint(' /kopyala panoya · /help komutlar · Ctrl+C çıkış', theme.greyDim);
|
|
95
|
+
body = [line, inputLine, line, hint];
|
|
96
|
+
}
|
|
74
97
|
let out = '\x1b7'; // imleci kaydet
|
|
75
|
-
|
|
98
|
+
body.forEach((l, i) => { out += `\x1b[${start + i};1H\x1b[2K` + fit(l, W); });
|
|
76
99
|
out += '\x1b8'; // imleci geri yükle (içerik alanı)
|
|
77
100
|
process.stdout.write(out);
|
|
78
101
|
}
|
|
@@ -90,34 +113,71 @@ export async function runTui() {
|
|
|
90
113
|
}
|
|
91
114
|
// İçerik öğesini ekle + içerik alanına bas (taşarsa scrollback'e → kopyalanır). Footer sabit kalır.
|
|
92
115
|
const printItem = (it) => { displayItems.push(it); process.stdout.write(itemAnsi(it, cols()) + '\n'); drawFooter(); };
|
|
93
|
-
// ──
|
|
116
|
+
// ── Agent döngüsü (M2: araçlar + izin). Model tool çağırırsa izin sorulup çalıştırılır,
|
|
117
|
+
// sonuç geçmişe eklenip döngü devam eder; tool yoksa biter. ──
|
|
94
118
|
async function runTurn(userText) {
|
|
95
119
|
busy = true;
|
|
96
120
|
streamChars = 0;
|
|
97
121
|
history.push({ role: 'user', content: userText });
|
|
98
|
-
const timer = setInterval(() => { spin++; if (busy)
|
|
122
|
+
const timer = setInterval(() => { spin++; if (busy && !perm)
|
|
99
123
|
drawFooter(); }, 120);
|
|
100
|
-
let
|
|
124
|
+
let lastSig = '', sameCount = 0;
|
|
101
125
|
try {
|
|
102
|
-
for
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
for (let iter = 0; iter < 25; iter++) {
|
|
127
|
+
streamChars = 0;
|
|
128
|
+
let answer = '';
|
|
129
|
+
let toolCalls = [];
|
|
130
|
+
for await (const ev of streamChat(history, allToolSchemas(), config)) {
|
|
131
|
+
if (ev.type === 'text') {
|
|
132
|
+
answer += ev.text;
|
|
133
|
+
streamChars = answer.length;
|
|
134
|
+
drawFooter();
|
|
135
|
+
}
|
|
136
|
+
else if (ev.type === 'done')
|
|
137
|
+
toolCalls = ev.toolCalls || [];
|
|
138
|
+
else if (ev.type === 'error')
|
|
139
|
+
answer += `\n[hata: ${ev.error}]`;
|
|
140
|
+
}
|
|
141
|
+
answer = cleanModelText(answer).trim();
|
|
142
|
+
const am = { role: 'assistant', content: answer || '' };
|
|
143
|
+
if (toolCalls.length)
|
|
144
|
+
am.tool_calls = toolCalls.map((t) => ({ id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' } }));
|
|
145
|
+
history.push(am);
|
|
146
|
+
if (answer)
|
|
147
|
+
printItem({ kind: 'assistant', text: answer });
|
|
148
|
+
if (!toolCalls.length)
|
|
149
|
+
break;
|
|
150
|
+
// takılma koruması: aynı araç-çağrısı 3 kez üst üste → dur
|
|
151
|
+
const sig = JSON.stringify(toolCalls.map((tc) => tc.name + ':' + (tc.args || '')));
|
|
152
|
+
if (sig === lastSig)
|
|
153
|
+
sameCount++;
|
|
154
|
+
else {
|
|
155
|
+
sameCount = 0;
|
|
156
|
+
lastSig = sig;
|
|
157
|
+
}
|
|
158
|
+
if (sameCount >= 2) {
|
|
159
|
+
printItem({ kind: 'note', text: 'Aynı adım tekrarlandı, döngü önlemek için durduruldu.' });
|
|
160
|
+
break;
|
|
106
161
|
}
|
|
107
|
-
|
|
108
|
-
|
|
162
|
+
const results = await executeToolCalls(toolCalls, {
|
|
163
|
+
confirm: (c, args) => new Promise((resolve) => {
|
|
164
|
+
if (allowAll.has(c.name))
|
|
165
|
+
return resolve('allow');
|
|
166
|
+
perm = { label: toolLabel(c.name, args), name: c.name, resolve };
|
|
167
|
+
drawFooter();
|
|
168
|
+
}),
|
|
169
|
+
onResult: (c, _i, res) => printItem({ kind: 'tool', label: toolLabel(c.name, res.args), result: sanitizeOutput(res.output), ok: res.ok }),
|
|
170
|
+
});
|
|
171
|
+
for (let i = 0; i < toolCalls.length; i++)
|
|
172
|
+
history.push({ role: 'tool', tool_call_id: toolCalls[i].id, content: (results[i].output || '').slice(0, 8000) });
|
|
109
173
|
}
|
|
110
174
|
}
|
|
111
175
|
catch (e) {
|
|
112
|
-
|
|
176
|
+
printItem({ kind: 'note', text: `[bağlantı hatası: ${e?.message || e}]` });
|
|
113
177
|
}
|
|
114
178
|
clearInterval(timer);
|
|
115
|
-
answer = cleanModelText(answer).trim();
|
|
116
179
|
busy = false;
|
|
117
|
-
|
|
118
|
-
history.push({ role: 'assistant', content: answer });
|
|
119
|
-
printItem({ kind: 'assistant', text: answer });
|
|
120
|
-
}
|
|
180
|
+
perm = null;
|
|
121
181
|
drawFooter();
|
|
122
182
|
}
|
|
123
183
|
// ── Kurulum ──
|
|
@@ -141,6 +201,34 @@ export async function runTui() {
|
|
|
141
201
|
catch { } });
|
|
142
202
|
let ctrlcAt = 0;
|
|
143
203
|
process.stdin.on('keypress', (str, key) => {
|
|
204
|
+
// İzin dialogu aktifse: E=Evet, T=Tümüne izin, H/Esc=Hayır (Ctrl+C de Hayır)
|
|
205
|
+
if (perm) {
|
|
206
|
+
const k = (str || '').toLowerCase();
|
|
207
|
+
const r = perm.resolve, nm = perm.name;
|
|
208
|
+
if (key && key.ctrl && key.name === 'c') {
|
|
209
|
+
perm = null;
|
|
210
|
+
r({ deny: 'kullanıcı iptal etti' });
|
|
211
|
+
drawFooter();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (k === 'e' || k === '1' || (key && key.name === 'return')) {
|
|
215
|
+
perm = null;
|
|
216
|
+
r('allow');
|
|
217
|
+
drawFooter();
|
|
218
|
+
}
|
|
219
|
+
else if (k === 't' || k === '2') {
|
|
220
|
+
allowAll.add(nm);
|
|
221
|
+
perm = null;
|
|
222
|
+
r('allow');
|
|
223
|
+
drawFooter();
|
|
224
|
+
}
|
|
225
|
+
else if (k === 'h' || k === '3' || (key && key.name === 'escape')) {
|
|
226
|
+
perm = null;
|
|
227
|
+
r({ deny: '' });
|
|
228
|
+
drawFooter();
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
144
232
|
if (key && key.ctrl && key.name === 'c') {
|
|
145
233
|
if (inputBuf) {
|
|
146
234
|
inputBuf = '';
|