wormclaude 1.0.147 → 1.0.148
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/cmdsec.js +3 -0
- package/dist/commands.js +19 -0
- package/dist/theme.js +1 -1
- package/dist/tools.js +8 -3
- package/dist/tui.js +54 -0
- package/dist/vision.js +61 -0
- package/package.json +1 -1
package/dist/cmdsec.js
CHANGED
|
@@ -191,6 +191,9 @@ const READONLY_ROOTS = new Set([
|
|
|
191
191
|
'ripgrep', 'sed', 'sort', 'stat', 'tail', 'tree', 'uniq', 'wc', 'which', 'where', 'whoami',
|
|
192
192
|
'id', 'hostname', 'uname', 'date', 'cal', 'uptime', 'file', 'realpath', 'nl', 'tac', 'cmp',
|
|
193
193
|
'md5sum', 'sha256sum', 'seq', 'test', 'true', 'false',
|
|
194
|
+
// Windows read-only komutları (PowerShell/cmd) — listeleme/okuma, yazma yok
|
|
195
|
+
'dir', 'type', 'findstr', 'ver', 'tasklist', 'ipconfig', 'systeminfo',
|
|
196
|
+
'get-content', 'get-childitem', 'gci', 'select-string', 'get-location', 'get-date', 'get-process',
|
|
194
197
|
'node', 'python', 'python3', 'java', 'go', 'rustc', 'php', 'ruby', // yalnız --version
|
|
195
198
|
'npm', 'pnpm', 'yarn', 'pip', 'pip3', 'docker', 'kubectl', 'cargo', // yalnız read-only alt-komut
|
|
196
199
|
]);
|
package/dist/commands.js
CHANGED
|
@@ -10,6 +10,7 @@ import * as usage from './usage.js';
|
|
|
10
10
|
import { runCompact, contextPercent, autoCompactThreshold } from './compact.js';
|
|
11
11
|
import { cmdDesc, setLang, saveLang, getLang } from './i18n.js';
|
|
12
12
|
import { listCheckpoints, rewindLast, rewindTo, rewindAll } from './checkpoint.js';
|
|
13
|
+
import { setPlanMode } from './tools.js';
|
|
13
14
|
import { loadSkills, getSkills, getSkillsDir, installSkill, updateSkill, removeSkill, getRegistry } from './skills.js';
|
|
14
15
|
import { getApprovedCommands, approveCommands, unapproveCommands, clearApproved } from './cmdsec.js';
|
|
15
16
|
import { isLearnEnabled, setLearnEnabled, getLearnFile, getLearnCount } from './learn.js';
|
|
@@ -25,6 +26,7 @@ export const COMMANDS = [
|
|
|
25
26
|
{ name: '/resume', desc: 'bu klasördeki önceki oturumu geri yükle (kesinti/çökme sonrası devam)' },
|
|
26
27
|
{ name: '/rewind', desc: 'dosya değişikliğini geri al: /rewind (son) · /rewind <id> · /rewind all' },
|
|
27
28
|
{ name: '/checkpoints', desc: 'geri alınabilir dosya değişikliklerini (checkpoint) listele' },
|
|
29
|
+
{ name: '/plan', desc: 'plan modu: önce araştır+plan yaz, yazma/komut engelli (/plan · /plan off)' },
|
|
28
30
|
{ name: '/compact', desc: 'geçmişi modelle özetleyip bağlamı küçült' },
|
|
29
31
|
{ name: '/context', desc: 'bağlam / token kullanımını göster' },
|
|
30
32
|
{ name: '/cost', desc: 'oturum token tahminini göster' },
|
|
@@ -265,6 +267,23 @@ export async function runSlashCommand(input, ctx) {
|
|
|
265
267
|
ctx.note((r.ok ? '↩ Geri alındı — ' : '⚠ ') + r.msg);
|
|
266
268
|
return true;
|
|
267
269
|
}
|
|
270
|
+
case '/plan': {
|
|
271
|
+
const a = arg.toLowerCase();
|
|
272
|
+
const h = ctx.getHistory();
|
|
273
|
+
if (a === 'off' || a === 'kapat' || a === 'çık' || a === 'cik') {
|
|
274
|
+
setPlanMode(false);
|
|
275
|
+
h.push({ role: 'user', content: '[PLAN MODE OFF] Plan onaylandı/kapatıldı — artık dosya yazıp komut çalıştırabilirsin.' });
|
|
276
|
+
ctx.setHistory(h);
|
|
277
|
+
ctx.note('📝 Plan modu KAPALI — uygulamaya geçebilirsin.');
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
setPlanMode(true);
|
|
281
|
+
h.push({ role: 'user', content: '[PLAN MODE ON] You are now in PLAN MODE. Investigate ONLY with read-only tools (Read, Glob, Grep, WebSearch/WebFetch) and then propose a clear, concise step-by-step plan. Do NOT write or edit files and do NOT run shell commands yet — those tools are blocked until the user types /plan off. Reply in the user\'s language.' });
|
|
282
|
+
ctx.setHistory(h);
|
|
283
|
+
ctx.note('📝 Plan modu AÇIK — model araştırıp plan yazacak; yazma/komut engelli. Onaylayınca /plan off ile uygula.');
|
|
284
|
+
}
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
268
287
|
case '/help':
|
|
269
288
|
ctx.note((getLang() === 'en' ? 'Commands:\n' : 'Komutlar:\n') +
|
|
270
289
|
COMMANDS.map((c) => ` ${c.name.padEnd(10)} ${cmdDesc(c.name)}`).join('\n') +
|
package/dist/theme.js
CHANGED
package/dist/tools.js
CHANGED
|
@@ -16,7 +16,7 @@ import { tasks } from './tasks.js';
|
|
|
16
16
|
import { getMcpToolSchemas, callMcpTool } from './mcp.js';
|
|
17
17
|
import { getSkills, getSkill, buildSkillPrompt } from './skills.js';
|
|
18
18
|
import { getExcludedTools } from './extensions.js';
|
|
19
|
-
import { checkCommand } from './cmdsec.js';
|
|
19
|
+
import { checkCommand, isShellCommandReadOnly } from './cmdsec.js';
|
|
20
20
|
import * as Diff from 'diff';
|
|
21
21
|
import * as computer from './computer.js';
|
|
22
22
|
// Agent/alt-agent araçlarının backend'e ulaşması için config. cli.tsx başlangıçta
|
|
@@ -634,6 +634,7 @@ const TOOL_META = {
|
|
|
634
634
|
// Plan modu: açıkken yazma/komut araçları engellenir (ExitPlanMode onayına kadar).
|
|
635
635
|
let planMode = false;
|
|
636
636
|
export function isPlanMode() { return planMode; }
|
|
637
|
+
export function setPlanMode(on) { planMode = !!on; }
|
|
637
638
|
const MAX_TOOL_CONCURRENCY = Number(process.env.WORMCLAUDE_MAX_TOOL_CONCURRENCY) || 10;
|
|
638
639
|
export function isConcurrencySafe(name) {
|
|
639
640
|
return TOOL_META[name]?.concurrencySafe ?? false; // bilinmeyen/MCP → sıralı (güvenli taraf)
|
|
@@ -689,9 +690,13 @@ async function execOne(call, hooks) {
|
|
|
689
690
|
}
|
|
690
691
|
return { ok: true, output: 'Plan reddedildi — plan modunda kal ve düzelt.', args };
|
|
691
692
|
}
|
|
692
|
-
// 3) Plan modu zorlaması: yazma
|
|
693
|
+
// 3) Plan modu zorlaması: yazma araçları engelli. Bash/PowerShell yalnız READ-ONLY komutsa
|
|
694
|
+
// (ls/cat/git status gibi) araştırma için izinli; yazan komut engelli.
|
|
693
695
|
if (planMode && !isReadOnly(call.name)) {
|
|
694
|
-
|
|
696
|
+
const _roCmd = (call.name === 'Bash' || call.name === 'PowerShell') && args?.command && isShellCommandReadOnly(String(args.command));
|
|
697
|
+
if (!_roCmd) {
|
|
698
|
+
return { ok: false, output: 'Plan modunda — yazma/komut engellendi (yalnız okuma/araştırma). Planı yaz; uygulamak için kullanıcı /plan off demeli.', args };
|
|
699
|
+
}
|
|
695
700
|
}
|
|
696
701
|
// 3.5) Komut güvenliği (Bash/PowerShell) — cmdsec: deny→blokla, allow→izinsiz, confirm→izin akışı
|
|
697
702
|
if ((call.name === 'Bash' || call.name === 'PowerShell') && args && args.command) {
|
package/dist/tui.js
CHANGED
|
@@ -8,6 +8,7 @@ import stringWidth from 'string-width';
|
|
|
8
8
|
import { loadConfig, streamChat, fetchAccount } from './api.js';
|
|
9
9
|
import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig, getBashCwd } from './tools.js';
|
|
10
10
|
import { setCheckpointStore } from './checkpoint.js';
|
|
11
|
+
import { extractImages, visionMessages, VISION_MODEL } from './vision.js';
|
|
11
12
|
import * as path from 'node:path';
|
|
12
13
|
import { sanitizeOutput } from './errorsan.js';
|
|
13
14
|
import { itemAnsi, markdownAnsi } from './ansi.js';
|
|
@@ -313,6 +314,53 @@ export async function runTui() {
|
|
|
313
314
|
const printItem = (it) => { displayItems.push(it); process.stdout.write(itemAnsi(it, cols()) + '\n'); refresh(); };
|
|
314
315
|
// ── Agent döngüsü (M2: araçlar + izin). Model tool çağırırsa izin sorulup çalıştırılır,
|
|
315
316
|
// sonuç geçmişe eklenip döngü devam eder; tool yoksa biter. ──
|
|
317
|
+
// Görsel (vision) turu — sürükle-bırak resim. Ana tool-loop'a girmez; VL modeline gider.
|
|
318
|
+
// Geçmişe base64 DEĞİL kısa "[görsel: ad]" etiketi yazılır (bağlam şişmesin).
|
|
319
|
+
async function runVisionTurn(question, images, displayText) {
|
|
320
|
+
busy = true;
|
|
321
|
+
streamChars = 0;
|
|
322
|
+
taskStart = Date.now();
|
|
323
|
+
turnAbort = new AbortController();
|
|
324
|
+
const _ml = _detectMsgLang(question);
|
|
325
|
+
if (_ml && _ml !== getLang())
|
|
326
|
+
setLang(_ml);
|
|
327
|
+
printItem({ kind: 'user', text: displayText });
|
|
328
|
+
printItem({ kind: 'note', text: `🖼 ${images.length} görsel inceleniyor (${images.map((i) => i.name).join(', ')})…` });
|
|
329
|
+
const timer = setInterval(() => { spin++; if (busy)
|
|
330
|
+
refresh(); }, 120);
|
|
331
|
+
let answer = '';
|
|
332
|
+
try {
|
|
333
|
+
const msgs = visionMessages(question, images);
|
|
334
|
+
for await (const ev of streamChat(msgs, [], { ...config, model: VISION_MODEL }, turnAbort?.signal)) {
|
|
335
|
+
if (ev.type === 'text') {
|
|
336
|
+
answer += ev.text;
|
|
337
|
+
streamChars = answer.length;
|
|
338
|
+
}
|
|
339
|
+
else if (ev.type === 'error') {
|
|
340
|
+
answer += `\n[hata: ${ev.error}]`;
|
|
341
|
+
}
|
|
342
|
+
else if (ev.type === 'done') {
|
|
343
|
+
const u = ev.usage;
|
|
344
|
+
if (u && u.input)
|
|
345
|
+
ctxUsed = u.input;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
answer += `\n[hata: ${e?.message || e}]`;
|
|
351
|
+
}
|
|
352
|
+
clearInterval(timer);
|
|
353
|
+
answer = stripEchoBlocks(stripInlineToolCalls(cleanModelText(answer))).trim();
|
|
354
|
+
if (answer)
|
|
355
|
+
printItem({ kind: 'assistant', text: answer });
|
|
356
|
+
// Geçmiş: base64 saklama → kısa etiket + soru, sonra cevap (sonraki turda metin modeli devam etsin)
|
|
357
|
+
const tag = images.map((i) => `[görsel: ${i.name}]`).join(' ');
|
|
358
|
+
history.push({ role: 'user', content: `${tag} ${question}`.trim() });
|
|
359
|
+
history.push({ role: 'assistant', content: answer || '(görsel yanıtı alınamadı)' });
|
|
360
|
+
busy = false;
|
|
361
|
+
turnAbort = null;
|
|
362
|
+
refresh();
|
|
363
|
+
}
|
|
316
364
|
async function runTurn(userText, displayText) {
|
|
317
365
|
busy = true;
|
|
318
366
|
streamChars = 0;
|
|
@@ -818,6 +866,12 @@ export async function runTui() {
|
|
|
818
866
|
runCommand(v);
|
|
819
867
|
return;
|
|
820
868
|
}
|
|
869
|
+
// 🖼 Görsel sürükle-bırak — girdide resim yolu varsa VL modeline gönder (vision turu)
|
|
870
|
+
const _vis = extractImages(v);
|
|
871
|
+
if (_vis.images.length) {
|
|
872
|
+
runVisionTurn(_vis.text, _vis.images, v);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
821
875
|
// @dosya mention — referanslanan dosya içeriğini modele enjekte et, kullanıcıya orijinali göster
|
|
822
876
|
const at = resolveAtMentions(v);
|
|
823
877
|
if (at.files.length) {
|
package/dist/vision.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Görsel (vision) girdisi — terminale dosya SÜRÜKLE-BIRAK yapınca yol metne yazılır.
|
|
2
|
+
// Bu modül girdideki görsel dosya yollarını bulur, okur, base64'ler ve metinden ayıklar.
|
|
3
|
+
// Vision isteği gateway'e model='wormclaude-vision' ile gider (VL modeline yönlenir).
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
const IMG_EXT = '(?:png|jpe?g|gif|webp|bmp)';
|
|
7
|
+
const MIME = {
|
|
8
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', bmp: 'image/bmp',
|
|
9
|
+
};
|
|
10
|
+
const MAX_BYTES = 12 * 1024 * 1024; // 12MB/görsel
|
|
11
|
+
const MAX_IMAGES = 4;
|
|
12
|
+
/** Girdideki görsel dosya yollarını bulur, okur, base64'ler; metinden çıkarır.
|
|
13
|
+
* Sürükle-bırak biçimleri: "C:\...\a.png" (tırnaklı) · C:\...\a.png · ./a.png · a.png · PowerShell '& path'. */
|
|
14
|
+
export function extractImages(input) {
|
|
15
|
+
const images = [];
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
let text = input || '';
|
|
18
|
+
const tryAdd = (raw) => {
|
|
19
|
+
if (images.length >= MAX_IMAGES)
|
|
20
|
+
return false;
|
|
21
|
+
let p = (raw || '').trim().replace(/^["'`]+|["'`]+$/g, '');
|
|
22
|
+
if (!p)
|
|
23
|
+
return false;
|
|
24
|
+
const abs = path.isAbsolute(p) ? p : path.resolve(process.cwd(), p);
|
|
25
|
+
const key = abs.toLowerCase();
|
|
26
|
+
if (seen.has(key))
|
|
27
|
+
return true; // zaten eklendi → metinden yine de çıkar
|
|
28
|
+
try {
|
|
29
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile())
|
|
30
|
+
return false;
|
|
31
|
+
const buf = fs.readFileSync(abs);
|
|
32
|
+
if (!buf.length || buf.length > MAX_BYTES)
|
|
33
|
+
return false;
|
|
34
|
+
const ext = (abs.split('.').pop() || '').toLowerCase();
|
|
35
|
+
seen.add(key);
|
|
36
|
+
images.push({ name: path.basename(abs), path: abs, data: buf.toString('base64'), media_type: MIME[ext] || 'image/png' });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
// 1) Tırnaklı yollar: "..." / '...' / `...`
|
|
44
|
+
text = text.replace(new RegExp('(["\'`])([^"\'`]+?\\.' + IMG_EXT + ')\\1', 'gi'), (m, _q, p) => (tryAdd(p) ? ' ' : m));
|
|
45
|
+
// 2) Tırnaksız mutlak/göreli yol (boşluksuz): C:\..\a.png · \\unc\a.png · /a.png · ./a.png · ~/a.png
|
|
46
|
+
text = text.replace(new RegExp('(?:^|\\s)&?\\s*((?:[A-Za-z]:[\\\\/]|\\\\\\\\|/|\\./|~/)[^\\s"\'`]*?\\.' + IMG_EXT + ')(?=\\s|$)', 'gi'), (m, p) => (tryAdd(p) ? ' ' : m));
|
|
47
|
+
// 3) Çıplak dosya adı (çalışma klasöründe): foto.png
|
|
48
|
+
text = text.replace(new RegExp('(?:^|\\s)([\\w.\\-]+\\.' + IMG_EXT + ')(?=\\s|$)', 'gi'), (m, p) => (tryAdd(p) ? ' ' : m));
|
|
49
|
+
return { images, text: text.replace(/\s{2,}/g, ' ').trim() };
|
|
50
|
+
}
|
|
51
|
+
/** Vision sistem promptu (kısa, dil-duyarlı) + içerik dizisi kurar. */
|
|
52
|
+
export const VISION_MODEL = 'wormclaude-vision';
|
|
53
|
+
export function visionMessages(question, images) {
|
|
54
|
+
const content = [{ type: 'text', text: question || 'Bu görselde ne var? Kullanıcının dilinde yanıtla.' }];
|
|
55
|
+
for (const im of images)
|
|
56
|
+
content.push({ type: 'image_url', image_url: { url: `data:${im.media_type};base64,${im.data}` } });
|
|
57
|
+
const system = 'You are WormClaude, a careful vision assistant. Look closely at the image(s) and answer the user\'s '
|
|
58
|
+
+ 'question concretely and accurately, IN THE USER\'S LANGUAGE (Turkish if they wrote Turkish). Describe only what '
|
|
59
|
+
+ 'you actually see — text, UI, objects, code, errors. If it is the WormClaude app, call it WormClaude (never "bot").';
|
|
60
|
+
return [{ role: 'system', content: system }, { role: 'user', content }];
|
|
61
|
+
}
|