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
package/dist/tools.js
CHANGED
|
@@ -8,10 +8,14 @@ import * as os from 'node:os';
|
|
|
8
8
|
import * as path from 'node:path';
|
|
9
9
|
import { loadConfig } from './api.js';
|
|
10
10
|
import { runAgentLoop } from './agent.js';
|
|
11
|
+
import { resolveSubagent, subagentTypesHint } from './subagents.js';
|
|
12
|
+
import { saveMemoryFact } from './memory.js';
|
|
11
13
|
import { tasks } from './tasks.js';
|
|
12
14
|
import { getMcpToolSchemas, callMcpTool } from './mcp.js';
|
|
13
15
|
import { getSkills, getSkill, buildSkillPrompt } from './skills.js';
|
|
14
16
|
import { getExcludedTools } from './extensions.js';
|
|
17
|
+
import { checkCommand } from './cmdsec.js';
|
|
18
|
+
import * as Diff from 'diff';
|
|
15
19
|
import * as computer from './computer.js';
|
|
16
20
|
// Agent/alt-agent araçlarının backend'e ulaşması için config. cli.tsx başlangıçta
|
|
17
21
|
// setToolConfig ile aynı (mutable) config nesnesini verir → /config değişiklikleri görülür.
|
|
@@ -299,12 +303,13 @@ export const toolSchemas = [
|
|
|
299
303
|
type: 'function',
|
|
300
304
|
function: {
|
|
301
305
|
name: 'Agent',
|
|
302
|
-
description: AGENT_DESCRIPTION,
|
|
306
|
+
description: AGENT_DESCRIPTION + '\n\nAvailable subagent_type values (specialized prompt + restricted tools):\n' + subagentTypesHint(),
|
|
303
307
|
parameters: {
|
|
304
308
|
type: 'object',
|
|
305
309
|
properties: {
|
|
306
310
|
description: { type: 'string', description: 'A short (3-5 word) description of the task' },
|
|
307
311
|
prompt: { type: 'string', description: 'The detailed, self-contained task for the sub-agent to perform' },
|
|
312
|
+
subagent_type: { type: 'string', description: 'Optional: specialized agent to use (e.g. general-purpose, security-recon, code-explorer). Selects a tailored system prompt and a restricted tool set. Omit for a general sub-agent.' },
|
|
308
313
|
run_in_background: { type: 'boolean', description: 'Run the sub-agent in the background and return a task id (default false)' },
|
|
309
314
|
},
|
|
310
315
|
required: ['description', 'prompt'],
|
|
@@ -325,6 +330,21 @@ export const toolSchemas = [
|
|
|
325
330
|
},
|
|
326
331
|
},
|
|
327
332
|
},
|
|
333
|
+
{
|
|
334
|
+
type: 'function',
|
|
335
|
+
function: {
|
|
336
|
+
name: 'SaveMemory',
|
|
337
|
+
description: 'Save a single concise fact to long-term memory so it persists across sessions. Use when the user explicitly asks you to remember something, or when you detect a durable preference, decision, project convention, or constraint worth keeping. Keep the fact short and self-contained. Do NOT use for transient or session-only details.',
|
|
338
|
+
parameters: {
|
|
339
|
+
type: 'object',
|
|
340
|
+
properties: {
|
|
341
|
+
fact: { type: 'string', description: 'The specific, self-contained fact to remember (e.g. "User prefers Turkish responses", "Project uses pnpm not npm").' },
|
|
342
|
+
scope: { type: 'string', enum: ['project', 'global'], description: 'project = this project only (default), global = all projects.' },
|
|
343
|
+
},
|
|
344
|
+
required: ['fact'],
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
328
348
|
{
|
|
329
349
|
type: 'function',
|
|
330
350
|
function: {
|
|
@@ -538,6 +558,7 @@ const TOOL_META = {
|
|
|
538
558
|
Scroll: { needsPermission: true, validate: (a) => (a && a.direction ? null : 'direction gerekli') },
|
|
539
559
|
WebSearch: { readOnly: true, needsPermission: true, validate: (a) => (a && a.query ? null : 'query gerekli') },
|
|
540
560
|
TodoWrite: { readOnly: true, validate: (a) => (a && Array.isArray(a.todos) ? null : 'todos dizisi gerekli') },
|
|
561
|
+
SaveMemory: { validate: (a) => (a && a.fact && String(a.fact).trim() ? null : 'fact gerekli') },
|
|
541
562
|
PowerShell: { needsPermission: true, validate: (a) => (a && a.command ? null : 'command gerekli') },
|
|
542
563
|
NotebookEdit: { needsPermission: true, validate: (a) => (a && a.notebook_path ? null : 'notebook_path gerekli') },
|
|
543
564
|
REPL: { needsPermission: true, validate: (a) => (a && a.language && a.code ? null : 'language ve code gerekli') },
|
|
@@ -602,6 +623,17 @@ async function execOne(call, hooks) {
|
|
|
602
623
|
if (planMode && !isReadOnly(call.name)) {
|
|
603
624
|
return { ok: false, output: 'Plan modunda — yazma/komut engellendi. Önce ExitPlanMode ile planı onaylat.', args };
|
|
604
625
|
}
|
|
626
|
+
// 3.5) Komut güvenliği (Bash/PowerShell) — cmdsec: deny→blokla, allow→izinsiz, confirm→izin akışı
|
|
627
|
+
if ((call.name === 'Bash' || call.name === 'PowerShell') && args && args.command) {
|
|
628
|
+
const chk = checkCommand(String(args.command));
|
|
629
|
+
if (chk.decision === 'deny') {
|
|
630
|
+
return { ok: false, output: `⛔ Güvenlik: komut engellendi — ${chk.reason || 'tehlikeli komut'}`, args };
|
|
631
|
+
}
|
|
632
|
+
if (chk.decision === 'allow') {
|
|
633
|
+
const res = await executeTool(call.name, args);
|
|
634
|
+
return { ...res, args };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
605
637
|
// 4) İzin (gerekiyorsa)
|
|
606
638
|
if (needsPermission(call.name) && hooks?.confirm) {
|
|
607
639
|
const decision = await hooks.confirm(call, args);
|
|
@@ -651,13 +683,53 @@ export async function executeToolCalls(calls, hooks) {
|
|
|
651
683
|
return results;
|
|
652
684
|
}
|
|
653
685
|
// Alt-agent'a verilecek araç seti (özyineleme/iç içe agent engellenir).
|
|
654
|
-
|
|
655
|
-
|
|
686
|
+
// allow verilirse yalnız o adlardaki araçlar bırakılır (uzman ajan tool-kısıtı).
|
|
687
|
+
function subAgentTools(allow) {
|
|
688
|
+
let list = allToolSchemas().filter((t) => t.function.name !== 'Agent' && t.function.name !== 'Skill');
|
|
689
|
+
if (allow && allow.length) {
|
|
690
|
+
const set = new Set(allow);
|
|
691
|
+
const filtered = list.filter((t) => set.has(t.function.name));
|
|
692
|
+
if (filtered.length)
|
|
693
|
+
list = filtered; // boş kalırsa kısıtı yok say (güvenli geri dönüş)
|
|
694
|
+
}
|
|
695
|
+
return list;
|
|
656
696
|
}
|
|
657
697
|
const SUBAGENT_SYSTEM = 'You are a WormClaude sub-agent: an autonomous worker spawned to complete one specific task. ' +
|
|
658
698
|
'Use your tools (Bash, Read, Write, Edit, Glob, Grep, WebFetch, TaskOutput) to do the work, ' +
|
|
659
699
|
'then return a concise final report of what you did and any results requested. ' +
|
|
660
700
|
'You have no memory beyond this task. Be thorough and finish the task end-to-end.';
|
|
701
|
+
// Levenshtein mesafesi (küçük, dep'siz) — bilinmeyen tool adına en yakın öneriler.
|
|
702
|
+
function levenshtein(a, b) {
|
|
703
|
+
const m = a.length, n = b.length;
|
|
704
|
+
if (!m)
|
|
705
|
+
return n;
|
|
706
|
+
if (!n)
|
|
707
|
+
return m;
|
|
708
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
709
|
+
let cur = new Array(n + 1);
|
|
710
|
+
for (let i = 1; i <= m; i++) {
|
|
711
|
+
cur[0] = i;
|
|
712
|
+
for (let j = 1; j <= n; j++) {
|
|
713
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
714
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
|
|
715
|
+
}
|
|
716
|
+
[prev, cur] = [cur, prev];
|
|
717
|
+
}
|
|
718
|
+
return prev[n];
|
|
719
|
+
}
|
|
720
|
+
// Bilinmeyen tool adı için " Did you mean 'X'?" önerisi (en yakın ≤3, makul mesafe).
|
|
721
|
+
function suggestTools(unknown) {
|
|
722
|
+
const names = allToolSchemas().map((t) => t.function.name);
|
|
723
|
+
const ranked = names
|
|
724
|
+
.map((n) => ({ n, d: levenshtein(unknown.toLowerCase(), n.toLowerCase()) }))
|
|
725
|
+
.filter((x) => x.d <= Math.max(3, Math.floor(unknown.length / 2)))
|
|
726
|
+
.sort((a, b) => a.d - b.d)
|
|
727
|
+
.slice(0, 3)
|
|
728
|
+
.map((x) => `"${x.n}"`);
|
|
729
|
+
if (!ranked.length)
|
|
730
|
+
return '';
|
|
731
|
+
return ranked.length > 1 ? ` Did you mean one of: ${ranked.join(', ')}?` : ` Did you mean ${ranked[0]}?`;
|
|
732
|
+
}
|
|
661
733
|
export function toolLabel(name, args) {
|
|
662
734
|
try {
|
|
663
735
|
if (name === 'Bash')
|
|
@@ -675,9 +747,11 @@ export function toolLabel(name, args) {
|
|
|
675
747
|
if (name === 'WebFetch')
|
|
676
748
|
return `WebFetch(${args.url})`;
|
|
677
749
|
if (name === 'Agent')
|
|
678
|
-
return `Agent(${args.description || ''}${args.run_in_background ? ', bg' : ''})`;
|
|
750
|
+
return `Agent(${args.description || ''}${args.subagent_type ? ':' + args.subagent_type : ''}${args.run_in_background ? ', bg' : ''})`;
|
|
679
751
|
if (name === 'TaskOutput')
|
|
680
752
|
return `TaskOutput(${args.task_id})`;
|
|
753
|
+
if (name === 'SaveMemory')
|
|
754
|
+
return `SaveMemory(${String(args.fact || '').slice(0, 50)})`;
|
|
681
755
|
if (name === 'Skill')
|
|
682
756
|
return `Skill(${args.name})`;
|
|
683
757
|
if (name === 'WebSearch')
|
|
@@ -742,9 +816,9 @@ function walk(dir, out, depth = 0) {
|
|
|
742
816
|
function globToRegex(pattern) {
|
|
743
817
|
let re = pattern
|
|
744
818
|
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
745
|
-
.replace(/\*\*/g, '
|
|
819
|
+
.replace(/\*\*/g, '\u0000')
|
|
746
820
|
.replace(/\*/g, '[^/\\\\]*')
|
|
747
|
-
.replace(
|
|
821
|
+
.replace(/\u0000/g, '.*')
|
|
748
822
|
.replace(/\?/g, '.');
|
|
749
823
|
return new RegExp(re + '$', 'i');
|
|
750
824
|
}
|
|
@@ -764,6 +838,23 @@ const TYPE_EXT = {
|
|
|
764
838
|
sh: ['sh', 'bash', 'zsh'],
|
|
765
839
|
};
|
|
766
840
|
// ── Executor ──────────────────────────────────────────────────────────────────
|
|
841
|
+
// Edit/Write icin +/- satir ozeti (Diff paketiyle).
|
|
842
|
+
function diffStat(oldStr, newStr) {
|
|
843
|
+
try {
|
|
844
|
+
let added = 0, removed = 0;
|
|
845
|
+
for (const part of Diff.diffLines(oldStr || '', newStr || '')) {
|
|
846
|
+
const n = part.count || (part.value ? part.value.split('\n').filter(Boolean).length : 0);
|
|
847
|
+
if (part.added)
|
|
848
|
+
added += n;
|
|
849
|
+
else if (part.removed)
|
|
850
|
+
removed += n;
|
|
851
|
+
}
|
|
852
|
+
return (added || removed) ? ` (+${added} -${removed})` : '';
|
|
853
|
+
}
|
|
854
|
+
catch {
|
|
855
|
+
return '';
|
|
856
|
+
}
|
|
857
|
+
}
|
|
767
858
|
export async function executeTool(name, args) {
|
|
768
859
|
try {
|
|
769
860
|
if (name === 'See') {
|
|
@@ -809,8 +900,12 @@ export async function executeTool(name, args) {
|
|
|
809
900
|
return { ok: true, output: (out || '(no output)').slice(0, 20000) };
|
|
810
901
|
}
|
|
811
902
|
if (name === 'Agent') {
|
|
903
|
+
// Uzman ajan seçimi: subagent_type verilirse özel prompt + tool-kısıtı uygulanır.
|
|
904
|
+
const def = resolveSubagent(args.subagent_type);
|
|
905
|
+
const sysPrompt = def ? def.system : SUBAGENT_SYSTEM;
|
|
906
|
+
const subTools = subAgentTools(def?.tools);
|
|
812
907
|
const subMessages = [
|
|
813
|
-
{ role: 'system', content:
|
|
908
|
+
{ role: 'system', content: sysPrompt },
|
|
814
909
|
{ role: 'user', content: String(args.prompt || '') },
|
|
815
910
|
];
|
|
816
911
|
if (args.run_in_background) {
|
|
@@ -820,7 +915,7 @@ export async function executeTool(name, args) {
|
|
|
820
915
|
const { finalText } = await runAgentLoop({
|
|
821
916
|
config: cfg(),
|
|
822
917
|
messages: subMessages,
|
|
823
|
-
tools:
|
|
918
|
+
tools: subTools,
|
|
824
919
|
executeTool,
|
|
825
920
|
hooks: {
|
|
826
921
|
onText: (t) => tasks.append(task.id, t),
|
|
@@ -840,7 +935,7 @@ export async function executeTool(name, args) {
|
|
|
840
935
|
const { finalText } = await runAgentLoop({
|
|
841
936
|
config: cfg(),
|
|
842
937
|
messages: subMessages,
|
|
843
|
-
tools:
|
|
938
|
+
tools: subTools,
|
|
844
939
|
executeTool,
|
|
845
940
|
});
|
|
846
941
|
return { ok: true, output: finalText || '(sub-agent returned no text)' };
|
|
@@ -852,6 +947,16 @@ export async function executeTool(name, args) {
|
|
|
852
947
|
const body = t.output.slice(-9000) || '(no output yet)';
|
|
853
948
|
return { ok: true, output: `[${t.id}] ${t.kind} · ${t.status} · ${t.label}\n\n${body}` };
|
|
854
949
|
}
|
|
950
|
+
if (name === 'SaveMemory') {
|
|
951
|
+
try {
|
|
952
|
+
const scope = args.scope === 'global' ? 'global' : 'project';
|
|
953
|
+
const file = saveMemoryFact(String(args.fact || ''), scope);
|
|
954
|
+
return { ok: true, output: `Hatıra kaydedildi (${scope}): "${String(args.fact).trim()}" → ${file}` };
|
|
955
|
+
}
|
|
956
|
+
catch (e) {
|
|
957
|
+
return { ok: false, output: `Hatıra kaydedilemedi: ${e?.message || e}` };
|
|
958
|
+
}
|
|
959
|
+
}
|
|
855
960
|
if (name === 'Read') {
|
|
856
961
|
const fp = args.file_path;
|
|
857
962
|
if (!fs.existsSync(fp))
|
|
@@ -877,9 +982,16 @@ export async function executeTool(name, args) {
|
|
|
877
982
|
if (fs.existsSync(fp) && !readFiles.has(norm(fp)))
|
|
878
983
|
return { ok: false, output: 'Error: existing file must be read first. Use the Read tool before overwriting.' };
|
|
879
984
|
fs.mkdirSync(path.dirname(path.resolve(fp)), { recursive: true });
|
|
880
|
-
|
|
985
|
+
const _wnew = args.content ?? '';
|
|
986
|
+
const _wold = (() => { try {
|
|
987
|
+
return fs.readFileSync(fp, 'utf8');
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
return '';
|
|
991
|
+
} })();
|
|
992
|
+
fs.writeFileSync(fp, _wnew);
|
|
881
993
|
readFiles.add(norm(fp));
|
|
882
|
-
return { ok: true, output: `Wrote ${fp} (${
|
|
994
|
+
return { ok: true, output: `Wrote ${fp} (${_wnew.length} chars)${diffStat(_wold, _wnew)}` };
|
|
883
995
|
}
|
|
884
996
|
if (name === 'Edit') {
|
|
885
997
|
const fp = args.file_path;
|
|
@@ -900,9 +1012,10 @@ export async function executeTool(name, args) {
|
|
|
900
1012
|
ok: false,
|
|
901
1013
|
output: `Error: old_string is not unique (${count} matches). Provide more surrounding context or set replace_all: true.`,
|
|
902
1014
|
};
|
|
1015
|
+
const _ebefore = c;
|
|
903
1016
|
c = args.replace_all ? c.split(oldStr).join(newStr) : c.replace(oldStr, newStr);
|
|
904
1017
|
fs.writeFileSync(fp, c);
|
|
905
|
-
return { ok: true, output: `Edited ${fp}${args.replace_all ? ` (${count} occurrences)` : ''}` };
|
|
1018
|
+
return { ok: true, output: `Edited ${fp}${args.replace_all ? ` (${count} occurrences)` : ''}${diffStat(_ebefore, c)}` };
|
|
906
1019
|
}
|
|
907
1020
|
if (name === 'Glob') {
|
|
908
1021
|
const base = args.path || process.cwd();
|
|
@@ -1182,7 +1295,7 @@ export async function executeTool(name, args) {
|
|
|
1182
1295
|
}
|
|
1183
1296
|
if (name.startsWith('mcp__'))
|
|
1184
1297
|
return await callMcpTool(name, args);
|
|
1185
|
-
return { ok: false, output: `Unknown tool: ${name}
|
|
1298
|
+
return { ok: false, output: `Unknown tool: ${name}.${suggestTools(name)} Use exactly the registered tool names.` };
|
|
1186
1299
|
}
|
|
1187
1300
|
catch (e) {
|
|
1188
1301
|
return { ok: false, output: `Error: ${e?.message || String(e)}` };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wormclaude",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
17
|
+
"@types/diff": "^7.0.2",
|
|
18
|
+
"diff": "^9.0.0",
|
|
17
19
|
"ink": "^5.0.1",
|
|
18
20
|
"ink-spinner": "^5.0.0",
|
|
19
21
|
"ink-text-input": "^6.0.0",
|