wormclaude 1.0.13 → 1.0.15
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/agent.js +10 -1
- package/dist/api.js +12 -10
- package/dist/auth.js +10 -0
- package/dist/cli.js +7 -3
- package/dist/cmdsec.js +306 -0
- package/dist/commands.js +26 -0
- package/dist/errorsan.js +94 -0
- package/dist/extensions.js +13 -7
- package/dist/injections.js +108 -0
- package/dist/memory.js +41 -0
- package/dist/safejson.js +166 -0
- package/dist/streamparser.js +158 -0
- package/dist/subagents.js +119 -0
- package/dist/textclean.js +37 -0
- package/dist/theme.js +1 -1
- package/dist/tools.js +126 -13
- package/package.json +3 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Komut şablonu injection'ları — extension komutlarında dinamik içerik.
|
|
2
|
+
// Gemini/Blackbox CLI injectionParser + shellProcessor + atFileProcessor'dan uyarlandı.
|
|
3
|
+
// {{args}} -> kullanıcı argümanı (ham; !{} içinde shell-escape'li)
|
|
4
|
+
// !{komut} -> shell çıktısı gömer (cmdsec güvenlik kontrolüyle)
|
|
5
|
+
// @{dosya} -> dosya içeriği gömer (cwd'ye göre)
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { checkCommand, escapeShellArg } from './cmdsec.js';
|
|
10
|
+
const ARGS = '{{args}}';
|
|
11
|
+
const SHELL_TRIG = '!{';
|
|
12
|
+
const FILE_TRIG = '@{';
|
|
13
|
+
// Brace-sayan parser (injectionParser.js — temiz, birebir).
|
|
14
|
+
export function extractInjections(prompt, trigger, contextName) {
|
|
15
|
+
const injections = [];
|
|
16
|
+
let index = 0;
|
|
17
|
+
while (index < prompt.length) {
|
|
18
|
+
const startIndex = prompt.indexOf(trigger, index);
|
|
19
|
+
if (startIndex === -1)
|
|
20
|
+
break;
|
|
21
|
+
let i = startIndex + trigger.length;
|
|
22
|
+
let depth = 1;
|
|
23
|
+
let closed = false;
|
|
24
|
+
while (i < prompt.length) {
|
|
25
|
+
const ch = prompt[i];
|
|
26
|
+
if (ch === '{')
|
|
27
|
+
depth++;
|
|
28
|
+
else if (ch === '}') {
|
|
29
|
+
depth--;
|
|
30
|
+
if (depth === 0) {
|
|
31
|
+
injections.push({ content: prompt.substring(startIndex + trigger.length, i).trim(), startIndex, endIndex: i + 1 });
|
|
32
|
+
index = i + 1;
|
|
33
|
+
closed = true;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
if (!closed) {
|
|
40
|
+
const ctx = contextName ? ` ('${contextName}')` : '';
|
|
41
|
+
throw new Error(`Geçersiz injection${ctx}: ${trigger} kapanmamış (index ${startIndex}). Süslü parantezler dengeli olmalı.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return injections;
|
|
45
|
+
}
|
|
46
|
+
function runShell(cmd, cwd) {
|
|
47
|
+
const chk = checkCommand(cmd);
|
|
48
|
+
if (chk.decision === 'deny')
|
|
49
|
+
return `[engellendi: ${chk.reason || 'tehlikeli komut'}]`;
|
|
50
|
+
try {
|
|
51
|
+
const out = execSync(cmd, {
|
|
52
|
+
cwd, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024,
|
|
53
|
+
windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'],
|
|
54
|
+
});
|
|
55
|
+
return String(out).trim();
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
const o = (e?.stdout ? String(e.stdout) : '') + (e?.stderr ? String(e.stderr) : '');
|
|
59
|
+
return (o.trim() || `[komut hatası: ${e?.message || e}]`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readFileInjection(p, cwd) {
|
|
63
|
+
try {
|
|
64
|
+
const abs = path.isAbsolute(p) ? p : path.resolve(cwd, p);
|
|
65
|
+
const data = fs.readFileSync(abs, 'utf8');
|
|
66
|
+
return `--- ${p} ---\n${data}\n--- /${p} ---`;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return `[dosya okunamadı: ${p}]`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Bir injection türünü çözer; resolver(content) çıktıyı döndürür.
|
|
73
|
+
function replaceTrigger(text, trigger, resolver, ctx) {
|
|
74
|
+
const injs = extractInjections(text, trigger, ctx);
|
|
75
|
+
if (injs.length === 0)
|
|
76
|
+
return text;
|
|
77
|
+
let out = '';
|
|
78
|
+
let last = 0;
|
|
79
|
+
for (const inj of injs) {
|
|
80
|
+
out += text.substring(last, inj.startIndex);
|
|
81
|
+
out += resolver(inj.content);
|
|
82
|
+
last = inj.endIndex;
|
|
83
|
+
}
|
|
84
|
+
out += text.substring(last);
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
// Şablonu çöz: !{komut} -> shell çıktısı, @{dosya} -> dosya, {{args}} -> argüman.
|
|
88
|
+
export function resolveInjections(text, opts = {}) {
|
|
89
|
+
const args = opts.args ?? '';
|
|
90
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
91
|
+
const ctx = opts.contextName;
|
|
92
|
+
let out = text;
|
|
93
|
+
// 1) !{komut}: içindeki {{args}} shell-escape'li, sonra cmdsec ile çalıştır.
|
|
94
|
+
if (out.includes(SHELL_TRIG)) {
|
|
95
|
+
const shell = process.platform === 'win32' ? 'cmd' : 'bash';
|
|
96
|
+
out = replaceTrigger(out, SHELL_TRIG, (cmd) => {
|
|
97
|
+
const resolved = cmd.split(ARGS).join(escapeShellArg(args, shell));
|
|
98
|
+
return runShell(resolved, cwd);
|
|
99
|
+
}, ctx);
|
|
100
|
+
}
|
|
101
|
+
// 2) @{dosya}: içindeki {{args}} ham, sonra oku.
|
|
102
|
+
if (out.includes(FILE_TRIG)) {
|
|
103
|
+
out = replaceTrigger(out, FILE_TRIG, (p) => readFileInjection(p.split(ARGS).join(args), cwd), ctx);
|
|
104
|
+
}
|
|
105
|
+
// 3) Kalan {{args}} -> ham argüman.
|
|
106
|
+
out = out.split(ARGS).join(args);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
package/dist/memory.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Tetik eşikleri ve kilit mekanizması Claude Code'dan uyarlandı.
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
|
+
import * as os from 'node:os';
|
|
5
6
|
import { completeText } from './agent.js';
|
|
6
7
|
import { approxTokens } from './usage.js';
|
|
7
8
|
import { tasks } from './tasks.js';
|
|
@@ -133,6 +134,46 @@ export function dreamTimeGatePassed() {
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
export function getMemoryPath() { return MEM_FILE; }
|
|
137
|
+
// ── Açık kayıt: save_memory tool'u (otomatik hafızayı tamamlar) ────────────────
|
|
138
|
+
const MEMORY_HEADER = '## Eklenen Hatıralar';
|
|
139
|
+
/**
|
|
140
|
+
* Modelin/kullanıcının açıkça istediği tek bir kalıcı bilgiyi hafıza dosyasına ekler.
|
|
141
|
+
* Otomatik konsolidasyondan bağımsız; "## Eklenen Hatıralar" başlığı altına "- <fact>" yazar.
|
|
142
|
+
* scope: 'project' → cwd/.wormclaude/memory.md (varsayılan), 'global' → ~/.wormclaude/memory.md.
|
|
143
|
+
*/
|
|
144
|
+
export function saveMemoryFact(fact, scope = 'project') {
|
|
145
|
+
const clean = String(fact || '').trim().replace(/^(-+\s*)+/, '').trim();
|
|
146
|
+
if (!clean)
|
|
147
|
+
throw new Error('boş hatıra');
|
|
148
|
+
const dir = scope === 'global'
|
|
149
|
+
? path.join(os.homedir(), '.wormclaude')
|
|
150
|
+
: MEM_DIR;
|
|
151
|
+
const file = path.join(dir, 'memory.md');
|
|
152
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
153
|
+
let content = '';
|
|
154
|
+
try {
|
|
155
|
+
content = fs.readFileSync(file, 'utf8');
|
|
156
|
+
}
|
|
157
|
+
catch { }
|
|
158
|
+
const item = `- ${clean}`;
|
|
159
|
+
const idx = content.indexOf(MEMORY_HEADER);
|
|
160
|
+
if (idx === -1) {
|
|
161
|
+
const sep = content.length === 0 ? '' : content.endsWith('\n') ? '\n' : '\n\n';
|
|
162
|
+
content += `${sep}${MEMORY_HEADER}\n${item}\n`;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Başlıktan sonraki bölümün sonuna ekle (sonraki ## başlığına kadar)
|
|
166
|
+
const start = idx + MEMORY_HEADER.length;
|
|
167
|
+
let end = content.indexOf('\n## ', start);
|
|
168
|
+
if (end === -1)
|
|
169
|
+
end = content.length;
|
|
170
|
+
const before = content.slice(0, end).replace(/\s+$/, '');
|
|
171
|
+
const after = content.slice(end);
|
|
172
|
+
content = `${before}\n${item}${after.startsWith('\n') ? '' : '\n'}${after}`;
|
|
173
|
+
}
|
|
174
|
+
fs.writeFileSync(file, content);
|
|
175
|
+
return file;
|
|
176
|
+
}
|
|
136
177
|
// Kalıcı hafızayı başlangıçta context'e yüklemek için oku.
|
|
137
178
|
// .wormclaude/memory.md (oturumlar arası hafıza) + WORMCLAUDE.md (proje notu).
|
|
138
179
|
export function loadMemoryContext() {
|
package/dist/safejson.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Qwen-14B'nin bozuk JSON tool-call argümanlarını kurtarmak için dayanıklı JSON yardımcıları.
|
|
2
|
+
// Bağımlılıksız (jsonrepair YOK) — elle onarım stratejileri. Gemini safeJsonParse + fixBooleanCasing'den
|
|
3
|
+
// uyarlandı; WormClaude'a özel sadeleştirildi.
|
|
4
|
+
/**
|
|
5
|
+
* Python-tarzı True/False/None'ı JSON karşılıklarına çevirir (özyinelemeli, string-güvenli).
|
|
6
|
+
* Qwen bazen `{"flag": True}` üretir → geçerli JSON değil.
|
|
7
|
+
*/
|
|
8
|
+
export function fixBooleanCasing(text) {
|
|
9
|
+
let out = '';
|
|
10
|
+
let inString = false;
|
|
11
|
+
let escape = false;
|
|
12
|
+
for (let i = 0; i < text.length; i++) {
|
|
13
|
+
const ch = text[i];
|
|
14
|
+
if (inString) {
|
|
15
|
+
out += ch;
|
|
16
|
+
if (escape)
|
|
17
|
+
escape = false;
|
|
18
|
+
else if (ch === '\\')
|
|
19
|
+
escape = true;
|
|
20
|
+
else if (ch === '"')
|
|
21
|
+
inString = false;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (ch === '"') {
|
|
25
|
+
inString = true;
|
|
26
|
+
out += ch;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// String dışında: Python literal'lerini kelime-sınırında değiştir
|
|
30
|
+
const rest = text.slice(i);
|
|
31
|
+
const m = /^(True|False|None)\b/.exec(rest);
|
|
32
|
+
if (m) {
|
|
33
|
+
out += m[1] === 'True' ? 'true' : m[1] === 'False' ? 'false' : 'null';
|
|
34
|
+
i += m[1].length - 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
out += ch;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** String dışındaki trailing virgülleri kaldırır: `{"a":1,}` → `{"a":1}` */
|
|
42
|
+
function stripTrailingCommas(text) {
|
|
43
|
+
let out = '';
|
|
44
|
+
let inString = false;
|
|
45
|
+
let escape = false;
|
|
46
|
+
for (let i = 0; i < text.length; i++) {
|
|
47
|
+
const ch = text[i];
|
|
48
|
+
if (inString) {
|
|
49
|
+
out += ch;
|
|
50
|
+
if (escape)
|
|
51
|
+
escape = false;
|
|
52
|
+
else if (ch === '\\')
|
|
53
|
+
escape = true;
|
|
54
|
+
else if (ch === '"')
|
|
55
|
+
inString = false;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (ch === '"') {
|
|
59
|
+
inString = true;
|
|
60
|
+
out += ch;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ch === ',') {
|
|
64
|
+
// Sonraki anlamlı karakter } veya ] ise virgülü at
|
|
65
|
+
let j = i + 1;
|
|
66
|
+
while (j < text.length && /\s/.test(text[j]))
|
|
67
|
+
j++;
|
|
68
|
+
if (text[j] === '}' || text[j] === ']')
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
out += ch;
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/** Açık kalan string/parantez/köşeli ayraçları kapatır (streaming kesintisi onarımı). */
|
|
76
|
+
function closeOpenStructures(text) {
|
|
77
|
+
let inString = false;
|
|
78
|
+
let escape = false;
|
|
79
|
+
const stack = [];
|
|
80
|
+
for (let i = 0; i < text.length; i++) {
|
|
81
|
+
const ch = text[i];
|
|
82
|
+
if (inString) {
|
|
83
|
+
if (escape)
|
|
84
|
+
escape = false;
|
|
85
|
+
else if (ch === '\\')
|
|
86
|
+
escape = true;
|
|
87
|
+
else if (ch === '"')
|
|
88
|
+
inString = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (ch === '"')
|
|
92
|
+
inString = true;
|
|
93
|
+
else if (ch === '{')
|
|
94
|
+
stack.push('}');
|
|
95
|
+
else if (ch === '[')
|
|
96
|
+
stack.push(']');
|
|
97
|
+
else if (ch === '}' || ch === ']')
|
|
98
|
+
stack.pop();
|
|
99
|
+
}
|
|
100
|
+
let out = text;
|
|
101
|
+
if (inString)
|
|
102
|
+
out += '"';
|
|
103
|
+
while (stack.length)
|
|
104
|
+
out += stack.pop();
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 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.
|
|
110
|
+
*/
|
|
111
|
+
export function safeJsonParse(text, fallback) {
|
|
112
|
+
if (text == null)
|
|
113
|
+
return fallback;
|
|
114
|
+
const raw = String(text).trim();
|
|
115
|
+
if (!raw)
|
|
116
|
+
return fallback;
|
|
117
|
+
// 1) Düz
|
|
118
|
+
try {
|
|
119
|
+
return JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch { }
|
|
122
|
+
// 2) Boolean casing
|
|
123
|
+
let repaired = fixBooleanCasing(raw);
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(repaired);
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
// 3) Trailing virgüller
|
|
129
|
+
repaired = stripTrailingCommas(repaired);
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(repaired);
|
|
132
|
+
}
|
|
133
|
+
catch { }
|
|
134
|
+
// 4) Açık yapıları kapat
|
|
135
|
+
repaired = closeOpenStructures(repaired);
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(repaired);
|
|
138
|
+
}
|
|
139
|
+
catch { }
|
|
140
|
+
// 5) Pes
|
|
141
|
+
return fallback;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Döngüsel referansları ve hata/undefined değerleri tolere ederek JSON üretir.
|
|
145
|
+
* Asla fırlatmaz; başarısızsa fallback string döner.
|
|
146
|
+
*/
|
|
147
|
+
export function safeJsonStringify(value, space, fallback = '"[unserializable]"') {
|
|
148
|
+
const seen = new WeakSet();
|
|
149
|
+
try {
|
|
150
|
+
return JSON.stringify(value, (_k, v) => {
|
|
151
|
+
if (typeof v === 'bigint')
|
|
152
|
+
return v.toString();
|
|
153
|
+
if (v instanceof Error)
|
|
154
|
+
return { name: v.name, message: v.message };
|
|
155
|
+
if (typeof v === 'object' && v !== null) {
|
|
156
|
+
if (seen.has(v))
|
|
157
|
+
return '[Circular]';
|
|
158
|
+
seen.add(v);
|
|
159
|
+
}
|
|
160
|
+
return v;
|
|
161
|
+
}, space);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return fallback;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Streaming tool-call delta'larını dayanıklı biçimde birleştirir.
|
|
2
|
+
// Qwen-14B + vLLM(hermes) parçalı JSON arg, index çakışması, ID'siz devam chunk'ı üretebilir.
|
|
3
|
+
// Gemini streamingToolCallParser'dan uyarlandı; bizim {index, id?, name?, argChunk} delta'mıza göre sade.
|
|
4
|
+
import { safeJsonParse } from './safejson.js';
|
|
5
|
+
export class StreamingToolCallParser {
|
|
6
|
+
buffers = new Map();
|
|
7
|
+
depths = new Map();
|
|
8
|
+
inStrings = new Map();
|
|
9
|
+
escapes = new Map();
|
|
10
|
+
meta = new Map();
|
|
11
|
+
idToIndex = new Map();
|
|
12
|
+
nextIndex = 0;
|
|
13
|
+
/**
|
|
14
|
+
* Bir delta chunk'ı ekler. index streaming yanıtından gelir (çakışabilir); id yeni çağrı başlangıcı.
|
|
15
|
+
* @returns chunk işlendikten sonra o index'te JSON tamamlandıysa parse edilmiş değer.
|
|
16
|
+
*/
|
|
17
|
+
addChunk(index, chunk, id, name) {
|
|
18
|
+
let actual = index;
|
|
19
|
+
if (id) {
|
|
20
|
+
if (this.idToIndex.has(id)) {
|
|
21
|
+
actual = this.idToIndex.get(id);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Bu index'te FARKLI id'li, tamamlanmış bir çağrı varsa yeni slot bul
|
|
25
|
+
if (this.buffers.has(index)) {
|
|
26
|
+
const buf = this.buffers.get(index);
|
|
27
|
+
const depth = this.depths.get(index);
|
|
28
|
+
const m = this.meta.get(index);
|
|
29
|
+
if (buf.trim() && depth === 0 && m?.id && m.id !== id) {
|
|
30
|
+
try {
|
|
31
|
+
JSON.parse(buf);
|
|
32
|
+
actual = this.findNextIndex();
|
|
33
|
+
}
|
|
34
|
+
catch { /* yarım → güvenle yeniden kullan */ }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.idToIndex.set(id, actual);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (this.buffers.has(index)) {
|
|
41
|
+
// ID'siz devam: bu index tamamlandıysa en son yarım çağrıya yönlendir
|
|
42
|
+
const buf = this.buffers.get(index);
|
|
43
|
+
const depth = this.depths.get(index);
|
|
44
|
+
if (depth === 0 && buf.trim()) {
|
|
45
|
+
try {
|
|
46
|
+
JSON.parse(buf);
|
|
47
|
+
actual = this.findMostRecentIncomplete();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
actual = index;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!this.buffers.has(actual)) {
|
|
55
|
+
this.buffers.set(actual, '');
|
|
56
|
+
this.depths.set(actual, 0);
|
|
57
|
+
this.inStrings.set(actual, false);
|
|
58
|
+
this.escapes.set(actual, false);
|
|
59
|
+
this.meta.set(actual, {});
|
|
60
|
+
}
|
|
61
|
+
const m = this.meta.get(actual);
|
|
62
|
+
if (id)
|
|
63
|
+
m.id = id;
|
|
64
|
+
if (name)
|
|
65
|
+
m.name = (m.name || '') + name;
|
|
66
|
+
const newBuffer = this.buffers.get(actual) + chunk;
|
|
67
|
+
this.buffers.set(actual, newBuffer);
|
|
68
|
+
// Derinlik takibi — sadece string DIŞINDAKİ ayraçları say
|
|
69
|
+
let depth = this.depths.get(actual);
|
|
70
|
+
let inString = this.inStrings.get(actual);
|
|
71
|
+
let escape = this.escapes.get(actual);
|
|
72
|
+
for (const ch of chunk) {
|
|
73
|
+
if (!inString) {
|
|
74
|
+
if (ch === '{' || ch === '[')
|
|
75
|
+
depth++;
|
|
76
|
+
else if (ch === '}' || ch === ']')
|
|
77
|
+
depth--;
|
|
78
|
+
}
|
|
79
|
+
if (ch === '"' && !escape)
|
|
80
|
+
inString = !inString;
|
|
81
|
+
escape = ch === '\\' && !escape;
|
|
82
|
+
}
|
|
83
|
+
this.depths.set(actual, depth);
|
|
84
|
+
this.inStrings.set(actual, inString);
|
|
85
|
+
this.escapes.set(actual, escape);
|
|
86
|
+
if (depth === 0 && newBuffer.trim().length > 0) {
|
|
87
|
+
try {
|
|
88
|
+
return { complete: true, value: JSON.parse(newBuffer) };
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return { complete: false };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { complete: false };
|
|
95
|
+
}
|
|
96
|
+
/** Stream bittiğinde (finish_reason) çağrılır. name'i olan ve buffer'ı dolu tüm çağrıları döndürür. */
|
|
97
|
+
getCompleted() {
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const [index, buffer] of this.buffers.entries()) {
|
|
100
|
+
const m = this.meta.get(index);
|
|
101
|
+
if (!m?.name)
|
|
102
|
+
continue; // isimsiz parça → çağrı değil
|
|
103
|
+
// Boş buffer = argümansız tool (ör. {}). safeJsonParse onarım dener, başarısızsa {} fallback.
|
|
104
|
+
const args = buffer.trim() ? safeJsonParse(buffer, {}) : {};
|
|
105
|
+
out.push({ id: m.id || `call_${index}`, name: m.name, args, index });
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
reset() {
|
|
110
|
+
this.buffers.clear();
|
|
111
|
+
this.depths.clear();
|
|
112
|
+
this.inStrings.clear();
|
|
113
|
+
this.escapes.clear();
|
|
114
|
+
this.meta.clear();
|
|
115
|
+
this.idToIndex.clear();
|
|
116
|
+
this.nextIndex = 0;
|
|
117
|
+
}
|
|
118
|
+
findNextIndex() {
|
|
119
|
+
while (this.buffers.has(this.nextIndex)) {
|
|
120
|
+
const buf = this.buffers.get(this.nextIndex);
|
|
121
|
+
const depth = this.depths.get(this.nextIndex);
|
|
122
|
+
const m = this.meta.get(this.nextIndex);
|
|
123
|
+
if (!buf.trim() || depth > 0 || !m?.id)
|
|
124
|
+
return this.nextIndex;
|
|
125
|
+
try {
|
|
126
|
+
JSON.parse(buf);
|
|
127
|
+
if (depth === 0) {
|
|
128
|
+
this.nextIndex++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return this.nextIndex;
|
|
134
|
+
}
|
|
135
|
+
this.nextIndex++;
|
|
136
|
+
}
|
|
137
|
+
return this.nextIndex++;
|
|
138
|
+
}
|
|
139
|
+
findMostRecentIncomplete() {
|
|
140
|
+
let maxIndex = -1;
|
|
141
|
+
for (const [index, buffer] of this.buffers.entries()) {
|
|
142
|
+
const depth = this.depths.get(index);
|
|
143
|
+
const m = this.meta.get(index);
|
|
144
|
+
if (m?.id && (depth > 0 || !buffer.trim())) {
|
|
145
|
+
maxIndex = Math.max(maxIndex, index);
|
|
146
|
+
}
|
|
147
|
+
else if (buffer.trim()) {
|
|
148
|
+
try {
|
|
149
|
+
JSON.parse(buffer);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
maxIndex = Math.max(maxIndex, index);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return maxIndex >= 0 ? maxIndex : this.findNextIndex();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// İsimli/uzman alt-ajan tanımları + kullanıcı-seviyesi loader.
|
|
2
|
+
// Agent tool'u opsiyonel `subagent_type` ile çağrılınca buradan uzman prompt + tool-kısıtı çözülür.
|
|
3
|
+
// Gemini builtin-agents + subagent-manager'dan uyarlandı; WormClaude güvenlik kimliğine göre.
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
// ── Gömülü uzman ajanlar ──────────────────────────────────────────────────────
|
|
8
|
+
const BUILTINS = [
|
|
9
|
+
{
|
|
10
|
+
name: 'general-purpose',
|
|
11
|
+
description: 'Karmaşık soruları araştırma, kod arama ve çok adımlı görevler için genel amaçlı ajan.',
|
|
12
|
+
system: 'Sen bir WormClaude alt-ajanısın: tek bir görevi uçtan uca bitirmek için doğmuş otonom bir işçi. ' +
|
|
13
|
+
'İstenen neyse onu yap — fazlası değil, eksiği değil. Geniş arama için Grep/Glob, bilinen yol için Read kullan. ' +
|
|
14
|
+
'Gereksiz dosya YARATMA; *.md/README dosyalarını yalnız açıkça istenirse oluştur. ' +
|
|
15
|
+
'Bu görev dışında hafızan yok. Bitince yaptıklarının ve istenen sonuçların kısa, net bir raporunu döndür. ' +
|
|
16
|
+
'Yanıtta paylaştığın dosya yolları MUTLAK olmalı; göreli yol kullanma. Emoji kullanma.',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'security-recon',
|
|
20
|
+
description: 'YETKİLİ güvenlik testlerinde keşif: port/servis/teknoloji tespiti, yüzey analizi, raporlama.',
|
|
21
|
+
system: 'Sen bir WormClaude güvenlik-keşif alt-ajanısın. SADECE yetkili/izinli hedeflerde çalış. ' +
|
|
22
|
+
'Görevin: pasif ve aktif keşif (port/servis tespiti, teknoloji parmak izi, dizin/endpoint yüzeyi, ' +
|
|
23
|
+
'sürüm tespiti) yapıp bulgularını yapılandırılmış bir rapor olarak döndürmek. ' +
|
|
24
|
+
'Yıkıcı işlem, gerçek istismar veya hizmet-dışı bırakma YAPMA — yalnız keşif ve analiz. ' +
|
|
25
|
+
'Bulguları önem derecesiyle (bilgi/düşük/orta/yüksek) sınıflandır. Komut çıktısını ham bırakma, yorumla. Emoji kullanma.',
|
|
26
|
+
tools: ['Bash', 'Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'TaskOutput'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'code-explorer',
|
|
30
|
+
description: 'Büyük kod tabanlarını haritalama: mimari, modül ilişkileri, giriş noktaları.',
|
|
31
|
+
system: 'Sen bir WormClaude kod-keşif alt-ajanısın. Görevin bir kod tabanını hızla haritalamak: ' +
|
|
32
|
+
'dizin yapısı, ana modüller, giriş noktaları, önemli soyutlamalar ve bağımlılıklar. ' +
|
|
33
|
+
'Grep/Glob ile geniş tara, Read ile kritik dosyaları incele. Dosya YARATMA/DEĞİŞTİRME. ' +
|
|
34
|
+
'Sonucu: kısa mimari özet + ilgili dosya yolları (MUTLAK) + kod parçaları olarak döndür. Emoji kullanma.',
|
|
35
|
+
tools: ['Grep', 'Glob', 'Read', 'TaskOutput'],
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
// ── Kullanıcı-seviyesi ajanlar: ~/.wormclaude/agents/*.md ─────────────────────
|
|
39
|
+
function agentsDir() {
|
|
40
|
+
return path.join(os.homedir(), '.wormclaude', 'agents');
|
|
41
|
+
}
|
|
42
|
+
/** Minimal frontmatter ayrıştırma (yaml-parser gelene kadar; name/description/tools + body). */
|
|
43
|
+
function parseAgentFile(content, fallbackName) {
|
|
44
|
+
const m = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/.exec(content);
|
|
45
|
+
if (!m)
|
|
46
|
+
return null;
|
|
47
|
+
const [, fm, body] = m;
|
|
48
|
+
const get = (key) => {
|
|
49
|
+
const r = new RegExp(`^${key}\\s*:\\s*(.+)$`, 'mi').exec(fm);
|
|
50
|
+
return r ? r[1].trim().replace(/^["']|["']$/g, '') : undefined;
|
|
51
|
+
};
|
|
52
|
+
const name = (get('name') || fallbackName).trim();
|
|
53
|
+
const description = get('description') || '';
|
|
54
|
+
const system = body.trim();
|
|
55
|
+
if (!name || !system)
|
|
56
|
+
return null;
|
|
57
|
+
// tools: [A, B] veya A,B
|
|
58
|
+
let tools;
|
|
59
|
+
const rawTools = get('tools');
|
|
60
|
+
if (rawTools) {
|
|
61
|
+
tools = rawTools.replace(/[\[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
62
|
+
if (!tools.length)
|
|
63
|
+
tools = undefined;
|
|
64
|
+
}
|
|
65
|
+
// İsim guard: rezerve/biçim
|
|
66
|
+
const RESERVED = new Set(['self', 'system', 'user', 'model', 'tool', 'config', 'default']);
|
|
67
|
+
if (!/^[a-zA-Z0-9_-]{2,50}$/.test(name) || RESERVED.has(name.toLowerCase()))
|
|
68
|
+
return null;
|
|
69
|
+
return { name, description, system, tools };
|
|
70
|
+
}
|
|
71
|
+
function loadUserAgents() {
|
|
72
|
+
try {
|
|
73
|
+
const dir = agentsDir();
|
|
74
|
+
if (!fs.existsSync(dir))
|
|
75
|
+
return [];
|
|
76
|
+
return fs.readdirSync(dir)
|
|
77
|
+
.filter((f) => f.endsWith('.md'))
|
|
78
|
+
.map((f) => {
|
|
79
|
+
try {
|
|
80
|
+
const def = parseAgentFile(fs.readFileSync(path.join(dir, f), 'utf8'), f.replace(/\.md$/, ''));
|
|
81
|
+
return def;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.filter((d) => d !== null);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Ajanı isme göre çöz. Öncelik: kullanıcı dosyası > gömülü. Bulunamazsa null. */
|
|
94
|
+
export function resolveSubagent(type) {
|
|
95
|
+
if (!type)
|
|
96
|
+
return null;
|
|
97
|
+
const t = type.trim().toLowerCase();
|
|
98
|
+
const user = loadUserAgents().find((a) => a.name.toLowerCase() === t);
|
|
99
|
+
if (user)
|
|
100
|
+
return user;
|
|
101
|
+
return BUILTINS.find((a) => a.name.toLowerCase() === t) || null;
|
|
102
|
+
}
|
|
103
|
+
/** Tüm kullanılabilir ajanları listele (kullanıcı dosyaları gömülüleri gölgeler). */
|
|
104
|
+
export function listSubagents() {
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const a of [...loadUserAgents(), ...BUILTINS]) {
|
|
108
|
+
const k = a.name.toLowerCase();
|
|
109
|
+
if (seen.has(k))
|
|
110
|
+
continue;
|
|
111
|
+
seen.add(k);
|
|
112
|
+
out.push(a);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** Agent tool açıklamasına eklenecek "kullanılabilir tipler" satırı. */
|
|
117
|
+
export function subagentTypesHint() {
|
|
118
|
+
return listSubagents().map((a) => `- ${a.name}: ${a.description}`).join('\n');
|
|
119
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Model metnindeki sızıntıları temizler: özel tokenlar + parçalı tool-call markup.
|
|
2
|
+
// Qwen-14B + vLLM(hermes) bazen ham çıktıya kontrol token'ları veya tool-call etiketleri sızdırır.
|
|
3
|
+
// ChatML / Qwen özel token'ları
|
|
4
|
+
const SPECIAL_TOKENS = [
|
|
5
|
+
'<|endoftext|>', '<|im_start|>', '<|im_end|>', '<|im_sep|>',
|
|
6
|
+
'<|endofturn|>', '<|fim_prefix|>', '<|fim_middle|>', '<|fim_suffix|>',
|
|
7
|
+
];
|
|
8
|
+
// Hermes tool-call markup (metne sızarsa) — bloğun TAMAMINI kaldır.
|
|
9
|
+
const TOOL_CALL_BLOCK = /<tool_call>[\s\S]*?<\/tool_call>/g;
|
|
10
|
+
const TOOL_RESPONSE_BLOCK = /<tool_response>[\s\S]*?<\/tool_response>/g;
|
|
11
|
+
// Açık kalan (kapanışı gelmemiş) tek etiketler
|
|
12
|
+
const STRAY_TOOL_TAGS = /<\/?(tool_call|tool_response)>/g;
|
|
13
|
+
/**
|
|
14
|
+
* Görüntülenecek/saklanacak model metnini temizler. İçeriği bozmadan yalnız
|
|
15
|
+
* sızan kontrol token'larını ve tool-call markup'ını çıkarır.
|
|
16
|
+
*/
|
|
17
|
+
export function cleanModelText(text) {
|
|
18
|
+
if (!text)
|
|
19
|
+
return text;
|
|
20
|
+
let out = text;
|
|
21
|
+
out = out.replace(TOOL_CALL_BLOCK, '');
|
|
22
|
+
out = out.replace(TOOL_RESPONSE_BLOCK, '');
|
|
23
|
+
out = out.replace(STRAY_TOOL_TAGS, '');
|
|
24
|
+
for (const tok of SPECIAL_TOKENS) {
|
|
25
|
+
if (out.includes(tok))
|
|
26
|
+
out = out.split(tok).join('');
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Streaming sırasında güvenli temizlik: yalnız TAM özel-token'ları ve kapanmış blokları
|
|
32
|
+
* temizler — yarım gelen bir etiketi (ör. "<tool_") sonraki chunk'ı bekleyeceği için bırakır.
|
|
33
|
+
* Akan tamponun tamamına uygulanır (parçalı token'ı bölmemek için).
|
|
34
|
+
*/
|
|
35
|
+
export function cleanStreamingBuffer(buffer) {
|
|
36
|
+
return cleanModelText(buffer);
|
|
37
|
+
}
|
package/dist/theme.js
CHANGED