wave-agent-sdk 0.11.6 → 0.11.7
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/builtin/skills/init/SKILL.md +2 -0
- package/builtin/skills/settings/SKILLS.md +3 -2
- package/builtin/skills/settings/SUBAGENTS.md +1 -3
- package/dist/agent.d.ts +6 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +18 -1
- package/dist/constants/tools.d.ts +1 -1
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +1 -1
- package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
- package/dist/managers/MemoryRuleManager.js +1 -9
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +22 -3
- package/dist/managers/messageManager.d.ts +13 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +62 -34
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +4 -2
- package/dist/managers/slashCommandManager.d.ts +2 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +98 -4
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +8 -2
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +5 -0
- package/dist/services/GitService.d.ts +1 -0
- package/dist/services/GitService.d.ts.map +1 -1
- package/dist/services/GitService.js +16 -0
- package/dist/services/MarketplaceService.d.ts +7 -0
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +321 -252
- package/dist/services/aiService.d.ts +34 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +124 -1
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +18 -0
- package/dist/tools/agentTool.js +3 -3
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +4 -4
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +2 -0
- package/dist/tools/globTool.d.ts.map +1 -1
- package/dist/tools/globTool.js +15 -3
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +38 -12
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +61 -0
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/types.d.ts +16 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts +3 -0
- package/dist/tools/webFetchTool.d.ts.map +1 -0
- package/dist/tools/webFetchTool.js +171 -0
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +2 -0
- package/dist/types/commands.d.ts +1 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/bashParser.d.ts +14 -0
- package/dist/utils/bashParser.d.ts.map +1 -1
- package/dist/utils/bashParser.js +243 -142
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +7 -0
- package/dist/utils/fileUtils.d.ts +8 -0
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +52 -0
- package/dist/utils/messageOperations.d.ts +12 -3
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +77 -9
- package/package.json +4 -2
- package/src/agent.ts +19 -1
- package/src/constants/tools.ts +1 -1
- package/src/managers/MemoryRuleManager.ts +1 -10
- package/src/managers/aiManager.ts +23 -3
- package/src/managers/messageManager.ts +76 -38
- package/src/managers/pluginManager.ts +4 -2
- package/src/managers/slashCommandManager.ts +130 -4
- package/src/managers/toolManager.ts +11 -2
- package/src/prompts/index.ts +6 -0
- package/src/services/GitService.ts +20 -0
- package/src/services/MarketplaceService.ts +397 -324
- package/src/services/aiService.ts +197 -1
- package/src/services/initializationService.ts +38 -0
- package/src/tools/agentTool.ts +3 -3
- package/src/tools/bashTool.ts +3 -4
- package/src/tools/editTool.ts +3 -0
- package/src/tools/globTool.ts +16 -3
- package/src/tools/grepTool.ts +41 -13
- package/src/tools/readTool.ts +69 -0
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/types.ts +13 -0
- package/src/tools/webFetchTool.ts +194 -0
- package/src/tools/writeTool.ts +3 -0
- package/src/types/commands.ts +1 -1
- package/src/types/messaging.ts +1 -0
- package/src/utils/bashParser.ts +268 -157
- package/src/utils/convertMessagesForAPI.ts +8 -0
- package/src/utils/fileUtils.ts +69 -0
- package/src/utils/messageOperations.ts +84 -9
- package/dist/tools/taskOutputTool.d.ts +0 -3
- package/dist/tools/taskOutputTool.d.ts.map +0 -1
- package/dist/tools/taskOutputTool.js +0 -198
- package/src/tools/taskOutputTool.ts +0 -222
package/src/utils/bashParser.ts
CHANGED
|
@@ -456,14 +456,160 @@ export const DANGEROUS_COMMANDS = [
|
|
|
456
456
|
"chown",
|
|
457
457
|
"sh",
|
|
458
458
|
"bash",
|
|
459
|
+
"zsh",
|
|
460
|
+
"fish",
|
|
461
|
+
"pwsh",
|
|
462
|
+
"cmd.exe",
|
|
463
|
+
"powershell.exe",
|
|
459
464
|
"sudo",
|
|
460
465
|
"dd",
|
|
461
466
|
"apt",
|
|
462
467
|
"apt-get",
|
|
463
468
|
"yum",
|
|
464
469
|
"dnf",
|
|
470
|
+
"ssh",
|
|
471
|
+
"scp",
|
|
472
|
+
"sftp",
|
|
473
|
+
"ftp",
|
|
474
|
+
"telnet",
|
|
475
|
+
"nc",
|
|
476
|
+
"netcat",
|
|
465
477
|
];
|
|
466
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Registry of commands and their expected subcommand depth for smart prefix extraction.
|
|
481
|
+
* For example, 'git: 2' means 'git commit' is a valid prefix, but 'git' alone is not.
|
|
482
|
+
* Multi-word keys can be used for more specific rules.
|
|
483
|
+
*/
|
|
484
|
+
export interface ToolRule {
|
|
485
|
+
depth: number;
|
|
486
|
+
scopeFlags?: string[];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export const TOOL_RULES: Record<string, ToolRule> = {
|
|
490
|
+
// Node/JS
|
|
491
|
+
npm: { depth: 2, scopeFlags: ["--prefix", "-C", "--registry"] },
|
|
492
|
+
"npm run": { depth: 3, scopeFlags: ["--prefix", "-C", "--registry"] },
|
|
493
|
+
pnpm: { depth: 2, scopeFlags: ["-C", "--dir", "-F", "--filter"] },
|
|
494
|
+
"pnpm run": { depth: 3, scopeFlags: ["-C", "--dir", "-F", "--filter"] },
|
|
495
|
+
yarn: { depth: 2, scopeFlags: ["workspace", "--cwd"] },
|
|
496
|
+
"yarn run": { depth: 3, scopeFlags: ["workspace", "--cwd"] },
|
|
497
|
+
"yarn workspace": { depth: 4, scopeFlags: ["--cwd"] },
|
|
498
|
+
bun: { depth: 2 },
|
|
499
|
+
"bun run": { depth: 3 },
|
|
500
|
+
deno: { depth: 2 },
|
|
501
|
+
"deno run": { depth: 3 },
|
|
502
|
+
"deno task": { depth: 3 },
|
|
503
|
+
|
|
504
|
+
// Git
|
|
505
|
+
git: {
|
|
506
|
+
depth: 2,
|
|
507
|
+
scopeFlags: ["-C", "-c", "--directory", "--work-tree", "--git-dir"],
|
|
508
|
+
},
|
|
509
|
+
|
|
510
|
+
// Python
|
|
511
|
+
python: { depth: 2 },
|
|
512
|
+
python3: { depth: 2 },
|
|
513
|
+
"python -m": { depth: 2 },
|
|
514
|
+
"python3 -m": { depth: 2 },
|
|
515
|
+
"python -m pip install": { depth: 3 },
|
|
516
|
+
"python3 -m pip install": { depth: 3 },
|
|
517
|
+
pip: { depth: 2 },
|
|
518
|
+
pip3: { depth: 2 },
|
|
519
|
+
poetry: { depth: 2 },
|
|
520
|
+
conda: { depth: 2 },
|
|
521
|
+
|
|
522
|
+
// Java
|
|
523
|
+
mvn: { depth: 2 },
|
|
524
|
+
gradle: { depth: 2 },
|
|
525
|
+
java: { depth: 1 },
|
|
526
|
+
"java -jar": { depth: 1 },
|
|
527
|
+
|
|
528
|
+
// Rust & Go
|
|
529
|
+
cargo: { depth: 2 },
|
|
530
|
+
go: { depth: 2 },
|
|
531
|
+
|
|
532
|
+
// Containers & Infrastructure
|
|
533
|
+
docker: { depth: 2 },
|
|
534
|
+
"docker-compose": { depth: 2 },
|
|
535
|
+
kubectl: { depth: 2 },
|
|
536
|
+
terraform: { depth: 2 },
|
|
537
|
+
gcloud: { depth: 2 },
|
|
538
|
+
"gcloud compute": { depth: 4 },
|
|
539
|
+
"gcloud container": { depth: 4 },
|
|
540
|
+
aws: { depth: 2 },
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Registry of dangerous subcommands for specific tools.
|
|
545
|
+
*/
|
|
546
|
+
export const DANGEROUS_SUBCOMMANDS: Record<string, string[]> = {
|
|
547
|
+
docker: ["rm", "rmi", "system", "volume", "network", "image", "container"],
|
|
548
|
+
git: ["reset", "clean"],
|
|
549
|
+
npm: ["uninstall", "un", "remove", "rm"],
|
|
550
|
+
pnpm: ["uninstall", "un", "remove", "rm"],
|
|
551
|
+
yarn: ["remove"],
|
|
552
|
+
deno: ["uninstall"],
|
|
553
|
+
bun: ["remove", "rm"],
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Heuristic to determine if a flag takes an argument.
|
|
558
|
+
* If nextArg doesn't start with '-' and isn't a known subcommand, assume it's a flag value.
|
|
559
|
+
*/
|
|
560
|
+
function flagTakesArg(flag: string, nextArg: string | undefined): boolean {
|
|
561
|
+
if (!nextArg) return false;
|
|
562
|
+
if (nextArg.startsWith("-")) return false;
|
|
563
|
+
// If it's a common subcommand, it's probably not a flag argument
|
|
564
|
+
const commonSubcommands = [
|
|
565
|
+
"install",
|
|
566
|
+
"add",
|
|
567
|
+
"remove",
|
|
568
|
+
"run",
|
|
569
|
+
"test",
|
|
570
|
+
"build",
|
|
571
|
+
"status",
|
|
572
|
+
"diff",
|
|
573
|
+
"commit",
|
|
574
|
+
"push",
|
|
575
|
+
"pull",
|
|
576
|
+
"checkout",
|
|
577
|
+
"log",
|
|
578
|
+
"fetch",
|
|
579
|
+
"merge",
|
|
580
|
+
"rebase",
|
|
581
|
+
];
|
|
582
|
+
if (commonSubcommands.includes(nextArg)) return false;
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Detects if an argument is a file path or URL.
|
|
588
|
+
*/
|
|
589
|
+
function shouldStopAtArg(arg: string): boolean {
|
|
590
|
+
if (!arg) return false;
|
|
591
|
+
// URLs
|
|
592
|
+
if (/^(https?|ftp|ssh|git):\/\//.test(arg)) return true;
|
|
593
|
+
// File paths (starts with /, ./, ../, or ~/)
|
|
594
|
+
if (
|
|
595
|
+
arg.startsWith("/") ||
|
|
596
|
+
arg.startsWith("./") ||
|
|
597
|
+
arg.startsWith("../") ||
|
|
598
|
+
arg.startsWith("~/")
|
|
599
|
+
)
|
|
600
|
+
return true;
|
|
601
|
+
// Common file extensions (but not scoped packages or common subcommands)
|
|
602
|
+
if (
|
|
603
|
+
/\.(ts|js|py|sh|md|txt|json|yml|yaml|html|css|go|rs|java|cpp|c|h|php|rb|pl|sql)$/.test(
|
|
604
|
+
arg,
|
|
605
|
+
) &&
|
|
606
|
+
!arg.includes("@") &&
|
|
607
|
+
!arg.includes("/")
|
|
608
|
+
)
|
|
609
|
+
return true;
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
|
|
467
613
|
/**
|
|
468
614
|
* Extracts a "smart prefix" from a bash command based on common developer tools.
|
|
469
615
|
* Returns null if the command is blacklisted or cannot be safely prefix-matched.
|
|
@@ -475,187 +621,152 @@ export function getSmartPrefix(command: string): string | null {
|
|
|
475
621
|
// For now, we only support prefix matching for single commands or the first command in a chain
|
|
476
622
|
// to keep it simple and safe.
|
|
477
623
|
const firstCommand = parts[0];
|
|
478
|
-
let stripped = stripRedirections(stripEnvVars(firstCommand));
|
|
479
624
|
|
|
480
|
-
//
|
|
481
|
-
if (
|
|
482
|
-
stripped = stripped.substring(5).trim();
|
|
483
|
-
}
|
|
625
|
+
// Safety check: don't allow heredoc writes
|
|
626
|
+
if (isBashHeredocWrite(firstCommand)) return null;
|
|
484
627
|
|
|
628
|
+
const stripped = stripRedirections(stripEnvVars(firstCommand));
|
|
485
629
|
const tokens = stripped.split(/\s+/);
|
|
486
630
|
if (tokens.length === 0) return null;
|
|
487
631
|
|
|
488
|
-
const
|
|
489
|
-
|
|
632
|
+
const prefixParts: string[] = [];
|
|
633
|
+
let i = 0;
|
|
634
|
+
|
|
635
|
+
// Handle prefix tools like sudo
|
|
636
|
+
const prefixTools = ["sudo", "time", "stdbuf", "timeout"];
|
|
637
|
+
while (i < tokens.length && prefixTools.includes(tokens[i])) {
|
|
638
|
+
prefixParts.push(tokens[i]);
|
|
639
|
+
i++;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (i >= tokens.length) return null;
|
|
490
643
|
|
|
644
|
+
const exe = tokens[i];
|
|
491
645
|
// Blacklist - Hard blacklist for dangerous commands
|
|
492
646
|
if (DANGEROUS_COMMANDS.includes(exe)) return null;
|
|
493
647
|
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
prefixParts.push(tokens[currentIdx], tokens[currentIdx + 1]);
|
|
506
|
-
currentIdx += 2;
|
|
648
|
+
// Find the longest matching rule in TOOL_RULES
|
|
649
|
+
let bestRuleKey = "";
|
|
650
|
+
let rule: ToolRule | undefined;
|
|
651
|
+
|
|
652
|
+
for (const [key, r] of Object.entries(TOOL_RULES)) {
|
|
653
|
+
const keyTokens = key.split(/\s+/);
|
|
654
|
+
let match = true;
|
|
655
|
+
for (let j = 0; j < keyTokens.length; j++) {
|
|
656
|
+
if (tokens[i + j] !== keyTokens[j]) {
|
|
657
|
+
match = false;
|
|
658
|
+
break;
|
|
507
659
|
}
|
|
508
|
-
} else if (
|
|
509
|
-
exe === "npm" &&
|
|
510
|
-
(tokens[currentIdx] === "--prefix" || tokens[currentIdx] === "-C") &&
|
|
511
|
-
tokens[currentIdx + 1]
|
|
512
|
-
) {
|
|
513
|
-
prefixParts.push(tokens[currentIdx], tokens[currentIdx + 1]);
|
|
514
|
-
currentIdx += 2;
|
|
515
|
-
} else if (
|
|
516
|
-
exe === "yarn" &&
|
|
517
|
-
tokens[currentIdx] === "workspace" &&
|
|
518
|
-
tokens[currentIdx + 1]
|
|
519
|
-
) {
|
|
520
|
-
prefixParts.push(tokens[currentIdx], tokens[currentIdx + 1]);
|
|
521
|
-
currentIdx += 2;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const subCommand = tokens[currentIdx];
|
|
525
|
-
const safeSubcommands = [
|
|
526
|
-
"install",
|
|
527
|
-
"i",
|
|
528
|
-
"add",
|
|
529
|
-
"remove",
|
|
530
|
-
"rm",
|
|
531
|
-
"uninstall",
|
|
532
|
-
"un",
|
|
533
|
-
"test",
|
|
534
|
-
"t",
|
|
535
|
-
"build",
|
|
536
|
-
"start",
|
|
537
|
-
"dev",
|
|
538
|
-
];
|
|
539
|
-
|
|
540
|
-
if (safeSubcommands.includes(subCommand)) {
|
|
541
|
-
prefixParts.push(subCommand);
|
|
542
|
-
return prefixParts.join(" ");
|
|
543
660
|
}
|
|
544
|
-
if (
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
) {
|
|
548
|
-
prefixParts.push(subCommand, tokens[currentIdx + 1]);
|
|
549
|
-
return prefixParts.join(" ");
|
|
661
|
+
if (match && key.length > bestRuleKey.length) {
|
|
662
|
+
bestRuleKey = key;
|
|
663
|
+
rule = r;
|
|
550
664
|
}
|
|
551
|
-
return null;
|
|
552
665
|
}
|
|
553
666
|
|
|
554
|
-
//
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
667
|
+
// If no rule found, we don't suggest a prefix
|
|
668
|
+
if (!rule) return null;
|
|
669
|
+
|
|
670
|
+
const depth = rule.depth;
|
|
671
|
+
const scopeFlags = rule.scopeFlags || [];
|
|
672
|
+
let currentDepth = 0;
|
|
673
|
+
|
|
674
|
+
// Safety check: only allow safe subcommands for git
|
|
675
|
+
const safeGitSubcommands = [
|
|
676
|
+
"commit",
|
|
677
|
+
"push",
|
|
678
|
+
"pull",
|
|
679
|
+
"checkout",
|
|
680
|
+
"add",
|
|
681
|
+
"status",
|
|
682
|
+
"diff",
|
|
683
|
+
"branch",
|
|
684
|
+
"merge",
|
|
685
|
+
"rebase",
|
|
686
|
+
"log",
|
|
687
|
+
"fetch",
|
|
688
|
+
"remote",
|
|
689
|
+
"stash",
|
|
690
|
+
];
|
|
691
|
+
|
|
692
|
+
const destructiveGitFlags = [
|
|
693
|
+
"-d",
|
|
694
|
+
"-D",
|
|
695
|
+
"--delete",
|
|
696
|
+
"--hard",
|
|
697
|
+
"--force",
|
|
698
|
+
"-f",
|
|
699
|
+
];
|
|
700
|
+
|
|
701
|
+
// Global safety check: scan ALL tokens for dangerous flags/subcommands
|
|
702
|
+
for (let j = i; j < tokens.length; j++) {
|
|
703
|
+
const token = tokens[j];
|
|
704
|
+
if (token.startsWith("-")) {
|
|
705
|
+
if (exe === "git" && destructiveGitFlags.includes(token)) return null;
|
|
706
|
+
} else {
|
|
707
|
+
if (DANGEROUS_SUBCOMMANDS[exe]?.includes(token)) return null;
|
|
593
708
|
}
|
|
594
|
-
return null;
|
|
595
709
|
}
|
|
596
710
|
|
|
597
|
-
//
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
711
|
+
// Include all tokens from the best matching rule
|
|
712
|
+
const ruleTokens = bestRuleKey.split(/\s+/);
|
|
713
|
+
for (let j = 0; j < ruleTokens.length; j++) {
|
|
714
|
+
const token = tokens[i];
|
|
715
|
+
if (!token) break;
|
|
716
|
+
|
|
717
|
+
if (token.startsWith("-")) {
|
|
718
|
+
if (exe === "git" && destructiveGitFlags.includes(token)) return null;
|
|
719
|
+
} else {
|
|
720
|
+
if (DANGEROUS_SUBCOMMANDS[exe]?.includes(token)) return null;
|
|
721
|
+
if (
|
|
722
|
+
exe === "git" &&
|
|
723
|
+
currentDepth > 0 &&
|
|
724
|
+
!safeGitSubcommands.includes(token)
|
|
725
|
+
) {
|
|
726
|
+
return null;
|
|
605
727
|
}
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
if (["install", "add", "remove", "test", "run"].includes(sub)) {
|
|
609
|
-
return `${exe} ${sub}`;
|
|
728
|
+
currentDepth++;
|
|
610
729
|
}
|
|
611
|
-
return null;
|
|
612
|
-
}
|
|
613
730
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
if (sub && !sub.startsWith("-")) {
|
|
617
|
-
return `${exe} ${sub}`;
|
|
618
|
-
}
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
if (exe === "java") {
|
|
622
|
-
if (sub === "-jar") return "java -jar";
|
|
623
|
-
return "java";
|
|
731
|
+
prefixParts.push(token);
|
|
732
|
+
i++;
|
|
624
733
|
}
|
|
625
734
|
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
return `${exe} ${sub}`;
|
|
630
|
-
}
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
if (exe === "go") {
|
|
634
|
-
if (["build", "test", "run", "get", "mod"].includes(sub)) {
|
|
635
|
-
return `${exe} ${sub}`;
|
|
636
|
-
}
|
|
637
|
-
return null;
|
|
638
|
-
}
|
|
735
|
+
// Continue until we reach the required depth
|
|
736
|
+
while (i < tokens.length && currentDepth < depth) {
|
|
737
|
+
const token = tokens[i];
|
|
639
738
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
739
|
+
if (token.startsWith("-")) {
|
|
740
|
+
// Safety checks for flags
|
|
741
|
+
if (exe === "git" && destructiveGitFlags.includes(token)) return null;
|
|
742
|
+
|
|
743
|
+
prefixParts.push(token);
|
|
744
|
+
if (scopeFlags.includes(token) || flagTakesArg(token, tokens[i + 1])) {
|
|
745
|
+
if (i + 1 < tokens.length) {
|
|
746
|
+
prefixParts.push(tokens[++i]);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
750
|
+
// Safety checks for subcommands
|
|
751
|
+
if (DANGEROUS_SUBCOMMANDS[exe]?.includes(token)) return null;
|
|
752
|
+
if (
|
|
753
|
+
exe === "git" &&
|
|
754
|
+
currentDepth > 0 &&
|
|
755
|
+
!safeGitSubcommands.includes(token)
|
|
756
|
+
) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Stop at data/paths
|
|
761
|
+
if (shouldStopAtArg(token) && currentDepth > 0) break;
|
|
762
|
+
|
|
763
|
+
prefixParts.push(token);
|
|
764
|
+
currentDepth++;
|
|
656
765
|
}
|
|
657
|
-
|
|
766
|
+
i++;
|
|
658
767
|
}
|
|
659
768
|
|
|
660
|
-
return null;
|
|
769
|
+
if (currentDepth < depth) return null;
|
|
770
|
+
|
|
771
|
+
return prefixParts.join(" ");
|
|
661
772
|
}
|
|
@@ -230,6 +230,14 @@ export function convertMessagesForAPI(
|
|
|
230
230
|
});
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
|
+
|
|
234
|
+
// If there is a tool block in user message, add its result
|
|
235
|
+
if (block.type === "tool" && block.stage === "end" && block.result) {
|
|
236
|
+
contentParts.push({
|
|
237
|
+
type: "text",
|
|
238
|
+
text: `<local-command-stdout>\n${stripAnsiColors(block.result)}\n</local-command-stdout>`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
233
241
|
});
|
|
234
242
|
|
|
235
243
|
// Only add user message if there is meaningful content
|
package/src/utils/fileUtils.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createReadStream } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { glob } from "glob";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Reads the first line of a file efficiently using Node.js readline.
|
|
@@ -158,3 +159,71 @@ export async function ensureGlobalGitIgnore(pattern: string): Promise<void> {
|
|
|
158
159
|
// Ignore errors
|
|
159
160
|
}
|
|
160
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Simple Levenshtein distance implementation
|
|
165
|
+
*/
|
|
166
|
+
function levenshtein(a: string, b: string): number {
|
|
167
|
+
const matrix = Array.from({ length: a.length + 1 }, () =>
|
|
168
|
+
Array.from({ length: b.length + 1 }, () => 0),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
|
|
172
|
+
for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
|
|
173
|
+
|
|
174
|
+
for (let i = 1; i <= a.length; i++) {
|
|
175
|
+
for (let j = 1; j <= b.length; j++) {
|
|
176
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
177
|
+
matrix[i][j] = Math.min(
|
|
178
|
+
matrix[i - 1][j] + 1,
|
|
179
|
+
matrix[i][j - 1] + 1,
|
|
180
|
+
matrix[i - 1][j - 1] + cost,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return matrix[a.length][b.length];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Suggests similar paths if a file is not found.
|
|
190
|
+
*/
|
|
191
|
+
export async function suggestPathUnderCwd(
|
|
192
|
+
targetPath: string,
|
|
193
|
+
workdir: string,
|
|
194
|
+
): Promise<string[]> {
|
|
195
|
+
try {
|
|
196
|
+
const allFiles = await glob("**/*", {
|
|
197
|
+
cwd: workdir,
|
|
198
|
+
nodir: true,
|
|
199
|
+
dot: true,
|
|
200
|
+
ignore: ["**/.git/**", "**/node_modules/**"],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const targetBasename = path.basename(targetPath);
|
|
204
|
+
const suggestions = allFiles
|
|
205
|
+
.map((file) => ({
|
|
206
|
+
path: file,
|
|
207
|
+
distance: levenshtein(targetBasename, path.basename(file)),
|
|
208
|
+
}))
|
|
209
|
+
.filter((item) => item.distance <= 3) // Threshold for similarity
|
|
210
|
+
.sort((a, b) => a.distance - b.distance)
|
|
211
|
+
.slice(0, 5)
|
|
212
|
+
.map((item) => item.path);
|
|
213
|
+
|
|
214
|
+
return suggestions;
|
|
215
|
+
} catch {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Uses fuzzy matching to find the intended file.
|
|
222
|
+
*/
|
|
223
|
+
export async function findSimilarFile(
|
|
224
|
+
targetPath: string,
|
|
225
|
+
workdir: string,
|
|
226
|
+
): Promise<string | null> {
|
|
227
|
+
const suggestions = await suggestPathUnderCwd(targetPath, workdir);
|
|
228
|
+
return suggestions.length > 0 ? suggestions[0] : null;
|
|
229
|
+
}
|