wormclaude 1.0.78 → 1.0.80

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/api.js CHANGED
@@ -120,6 +120,30 @@ export async function* streamChat(messages, tools, config, signal) {
120
120
  };
121
121
  if (tools && tools.length)
122
122
  body.tools = tools;
123
+ // ── Zaman aşımı koruması ──────────────────────────────────────────────────
124
+ // Node fetch (undici) varsayılan olarak header ve chunk'lar için SONSUZA kadar
125
+ // bekler. Bayat keep-alive soketi / ağ takılmasında akış ebediyen asılı kalır
126
+ // ("mesaj gönderdim, yanıt gelmiyor"). Header ve chunk'lar-arası (idle) zaman
127
+ // aşımı ekliyoruz; kullanıcının abort sinyaliyle BİRLEŞİR.
128
+ const HEADERS_MS = Number(process.env.WORMCLAUDE_HEADERS_TIMEOUT_MS) || 45_000;
129
+ const IDLE_MS = Number(process.env.WORMCLAUDE_IDLE_TIMEOUT_MS) || 60_000;
130
+ const ac = new AbortController();
131
+ let timedOut = false;
132
+ const onUserAbort = () => ac.abort();
133
+ if (signal) {
134
+ if (signal.aborted)
135
+ ac.abort();
136
+ else
137
+ signal.addEventListener('abort', onUserAbort, { once: true });
138
+ }
139
+ let timer = setTimeout(() => { timedOut = true; ac.abort(); }, HEADERS_MS);
140
+ const armIdle = () => { if (timer)
141
+ clearTimeout(timer); timer = setTimeout(() => { timedOut = true; ac.abort(); }, IDLE_MS); };
142
+ const clearTimer = () => { if (timer) {
143
+ clearTimeout(timer);
144
+ timer = null;
145
+ } if (signal)
146
+ signal.removeEventListener('abort', onUserAbort); };
123
147
  let res;
124
148
  try {
125
149
  res = await fetchWithRetry(`${config.baseUrl}/chat/completions`, {
@@ -130,10 +154,15 @@ export async function* streamChat(messages, tools, config, signal) {
130
154
  ...(getTrace() ? { 'X-WC-Trace': getTrace(), 'X-WC-Session': getSession() } : {}),
131
155
  },
132
156
  body: JSON.stringify(body),
133
- signal,
157
+ signal: ac.signal,
134
158
  });
135
159
  }
