@xpert-ai/plugin-sdk 3.8.0 → 3.8.1
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/index.cjs.js +547 -38
- package/index.esm.js +547 -38
- package/package.json +1 -1
- package/src/lib/sandbox/protocol.d.ts +123 -5
- package/src/lib/sandbox/sandbox.d.ts +37 -4
package/index.esm.js
CHANGED
|
@@ -511,6 +511,7 @@ WorkflowNodeRegistry = __decorate([
|
|
|
511
511
|
])
|
|
512
512
|
], WorkflowNodeRegistry);
|
|
513
513
|
|
|
514
|
+
const COMMAND_METADATA$1 = '__command__';
|
|
514
515
|
/**
|
|
515
516
|
* Wrap Workflow Node Execution Command
|
|
516
517
|
*/ class WrapWorkflowNodeExecutionCommand extends Command {
|
|
@@ -521,6 +522,9 @@ WorkflowNodeRegistry = __decorate([
|
|
|
521
522
|
}
|
|
522
523
|
}
|
|
523
524
|
WrapWorkflowNodeExecutionCommand.type = '[Workflow] Wrap Workflow Node Execution';
|
|
525
|
+
Reflect.defineMetadata(COMMAND_METADATA$1, {
|
|
526
|
+
id: WrapWorkflowNodeExecutionCommand.type
|
|
527
|
+
}, WrapWorkflowNodeExecutionCommand);
|
|
524
528
|
|
|
525
529
|
const VECTOR_STORE_STRATEGY = 'VECTOR_STORE_STRATEGY';
|
|
526
530
|
const VectorStoreStrategy = (provider)=>applyDecorators(SetMetadata(VECTOR_STORE_STRATEGY, provider), SetMetadata(STRATEGY_META_KEY, VECTOR_STORE_STRATEGY));
|
|
@@ -2364,6 +2368,7 @@ function normalizeContextSize(value) {
|
|
|
2364
2368
|
return undefined;
|
|
2365
2369
|
}
|
|
2366
2370
|
|
|
2371
|
+
const COMMAND_METADATA = '__command__';
|
|
2367
2372
|
/**
|
|
2368
2373
|
* Get a Chat Model of copilot model and check it's token limitation, record the token usage
|
|
2369
2374
|
*/ class CreateModelClientCommand extends Command {
|
|
@@ -2374,6 +2379,9 @@ function normalizeContextSize(value) {
|
|
|
2374
2379
|
}
|
|
2375
2380
|
}
|
|
2376
2381
|
CreateModelClientCommand.type = '[AI Model] Create Model Client';
|
|
2382
|
+
Reflect.defineMetadata(COMMAND_METADATA, {
|
|
2383
|
+
id: CreateModelClientCommand.type
|
|
2384
|
+
}, CreateModelClientCommand);
|
|
2377
2385
|
|
|
2378
2386
|
const AGENT_MIDDLEWARE_STRATEGY = 'AGENT_MIDDLEWARE_STRATEGY';
|
|
2379
2387
|
const AgentMiddlewareStrategy = (provider)=>applyDecorators(SetMetadata(AGENT_MIDDLEWARE_STRATEGY, provider), SetMetadata(STRATEGY_META_KEY, AGENT_MIDDLEWARE_STRATEGY));
|
|
@@ -2630,18 +2638,78 @@ CancelConversationCommand.type = '[Chat Conversation] Cancel';
|
|
|
2630
2638
|
* only need to implement the execute() method.
|
|
2631
2639
|
*
|
|
2632
2640
|
* Requires Node.js 20+ on the sandbox host.
|
|
2633
|
-
*/
|
|
2641
|
+
*/ const MAX_LINE_LENGTH = 500;
|
|
2642
|
+
const MAX_GREP_LINE_LENGTH = 2000;
|
|
2643
|
+
const GREP_RESULT_LIMIT = 100;
|
|
2644
|
+
const GLOB_RESULT_LIMIT = 100;
|
|
2645
|
+
const WRITE_EXISTS_OUTPUT = "Error: File already exists";
|
|
2646
|
+
/**
|
|
2647
|
+
* UTF-8 safe Base64 encoding function.
|
|
2648
|
+
* Replaces btoa() which only supports Latin1 characters.
|
|
2649
|
+
*/ function utf8ToBase64(str) {
|
|
2650
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Format glob results into human-readable output.
|
|
2654
|
+
*/ function formatGlobOutput(files, pattern) {
|
|
2655
|
+
const limit = GLOB_RESULT_LIMIT;
|
|
2656
|
+
const truncated = files.length > limit;
|
|
2657
|
+
const finalFiles = truncated ? files.slice(0, limit) : files;
|
|
2658
|
+
if (finalFiles.length === 0) {
|
|
2659
|
+
return "No files found";
|
|
2660
|
+
}
|
|
2661
|
+
const lines = [];
|
|
2662
|
+
for (const file of finalFiles){
|
|
2663
|
+
lines.push(file.path);
|
|
2664
|
+
}
|
|
2665
|
+
if (truncated) {
|
|
2666
|
+
lines.push("");
|
|
2667
|
+
lines.push("(Results truncated. Consider using a more specific path or pattern.)");
|
|
2668
|
+
}
|
|
2669
|
+
return lines.join("\n");
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Format grep matches into human-readable output grouped by file.
|
|
2673
|
+
*/ function formatGrepOutput(matches) {
|
|
2674
|
+
const limit = GREP_RESULT_LIMIT;
|
|
2675
|
+
const truncated = matches.length > limit;
|
|
2676
|
+
const finalMatches = truncated ? matches.slice(0, limit) : matches;
|
|
2677
|
+
if (finalMatches.length === 0) {
|
|
2678
|
+
return "No matches found";
|
|
2679
|
+
}
|
|
2680
|
+
const lines = [
|
|
2681
|
+
`Found ${finalMatches.length} matches`
|
|
2682
|
+
];
|
|
2683
|
+
let currentFile = "";
|
|
2684
|
+
for (const match of finalMatches){
|
|
2685
|
+
if (currentFile !== match.path) {
|
|
2686
|
+
if (currentFile !== "") {
|
|
2687
|
+
lines.push("");
|
|
2688
|
+
}
|
|
2689
|
+
currentFile = match.path;
|
|
2690
|
+
lines.push(`${match.path}:`);
|
|
2691
|
+
}
|
|
2692
|
+
const text = match.text.length > MAX_GREP_LINE_LENGTH ? match.text.substring(0, MAX_GREP_LINE_LENGTH) + "..." : match.text;
|
|
2693
|
+
lines.push(` Line ${match.line}: ${text}`);
|
|
2694
|
+
}
|
|
2695
|
+
if (truncated) {
|
|
2696
|
+
lines.push("");
|
|
2697
|
+
lines.push("(Results truncated. Consider using a more specific path or pattern.)");
|
|
2698
|
+
}
|
|
2699
|
+
return lines.join("\n");
|
|
2700
|
+
}
|
|
2701
|
+
/**
|
|
2634
2702
|
* Node.js command template for glob operations.
|
|
2635
2703
|
* Uses web-standard atob() for base64 decoding.
|
|
2636
2704
|
*/ function buildGlobCommand(searchPath, pattern) {
|
|
2637
|
-
const pathB64 =
|
|
2638
|
-
const patternB64 =
|
|
2705
|
+
const pathB64 = utf8ToBase64(searchPath);
|
|
2706
|
+
const patternB64 = utf8ToBase64(pattern);
|
|
2639
2707
|
return `node -e "
|
|
2640
2708
|
const fs = require('fs');
|
|
2641
2709
|
const path = require('path');
|
|
2642
2710
|
|
|
2643
|
-
const searchPath =
|
|
2644
|
-
const pattern =
|
|
2711
|
+
const searchPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2712
|
+
const pattern = Buffer.from('${patternB64}', 'base64').toString('utf-8');
|
|
2645
2713
|
|
|
2646
2714
|
function globMatch(relativePath, pattern) {
|
|
2647
2715
|
const regexPattern = pattern
|
|
@@ -2686,12 +2754,12 @@ try {
|
|
|
2686
2754
|
/**
|
|
2687
2755
|
* Node.js command template for listing directory contents.
|
|
2688
2756
|
*/ function buildLsCommand(dirPath) {
|
|
2689
|
-
const pathB64 =
|
|
2757
|
+
const pathB64 = utf8ToBase64(dirPath);
|
|
2690
2758
|
return `node -e "
|
|
2691
2759
|
const fs = require('fs');
|
|
2692
2760
|
const path = require('path');
|
|
2693
2761
|
|
|
2694
|
-
const dirPath =
|
|
2762
|
+
const dirPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2695
2763
|
|
|
2696
2764
|
try {
|
|
2697
2765
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -2711,17 +2779,129 @@ try {
|
|
|
2711
2779
|
}
|
|
2712
2780
|
"`;
|
|
2713
2781
|
}
|
|
2782
|
+
const INDENTATION_SPACES = 2;
|
|
2783
|
+
const MAX_ENTRY_LENGTH = 500;
|
|
2784
|
+
/**
|
|
2785
|
+
* Node.js command template for recursive directory listing with depth control.
|
|
2786
|
+
*/ function buildListDirCommand(dirPath, offset, limit, depth) {
|
|
2787
|
+
const pathB64 = utf8ToBase64(dirPath);
|
|
2788
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;
|
|
2789
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 25;
|
|
2790
|
+
const safeDepth = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 2;
|
|
2791
|
+
return `node -e "
|
|
2792
|
+
const fs = require('fs');
|
|
2793
|
+
const path = require('path');
|
|
2794
|
+
|
|
2795
|
+
const dirPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2796
|
+
const offset = ${safeOffset};
|
|
2797
|
+
const limit = ${safeLimit};
|
|
2798
|
+
const maxDepth = ${safeDepth};
|
|
2799
|
+
const INDENTATION_SPACES = ${INDENTATION_SPACES};
|
|
2800
|
+
const MAX_ENTRY_LENGTH = ${MAX_ENTRY_LENGTH};
|
|
2801
|
+
|
|
2802
|
+
if (!path.isAbsolute(dirPath)) {
|
|
2803
|
+
console.error('Error: dir_path must be an absolute path');
|
|
2804
|
+
process.exit(1);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
if (!fs.existsSync(dirPath)) {
|
|
2808
|
+
console.error('Error: Directory not found');
|
|
2809
|
+
process.exit(1);
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
if (!fs.statSync(dirPath).isDirectory()) {
|
|
2813
|
+
console.error('Error: Path is not a directory');
|
|
2814
|
+
process.exit(1);
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
function collectEntries(currentPath, relativePrefix, remainingDepth, entries) {
|
|
2818
|
+
if (remainingDepth === 0) return;
|
|
2819
|
+
|
|
2820
|
+
try {
|
|
2821
|
+
const dirEntries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
2822
|
+
const sortedEntries = dirEntries
|
|
2823
|
+
.map(entry => {
|
|
2824
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
2825
|
+
const relativePath = path.join(relativePrefix, entry.name);
|
|
2826
|
+
const displayName = entry.name.length > MAX_ENTRY_LENGTH
|
|
2827
|
+
? entry.name.substring(0, MAX_ENTRY_LENGTH)
|
|
2828
|
+
: entry.name;
|
|
2829
|
+
|
|
2830
|
+
let kind = 'file';
|
|
2831
|
+
if (entry.isSymbolicLink()) kind = 'symlink';
|
|
2832
|
+
else if (entry.isDirectory()) kind = 'directory';
|
|
2833
|
+
|
|
2834
|
+
return {
|
|
2835
|
+
fullPath,
|
|
2836
|
+
relativePath,
|
|
2837
|
+
displayName,
|
|
2838
|
+
kind,
|
|
2839
|
+
depth: relativePrefix.split(path.sep).filter(Boolean).length
|
|
2840
|
+
};
|
|
2841
|
+
})
|
|
2842
|
+
.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
2843
|
+
|
|
2844
|
+
for (const entry of sortedEntries) {
|
|
2845
|
+
entries.push(entry);
|
|
2846
|
+
if (entry.kind === 'directory' && remainingDepth > 1) {
|
|
2847
|
+
collectEntries(entry.fullPath, entry.relativePath, remainingDepth - 1, entries);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
} catch (e) {
|
|
2851
|
+
// Skip unreadable directories
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
const allEntries = [];
|
|
2856
|
+
collectEntries(dirPath, '', maxDepth, allEntries);
|
|
2857
|
+
|
|
2858
|
+
if (allEntries.length === 0) {
|
|
2859
|
+
console.log('Absolute path: ' + dirPath);
|
|
2860
|
+
console.log('(Empty directory)');
|
|
2861
|
+
process.exit(0);
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
const startIndex = offset - 1;
|
|
2865
|
+
if (startIndex >= allEntries.length) {
|
|
2866
|
+
console.error('Error: offset exceeds directory entry count');
|
|
2867
|
+
process.exit(1);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
const endIndex = Math.min(startIndex + limit, allEntries.length);
|
|
2871
|
+
const selectedEntries = allEntries.slice(startIndex, endIndex);
|
|
2872
|
+
|
|
2873
|
+
console.log('Absolute path: ' + dirPath);
|
|
2874
|
+
|
|
2875
|
+
for (const entry of selectedEntries) {
|
|
2876
|
+
const indent = ' '.repeat(entry.depth * INDENTATION_SPACES);
|
|
2877
|
+
let name = entry.displayName;
|
|
2878
|
+
if (entry.kind === 'directory') name += '/';
|
|
2879
|
+
else if (entry.kind === 'symlink') name += '@';
|
|
2880
|
+
console.log(indent + name);
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
if (endIndex < allEntries.length) {
|
|
2884
|
+
console.log('More than ' + limit + ' entries found');
|
|
2885
|
+
}
|
|
2886
|
+
"`;
|
|
2887
|
+
}
|
|
2888
|
+
const TAB_WIDTH = 4;
|
|
2889
|
+
const COMMENT_PREFIXES = [
|
|
2890
|
+
'#',
|
|
2891
|
+
'//',
|
|
2892
|
+
'--'
|
|
2893
|
+
];
|
|
2714
2894
|
/**
|
|
2715
|
-
* Node.js command template for reading
|
|
2716
|
-
*/ function
|
|
2717
|
-
const pathB64 =
|
|
2718
|
-
|
|
2719
|
-
const
|
|
2720
|
-
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 0;
|
|
2895
|
+
* Node.js command template for slice mode reading.
|
|
2896
|
+
*/ function buildSliceReadCommand(filePath, offset, limit) {
|
|
2897
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
2898
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) - 1 : 0;
|
|
2899
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 2000;
|
|
2721
2900
|
return `node -e "
|
|
2722
2901
|
const fs = require('fs');
|
|
2902
|
+
const MAX_LINE_LENGTH = ${MAX_LINE_LENGTH};
|
|
2723
2903
|
|
|
2724
|
-
const filePath =
|
|
2904
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2725
2905
|
const offset = ${safeOffset};
|
|
2726
2906
|
const limit = ${safeLimit};
|
|
2727
2907
|
|
|
@@ -2738,28 +2918,188 @@ if (stat.size === 0) {
|
|
|
2738
2918
|
|
|
2739
2919
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
2740
2920
|
const lines = content.split('\\n');
|
|
2921
|
+
|
|
2922
|
+
if (offset >= lines.length) {
|
|
2923
|
+
console.log('Error: offset exceeds file length');
|
|
2924
|
+
process.exit(1);
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2741
2927
|
const selected = lines.slice(offset, offset + limit);
|
|
2742
2928
|
|
|
2743
2929
|
for (let i = 0; i < selected.length; i++) {
|
|
2744
2930
|
const lineNum = offset + i + 1;
|
|
2745
|
-
|
|
2931
|
+
let line = selected[i];
|
|
2932
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
2933
|
+
line = line.substring(0, MAX_LINE_LENGTH);
|
|
2934
|
+
}
|
|
2935
|
+
console.log('L' + lineNum + ': ' + line);
|
|
2936
|
+
}
|
|
2937
|
+
"`;
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Node.js command template for indentation mode reading.
|
|
2941
|
+
*/ function buildIndentationReadCommand(filePath, offset, limit, options) {
|
|
2942
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
2943
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;
|
|
2944
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 2000;
|
|
2945
|
+
var _options_anchor_line;
|
|
2946
|
+
const anchorLine = (_options_anchor_line = options.anchor_line) != null ? _options_anchor_line : safeOffset;
|
|
2947
|
+
var _options_max_levels;
|
|
2948
|
+
const maxLevels = (_options_max_levels = options.max_levels) != null ? _options_max_levels : 0;
|
|
2949
|
+
var _options_include_siblings;
|
|
2950
|
+
const includeSiblings = (_options_include_siblings = options.include_siblings) != null ? _options_include_siblings : false;
|
|
2951
|
+
var _options_include_header;
|
|
2952
|
+
const includeHeader = (_options_include_header = options.include_header) != null ? _options_include_header : true;
|
|
2953
|
+
var _options_max_lines;
|
|
2954
|
+
const maxLines = (_options_max_lines = options.max_lines) != null ? _options_max_lines : safeLimit;
|
|
2955
|
+
return `node -e "
|
|
2956
|
+
const fs = require('fs');
|
|
2957
|
+
const MAX_LINE_LENGTH = ${MAX_LINE_LENGTH};
|
|
2958
|
+
const TAB_WIDTH = ${TAB_WIDTH};
|
|
2959
|
+
const COMMENT_PREFIXES = ${JSON.stringify(COMMENT_PREFIXES)};
|
|
2960
|
+
|
|
2961
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2962
|
+
const anchorLine = ${anchorLine};
|
|
2963
|
+
const limit = ${safeLimit};
|
|
2964
|
+
const maxLevels = ${maxLevels};
|
|
2965
|
+
const includeSiblings = ${includeSiblings};
|
|
2966
|
+
const includeHeader = ${includeHeader};
|
|
2967
|
+
const maxLines = ${maxLines};
|
|
2968
|
+
|
|
2969
|
+
if (!fs.existsSync(filePath)) {
|
|
2970
|
+
console.log('Error: File not found');
|
|
2971
|
+
process.exit(1);
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
2975
|
+
const rawLines = content.split('\\n');
|
|
2976
|
+
|
|
2977
|
+
if (rawLines.length === 0 || anchorLine > rawLines.length) {
|
|
2978
|
+
console.log('Error: anchor_line exceeds file length');
|
|
2979
|
+
process.exit(1);
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
function measureIndent(line) {
|
|
2983
|
+
let indent = 0;
|
|
2984
|
+
for (const c of line) {
|
|
2985
|
+
if (c === ' ') indent++;
|
|
2986
|
+
else if (c === '\\t') indent += TAB_WIDTH;
|
|
2987
|
+
else break;
|
|
2988
|
+
}
|
|
2989
|
+
return indent;
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
function isBlank(line) { return line.trim() === ''; }
|
|
2993
|
+
function isComment(line) { return COMMENT_PREFIXES.some(p => line.trim().startsWith(p)); }
|
|
2994
|
+
function formatLine(line) { return line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) : line; }
|
|
2995
|
+
|
|
2996
|
+
const records = rawLines.map((raw, i) => ({
|
|
2997
|
+
number: i + 1,
|
|
2998
|
+
raw,
|
|
2999
|
+
display: formatLine(raw),
|
|
3000
|
+
indent: measureIndent(raw)
|
|
3001
|
+
}));
|
|
3002
|
+
|
|
3003
|
+
const effectiveIndents = [];
|
|
3004
|
+
let prevIndent = 0;
|
|
3005
|
+
for (const r of records) {
|
|
3006
|
+
if (isBlank(r.raw)) {
|
|
3007
|
+
effectiveIndents.push(prevIndent);
|
|
3008
|
+
} else {
|
|
3009
|
+
prevIndent = r.indent;
|
|
3010
|
+
effectiveIndents.push(prevIndent);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
const anchorIndex = anchorLine - 1;
|
|
3015
|
+
const anchorIndent = effectiveIndents[anchorIndex];
|
|
3016
|
+
const minIndent = maxLevels === 0 ? 0 : Math.max(0, anchorIndent - maxLevels * TAB_WIDTH);
|
|
3017
|
+
const finalLimit = Math.min(limit, maxLines, records.length);
|
|
3018
|
+
|
|
3019
|
+
if (finalLimit === 1) {
|
|
3020
|
+
console.log('L' + records[anchorIndex].number + ': ' + records[anchorIndex].display);
|
|
3021
|
+
process.exit(0);
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
const out = [records[anchorIndex]];
|
|
3025
|
+
let i = anchorIndex - 1;
|
|
3026
|
+
let j = anchorIndex + 1;
|
|
3027
|
+
let iCounterMinIndent = 0;
|
|
3028
|
+
let jCounterMinIndent = 0;
|
|
3029
|
+
|
|
3030
|
+
while (out.length < finalLimit) {
|
|
3031
|
+
let progressed = 0;
|
|
3032
|
+
|
|
3033
|
+
if (i >= 0) {
|
|
3034
|
+
if (effectiveIndents[i] >= minIndent) {
|
|
3035
|
+
out.unshift(records[i]);
|
|
3036
|
+
progressed++;
|
|
3037
|
+
const curI = i;
|
|
3038
|
+
i--;
|
|
3039
|
+
|
|
3040
|
+
if (effectiveIndents[curI] === minIndent && !includeSiblings) {
|
|
3041
|
+
const allowHeaderComment = includeHeader && isComment(records[curI].raw);
|
|
3042
|
+
const canTakeLine = allowHeaderComment || iCounterMinIndent === 0;
|
|
3043
|
+
if (canTakeLine) {
|
|
3044
|
+
iCounterMinIndent++;
|
|
3045
|
+
} else {
|
|
3046
|
+
out.shift();
|
|
3047
|
+
progressed--;
|
|
3048
|
+
i = -1;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
if (out.length >= finalLimit) break;
|
|
3053
|
+
} else {
|
|
3054
|
+
i = -1;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
if (j < records.length) {
|
|
3059
|
+
if (effectiveIndents[j] >= minIndent) {
|
|
3060
|
+
out.push(records[j]);
|
|
3061
|
+
progressed++;
|
|
3062
|
+
const curJ = j;
|
|
3063
|
+
j++;
|
|
3064
|
+
|
|
3065
|
+
if (effectiveIndents[curJ] === minIndent && !includeSiblings) {
|
|
3066
|
+
if (jCounterMinIndent > 0) {
|
|
3067
|
+
out.pop();
|
|
3068
|
+
progressed--;
|
|
3069
|
+
j = records.length;
|
|
3070
|
+
}
|
|
3071
|
+
jCounterMinIndent++;
|
|
3072
|
+
}
|
|
3073
|
+
} else {
|
|
3074
|
+
j = records.length;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
if (progressed === 0) break;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
while (out.length > 0 && isBlank(out[0].raw)) out.shift();
|
|
3082
|
+
while (out.length > 0 && isBlank(out[out.length - 1].raw)) out.pop();
|
|
3083
|
+
|
|
3084
|
+
for (const r of out) {
|
|
3085
|
+
console.log('L' + r.number + ': ' + r.display);
|
|
2746
3086
|
}
|
|
2747
3087
|
"`;
|
|
2748
3088
|
}
|
|
2749
3089
|
/**
|
|
2750
3090
|
* Node.js command template for writing files.
|
|
2751
3091
|
*/ function buildWriteCommand(filePath, content) {
|
|
2752
|
-
const pathB64 =
|
|
2753
|
-
const contentB64 =
|
|
3092
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3093
|
+
const contentB64 = utf8ToBase64(content);
|
|
2754
3094
|
return `node -e "
|
|
2755
3095
|
const fs = require('fs');
|
|
2756
3096
|
const path = require('path');
|
|
2757
3097
|
|
|
2758
|
-
const filePath =
|
|
2759
|
-
const content =
|
|
3098
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3099
|
+
const content = Buffer.from('${contentB64}', 'base64').toString('utf-8');
|
|
2760
3100
|
|
|
2761
3101
|
if (fs.existsSync(filePath)) {
|
|
2762
|
-
console.error('
|
|
3102
|
+
console.error('${WRITE_EXISTS_OUTPUT}');
|
|
2763
3103
|
process.exit(1);
|
|
2764
3104
|
}
|
|
2765
3105
|
|
|
@@ -2770,18 +3110,45 @@ fs.writeFileSync(filePath, content, 'utf-8');
|
|
|
2770
3110
|
console.log('OK');
|
|
2771
3111
|
"`;
|
|
2772
3112
|
}
|
|
3113
|
+
function buildExistsCommand(filePath) {
|
|
3114
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3115
|
+
return `node -e "
|
|
3116
|
+
const fs = require('fs');
|
|
3117
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3118
|
+
process.exit(fs.existsSync(filePath) ? 0 : 1);
|
|
3119
|
+
"`;
|
|
3120
|
+
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Node.js command template for appending to files.
|
|
3123
|
+
*/ function buildAppendCommand(filePath, content) {
|
|
3124
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3125
|
+
const contentB64 = utf8ToBase64(content);
|
|
3126
|
+
return `node -e "
|
|
3127
|
+
const fs = require('fs');
|
|
3128
|
+
const path = require('path');
|
|
3129
|
+
|
|
3130
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3131
|
+
const content = Buffer.from('${contentB64}', 'base64').toString('utf-8');
|
|
3132
|
+
|
|
3133
|
+
const parentDir = path.dirname(filePath) || '.';
|
|
3134
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
3135
|
+
|
|
3136
|
+
fs.appendFileSync(filePath, content, 'utf-8');
|
|
3137
|
+
console.log('OK');
|
|
3138
|
+
"`;
|
|
3139
|
+
}
|
|
2773
3140
|
/**
|
|
2774
3141
|
* Node.js command template for editing files.
|
|
2775
3142
|
*/ function buildEditCommand(filePath, oldStr, newStr, replaceAll) {
|
|
2776
|
-
const pathB64 =
|
|
2777
|
-
const oldB64 =
|
|
2778
|
-
const newB64 =
|
|
3143
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3144
|
+
const oldB64 = utf8ToBase64(oldStr);
|
|
3145
|
+
const newB64 = utf8ToBase64(newStr);
|
|
2779
3146
|
return `node -e "
|
|
2780
3147
|
const fs = require('fs');
|
|
2781
3148
|
|
|
2782
|
-
const filePath =
|
|
2783
|
-
const oldStr =
|
|
2784
|
-
const newStr =
|
|
3149
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3150
|
+
const oldStr = Buffer.from('${oldB64}', 'base64').toString('utf-8');
|
|
3151
|
+
const newStr = Buffer.from('${newB64}', 'base64').toString('utf-8');
|
|
2785
3152
|
const replaceAll = ${Boolean(replaceAll)};
|
|
2786
3153
|
|
|
2787
3154
|
let text;
|
|
@@ -2808,16 +3175,16 @@ console.log(count);
|
|
|
2808
3175
|
/**
|
|
2809
3176
|
* Node.js command template for grep operations.
|
|
2810
3177
|
*/ function buildGrepCommand(pattern, searchPath, globPattern) {
|
|
2811
|
-
const patternB64 =
|
|
2812
|
-
const pathB64 =
|
|
2813
|
-
const globB64 = globPattern ?
|
|
3178
|
+
const patternB64 = utf8ToBase64(pattern);
|
|
3179
|
+
const pathB64 = utf8ToBase64(searchPath);
|
|
3180
|
+
const globB64 = globPattern ? utf8ToBase64(globPattern) : "";
|
|
2814
3181
|
return `node -e "
|
|
2815
3182
|
const fs = require('fs');
|
|
2816
3183
|
const path = require('path');
|
|
2817
3184
|
|
|
2818
|
-
const pattern =
|
|
2819
|
-
const searchPath =
|
|
2820
|
-
const globPattern = ${globPattern ? `
|
|
3185
|
+
const pattern = Buffer.from('${patternB64}', 'base64').toString('utf-8');
|
|
3186
|
+
const searchPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3187
|
+
const globPattern = ${globPattern ? `Buffer.from('${globB64}', 'base64').toString('utf-8')` : "null"};
|
|
2821
3188
|
|
|
2822
3189
|
let regex;
|
|
2823
3190
|
try {
|
|
@@ -2886,6 +3253,13 @@ try {
|
|
|
2886
3253
|
*
|
|
2887
3254
|
* Requires Node.js 20+ on the sandbox host.
|
|
2888
3255
|
*/ class BaseSandbox {
|
|
3256
|
+
async streamExecute(command, onLine) {
|
|
3257
|
+
const result = await this.execute(command);
|
|
3258
|
+
if (result.output) {
|
|
3259
|
+
onLine(result.output);
|
|
3260
|
+
}
|
|
3261
|
+
return result;
|
|
3262
|
+
}
|
|
2889
3263
|
/**
|
|
2890
3264
|
* List files and directories in the specified directory (non-recursive).
|
|
2891
3265
|
*
|
|
@@ -2915,24 +3289,51 @@ try {
|
|
|
2915
3289
|
return infos;
|
|
2916
3290
|
}
|
|
2917
3291
|
/**
|
|
3292
|
+
* List directory contents recursively with depth control and pagination.
|
|
3293
|
+
*
|
|
3294
|
+
* @param dirPath - Absolute path to directory
|
|
3295
|
+
* @param offset - 1-indexed entry number to start from (default: 1)
|
|
3296
|
+
* @param limit - Maximum number of entries to return (default: 25)
|
|
3297
|
+
* @param depth - Maximum depth to traverse (default: 2)
|
|
3298
|
+
* @returns Human-readable directory tree with indentation
|
|
3299
|
+
*/ async listDir(dirPath, offset = 1, limit = 25, depth = 2) {
|
|
3300
|
+
if (offset < 1) {
|
|
3301
|
+
return "Error: offset must be a 1-indexed entry number";
|
|
3302
|
+
}
|
|
3303
|
+
if (limit < 1) {
|
|
3304
|
+
return "Error: limit must be greater than zero";
|
|
3305
|
+
}
|
|
3306
|
+
if (depth < 1) {
|
|
3307
|
+
return "Error: depth must be greater than zero";
|
|
3308
|
+
}
|
|
3309
|
+
const command = buildListDirCommand(dirPath, offset, limit, depth);
|
|
3310
|
+
const result = await this.execute(command);
|
|
3311
|
+
if (result.exitCode !== 0) {
|
|
3312
|
+
return result.output || `Error: Failed to list directory '${dirPath}'`;
|
|
3313
|
+
}
|
|
3314
|
+
return result.output;
|
|
3315
|
+
}
|
|
3316
|
+
/**
|
|
2918
3317
|
* Read file content with line numbers.
|
|
2919
3318
|
*
|
|
2920
3319
|
* @param filePath - Absolute file path
|
|
2921
|
-
* @param offset - Line offset to start reading from (
|
|
3320
|
+
* @param offset - Line offset to start reading from (1-indexed)
|
|
2922
3321
|
* @param limit - Maximum number of lines to read
|
|
3322
|
+
* @param mode - Read mode: 'slice' (default) or 'indentation'
|
|
3323
|
+
* @param indentation - Configuration for indentation mode
|
|
2923
3324
|
* @returns Formatted file content with line numbers, or error message
|
|
2924
|
-
*/ async read(filePath, offset =
|
|
2925
|
-
const command =
|
|
3325
|
+
*/ async read(filePath, offset = 1, limit = 2000, mode = 'slice', indentation) {
|
|
3326
|
+
const command = mode === 'indentation' ? buildIndentationReadCommand(filePath, offset, limit, indentation != null ? indentation : {}) : buildSliceReadCommand(filePath, offset, limit);
|
|
2926
3327
|
const result = await this.execute(command);
|
|
2927
3328
|
if (result.exitCode !== 0) {
|
|
2928
|
-
return `Error: File '${filePath}' not found`;
|
|
3329
|
+
return result.output || `Error: File '${filePath}' not found`;
|
|
2929
3330
|
}
|
|
2930
3331
|
return result.output;
|
|
2931
3332
|
}
|
|
2932
3333
|
/**
|
|
2933
3334
|
* Structured search results or error string for invalid input.
|
|
2934
|
-
*/ async grepRaw(pattern, path = "/",
|
|
2935
|
-
const command = buildGrepCommand(pattern, path,
|
|
3335
|
+
*/ async grepRaw(pattern, path = "/", include = null) {
|
|
3336
|
+
const command = buildGrepCommand(pattern, path, include);
|
|
2936
3337
|
const result = await this.execute(command);
|
|
2937
3338
|
if (result.exitCode === 1) {
|
|
2938
3339
|
// Check if it's a regex error
|
|
@@ -2957,6 +3358,18 @@ try {
|
|
|
2957
3358
|
return matches;
|
|
2958
3359
|
}
|
|
2959
3360
|
/**
|
|
3361
|
+
* Search file contents for a regex pattern, returning human-readable output.
|
|
3362
|
+
*/ async grep(pattern, path = "/", include = null) {
|
|
3363
|
+
const result = await this.grepRaw(pattern, path, include);
|
|
3364
|
+
if (typeof result === "string") {
|
|
3365
|
+
return result;
|
|
3366
|
+
}
|
|
3367
|
+
if (result.length === 0) {
|
|
3368
|
+
return "No matches found";
|
|
3369
|
+
}
|
|
3370
|
+
return formatGrepOutput(result);
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
2960
3373
|
* Structured glob matching returning FileInfo objects.
|
|
2961
3374
|
*/ async globInfo(pattern, path = "/") {
|
|
2962
3375
|
const command = buildGlobCommand(path, pattern);
|
|
@@ -2979,15 +3392,79 @@ try {
|
|
|
2979
3392
|
return infos;
|
|
2980
3393
|
}
|
|
2981
3394
|
/**
|
|
3395
|
+
* Find files matching a glob pattern, returning human-readable output.
|
|
3396
|
+
*/ async glob(pattern, path = "/") {
|
|
3397
|
+
const files = await this.globInfo(pattern, path);
|
|
3398
|
+
if (files.length === 0) {
|
|
3399
|
+
return "No files found";
|
|
3400
|
+
}
|
|
3401
|
+
return formatGlobOutput(files);
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
2982
3404
|
* Create a new file with content.
|
|
2983
3405
|
*/ async write(filePath, content) {
|
|
2984
3406
|
const command = buildWriteCommand(filePath, content);
|
|
2985
3407
|
const result = await this.execute(command);
|
|
2986
3408
|
if (result.exitCode !== 0) {
|
|
3409
|
+
const output = (result.output || "").trim();
|
|
3410
|
+
if (output.includes(WRITE_EXISTS_OUTPUT)) {
|
|
3411
|
+
return {
|
|
3412
|
+
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
3413
|
+
};
|
|
3414
|
+
}
|
|
3415
|
+
const fallback = await this.writeViaUpload(filePath, content);
|
|
3416
|
+
if (!fallback.error) {
|
|
3417
|
+
return fallback;
|
|
3418
|
+
}
|
|
3419
|
+
return {
|
|
3420
|
+
error: output ? `Failed to write file '${filePath}': ${output}` : `Failed to write file '${filePath}'.`
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
return {
|
|
3424
|
+
path: filePath,
|
|
3425
|
+
filesUpdate: null
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
async writeViaUpload(filePath, content) {
|
|
3429
|
+
const existsCheck = await this.execute(buildExistsCommand(filePath));
|
|
3430
|
+
if (existsCheck.exitCode === 0) {
|
|
2987
3431
|
return {
|
|
2988
3432
|
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
2989
3433
|
};
|
|
2990
3434
|
}
|
|
3435
|
+
const uploads = await this.uploadFiles([
|
|
3436
|
+
[
|
|
3437
|
+
filePath,
|
|
3438
|
+
Buffer.from(content, "utf-8")
|
|
3439
|
+
]
|
|
3440
|
+
]);
|
|
3441
|
+
const first = uploads[0];
|
|
3442
|
+
if (!first) {
|
|
3443
|
+
return {
|
|
3444
|
+
error: `Failed to write file '${filePath}': upload returned no result.`
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
if (first.error) {
|
|
3448
|
+
return {
|
|
3449
|
+
error: `Failed to write file '${filePath}': ${first.error}`
|
|
3450
|
+
};
|
|
3451
|
+
}
|
|
3452
|
+
return {
|
|
3453
|
+
path: filePath,
|
|
3454
|
+
filesUpdate: null
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3457
|
+
/**
|
|
3458
|
+
* Append content to a file. Creates the file if it doesn't exist.
|
|
3459
|
+
*/ async append(filePath, content) {
|
|
3460
|
+
const command = buildAppendCommand(filePath, content);
|
|
3461
|
+
const result = await this.execute(command);
|
|
3462
|
+
if (result.exitCode !== 0) {
|
|
3463
|
+
const output = (result.output || "").trim();
|
|
3464
|
+
return {
|
|
3465
|
+
error: output ? `Failed to append to file '${filePath}': ${output}` : `Failed to append to file '${filePath}'.`
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
2991
3468
|
return {
|
|
2992
3469
|
path: filePath,
|
|
2993
3470
|
filesUpdate: null
|
|
@@ -3026,6 +3503,38 @@ try {
|
|
|
3026
3503
|
};
|
|
3027
3504
|
}
|
|
3028
3505
|
}
|
|
3506
|
+
/**
|
|
3507
|
+
* Perform multiple sequential edits on a single file.
|
|
3508
|
+
* All edits are applied sequentially, with each edit operating on the result of the previous edit.
|
|
3509
|
+
* All edits must succeed for the operation to succeed (atomic).
|
|
3510
|
+
*/ async multiEdit(filePath, edits) {
|
|
3511
|
+
if (!edits || edits.length === 0) {
|
|
3512
|
+
return {
|
|
3513
|
+
error: "No edits provided"
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
const results = [];
|
|
3517
|
+
for (const edit of edits){
|
|
3518
|
+
var _edit_replaceAll;
|
|
3519
|
+
const result = await this.edit(filePath, edit.oldString, edit.newString, (_edit_replaceAll = edit.replaceAll) != null ? _edit_replaceAll : false);
|
|
3520
|
+
results.push(result);
|
|
3521
|
+
// If any edit fails, return immediately with error
|
|
3522
|
+
if (result.error) {
|
|
3523
|
+
return {
|
|
3524
|
+
error: `Multi-edit failed at edit ${results.length}: ${result.error}`,
|
|
3525
|
+
path: filePath,
|
|
3526
|
+
filesUpdate: null,
|
|
3527
|
+
results
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
// All edits succeeded
|
|
3532
|
+
return {
|
|
3533
|
+
path: filePath,
|
|
3534
|
+
filesUpdate: null,
|
|
3535
|
+
results
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3029
3538
|
}
|
|
3030
3539
|
|
|
3031
3540
|
const SANDBOX_PROVIDER = 'SANDBOX_PROVIDER';
|