136
160
  catch (e) {
161
+ clearTimer();
162
+ if (timedOut) {
163
+ yield { type: 'error', error: `Yanıt zaman aşımı — ${Math.round(HEADERS_MS / 1000)}sn içinde sunucudan yanıt gelmedi. Tekrar dene.` };
164
+ return;
165
+ }
137
166
  if (signal?.aborted || e?.name === 'AbortError') {
138
167
  yield { type: 'done', toolCalls: [] };
139
168
  return;
@@ -142,6 +171,7 @@ export async function* streamChat(messages, tools, config, signal) {
142
171
  return;
143
172
  }
144
173
  if (!res.ok || !res.body) {
174
+ clearTimer();
145
175
  let detail = '';
146
176
  try {
147
177
  detail = await res.text();
@@ -150,13 +180,14 @@ export async function* streamChat(messages, tools, config, signal) {
150
180
  yield { type: 'error', error: `HTTP ${res.status}: ${detail.slice(0, 300)}` };
151
181
  return;
152
182
  }
183
+ armIdle(); // header geldi → chunk'lar-arası idle zaman aşımına geç
153
184
  const reader = res.body.getReader();
154
185
  const decoder = new TextDecoder();
155
186
  let buf = '';
156
187
  const toolParser = new StreamingToolCallParser();
157
188
  let usage;
158
189
  while (true) {
159
- if (signal?.aborted) {
190
+ if (ac.signal.aborted && !timedOut) {
160
191
  try {
161
192
  await reader.cancel();
162
193
  }
@@ -168,10 +199,16 @@ export async function* streamChat(messages, tools, config, signal) {
168
199
  chunk = await reader.read();
169
200
  }
170
201
  catch (e) {
202
+ clearTimer();
203
+ if (timedOut) {
204
+ yield { type: 'error', error: `Bağlantı durdu — ${Math.round(IDLE_MS / 1000)}sn yeni veri gelmedi. Tekrar dene.` };
205
+ return;
206
+ }
171
207
  if (signal?.aborted || e?.name === 'AbortError')
172
208
  break;
173
209
  throw e;
174
210
  }
211
+ armIdle(); // her chunk → idle sayacını sıfırla
175
212
  const { done, value } = chunk;
176
213
  if (done)
177
214
  break;
@@ -212,6 +249,7 @@ export async function* streamChat(messages, tools, config, signal) {
212
249
  }
213
250
  }
214
251
  }
252
+ clearTimer(); // akış normal bitti → zamanlayıcı + dinleyici temizle
215
253
  // Tamamlanan çağrıları sağlam biçimde topla; args'ı GEÇERLİ JSON string olarak emit et
216
254
  // (tools.ts tarafındaki JSON.parse artık asla patlamaz — onarım burada yapıldı).
217
255
  const toolCalls = toolParser.getCompleted().map((c) => ({
package/dist/safejson.js CHANGED
@@ -104,9 +104,58 @@ function closeOpenStructures(text) {
104
104
  out += stack.pop();
105
105
  return out;
106
106
  }
107
+ /**
108
+ * String DEĞERLERİ içindeki kaçırılmamış çift tırnakları kaçırır.
109
+ * Qwen sık sık shell komutlarını tırnaksız-kaçışla üretir:
110
+ * {"command":"curl "http://x?q=<script>alert('XSS')</script>""}
111
+ * Bu geçerli JSON değil (ikinci " string'i erken kapatır). Bir string içindeyken bir " görünce
112
+ * ileriye bakarız: boşluk atlandıktan sonra gelen anlamlı karakter `:` ise (anahtar kapanışı),
113
+ * `,}]` ise (değer kapanışı) → gerçek kapanış; aksi halde bu İÇ bir tırnaktır → `\"` ile kaçırılır.
114
+ * JSON-iskeletinde anahtar her zaman `:`, değer her zaman `,}]` ile takip edildiğinden güvenlidir.
115
+ */
116
+ export function escapeStrayQuotes(text) {
117
+ let out = '';
118
+ let inString = false;
119
+ let escape = false;
120
+ for (let i = 0; i < text.length; i++) {
121
+ const ch = text[i];
122
+ if (!inString) {
123
+ out += ch;
124
+ if (ch === '"')
125
+ inString = true;
126
+ continue;
127
+ }
128
+ if (escape) {
129
+ out += ch;
130
+ escape = false;
131
+ continue;
132
+ }
133
+ if (ch === '\\') {
134
+ out += ch;
135
+ escape = true;
136
+ continue;
137
+ }
138
+ if (ch === '"') {
139
+ let j = i + 1;
140
+ while (j < text.length && /\s/.test(text[j]))
141
+ j++;
142
+ const next = text[j];
143
+ if (next === undefined || next === ':' || next === ',' || next === '}' || next === ']') {
144
+ out += ch;
145
+ inString = false; // gerçek kapanış
146
+ }
147
+ else {
148
+ out += '\\"'; // iç tırnak → kaçır
149
+ }
150
+ continue;
151
+ }
152
+ out += ch;
153
+ }
154
+ return out;
155
+ }
107
156
  /**
108
157
  * Bozuk/yarım JSON'u en iyi çabayla parse eder. Sırayla: düz parse → fixBooleanCasing →
109
- * trailing-comma temizliği → açık yapıları kapatma. Hepsi başarısızsa fallback döner.
158
+ * trailing-comma temizliği → iç-tırnak kaçırma → açık yapıları kapatma. Hepsi başarısızsa fallback.
110
159
  */
111
160
  export function safeJsonParse(text, fallback) {
112
161
  if (text == null)
@@ -131,13 +180,19 @@ export function safeJsonParse(text, fallback) {
131
180
  return JSON.parse(repaired);
132
181
  }
133
182
  catch { }
134
- // 4) Açık yapıları kapat
135
- repaired = closeOpenStructures(repaired);
183
+ // 4) İç (kaçırılmamış) tırnakları kaçır — shell komutlarındaki "..." için kritik
184
+ const quoted = escapeStrayQuotes(repaired);
185
+ try {
186
+ return JSON.parse(quoted);
187
+ }
188
+ catch { }
189
+ // 5) Açık yapıları kapat
190
+ repaired = closeOpenStructures(quoted);
136
191
  try {
137
192
  return JSON.parse(repaired);
138
193
  }
139
194
  catch { }
140
- // 5) Pes
195
+ // 6) Pes
141
196
  return fallback;
142
197
  }
143
198
  /**
package/dist/theme.js CHANGED
@@ -16,4 +16,4 @@ export const theme = {
16
16
  synType: '#a78bfa', // tip/sınıf adları, sabitler
17
17
  synProp: '#e0e0e0', // özellik/anahtar adları
18
18
  };
19
- export const VERSION = '1.0.78';
19
+ export const VERSION = '1.0.80';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {