@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.cjs.js
CHANGED
|
@@ -531,6 +531,7 @@ exports.WorkflowNodeRegistry = __decorate([
|
|
|
531
531
|
])
|
|
532
532
|
], exports.WorkflowNodeRegistry);
|
|
533
533
|
|
|
534
|
+
const COMMAND_METADATA$1 = '__command__';
|
|
534
535
|
/**
|
|
535
536
|
* Wrap Workflow Node Execution Command
|
|
536
537
|
*/ class WrapWorkflowNodeExecutionCommand extends cqrs.Command {
|
|
@@ -541,6 +542,9 @@ exports.WorkflowNodeRegistry = __decorate([
|
|
|
541
542
|
}
|
|
542
543
|
}
|
|
543
544
|
WrapWorkflowNodeExecutionCommand.type = '[Workflow] Wrap Workflow Node Execution';
|
|
545
|
+
Reflect.defineMetadata(COMMAND_METADATA$1, {
|
|
546
|
+
id: WrapWorkflowNodeExecutionCommand.type
|
|
547
|
+
}, WrapWorkflowNodeExecutionCommand);
|
|
544
548
|
|
|
545
549
|
const VECTOR_STORE_STRATEGY = 'VECTOR_STORE_STRATEGY';
|
|
546
550
|
const VectorStoreStrategy = (provider)=>common.applyDecorators(common.SetMetadata(VECTOR_STORE_STRATEGY, provider), common.SetMetadata(STRATEGY_META_KEY, VECTOR_STORE_STRATEGY));
|
|
@@ -2384,6 +2388,7 @@ function normalizeContextSize(value) {
|
|
|
2384
2388
|
return undefined;
|
|
2385
2389
|
}
|
|
2386
2390
|
|
|
2391
|
+
const COMMAND_METADATA = '__command__';
|
|
2387
2392
|
/**
|
|
2388
2393
|
* Get a Chat Model of copilot model and check it's token limitation, record the token usage
|
|
2389
2394
|
*/ class CreateModelClientCommand extends cqrs.Command {
|
|
@@ -2394,6 +2399,9 @@ function normalizeContextSize(value) {
|
|
|
2394
2399
|
}
|
|
2395
2400
|
}
|
|
2396
2401
|
CreateModelClientCommand.type = '[AI Model] Create Model Client';
|
|
2402
|
+
Reflect.defineMetadata(COMMAND_METADATA, {
|
|
2403
|
+
id: CreateModelClientCommand.type
|
|
2404
|
+
}, CreateModelClientCommand);
|
|
2397
2405
|
|
|
2398
2406
|
const AGENT_MIDDLEWARE_STRATEGY = 'AGENT_MIDDLEWARE_STRATEGY';
|
|
2399
2407
|
const AgentMiddlewareStrategy = (provider)=>common.applyDecorators(common.SetMetadata(AGENT_MIDDLEWARE_STRATEGY, provider), common.SetMetadata(STRATEGY_META_KEY, AGENT_MIDDLEWARE_STRATEGY));
|
|
@@ -2650,18 +2658,78 @@ CancelConversationCommand.type = '[Chat Conversation] Cancel';
|
|
|
2650
2658
|
* only need to implement the execute() method.
|
|
2651
2659
|
*
|
|
2652
2660
|
* Requires Node.js 20+ on the sandbox host.
|
|
2653
|
-
*/
|
|
2661
|
+
*/ const MAX_LINE_LENGTH = 500;
|
|
2662
|
+
const MAX_GREP_LINE_LENGTH = 2000;
|
|
2663
|
+
const GREP_RESULT_LIMIT = 100;
|
|
2664
|
+
const GLOB_RESULT_LIMIT = 100;
|
|
2665
|
+
const WRITE_EXISTS_OUTPUT = "Error: File already exists";
|
|
2666
|
+
/**
|
|
2667
|
+
* UTF-8 safe Base64 encoding function.
|
|
2668
|
+
* Replaces btoa() which only supports Latin1 characters.
|
|
2669
|
+
*/ function utf8ToBase64(str) {
|
|
2670
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Format glob results into human-readable output.
|
|
2674
|
+
*/ function formatGlobOutput(files, pattern) {
|
|
2675
|
+
const limit = GLOB_RESULT_LIMIT;
|
|
2676
|
+
const truncated = files.length > limit;
|
|
2677
|
+
const finalFiles = truncated ? files.slice(0, limit) : files;
|
|
2678
|
+
if (finalFiles.length === 0) {
|
|
2679
|
+
return "No files found";
|
|
2680
|
+
}
|
|
2681
|
+
const lines = [];
|
|
2682
|
+
for (const file of finalFiles){
|
|
2683
|
+
lines.push(file.path);
|
|
2684
|
+
}
|
|
2685
|
+
if (truncated) {
|
|
2686
|
+
lines.push("");
|
|
2687
|
+
lines.push("(Results truncated. Consider using a more specific path or pattern.)");
|
|
2688
|
+
}
|
|
2689
|
+
return lines.join("\n");
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Format grep matches into human-readable output grouped by file.
|
|
2693
|
+
*/ function formatGrepOutput(matches) {
|
|
2694
|
+
const limit = GREP_RESULT_LIMIT;
|
|
2695
|
+
const truncated = matches.length > limit;
|
|
2696
|
+
const finalMatches = truncated ? matches.slice(0, limit) : matches;
|
|
2697
|
+
if (finalMatches.length === 0) {
|
|
2698
|
+
return "No matches found";
|
|
2699
|
+
}
|
|
2700
|
+
const lines = [
|
|
2701
|
+
`Found ${finalMatches.length} matches`
|
|
2702
|
+
];
|
|
2703
|
+
let currentFile = "";
|
|
2704
|
+
for (const match of finalMatches){
|
|
2705
|
+
if (currentFile !== match.path) {
|
|
2706
|
+
if (currentFile !== "") {
|
|
2707
|
+
lines.push("");
|
|
2708
|
+
}
|
|
2709
|
+
currentFile = match.path;
|
|
2710
|
+
lines.push(`${match.path}:`);
|
|
2711
|
+
}
|
|
2712
|
+
const text = match.text.length > MAX_GREP_LINE_LENGTH ? match.text.substring(0, MAX_GREP_LINE_LENGTH) + "..." : match.text;
|
|
2713
|
+
lines.push(` Line ${match.line}: ${text}`);
|
|
2714
|
+
}
|
|
2715
|
+
if (truncated) {
|
|
2716
|
+
lines.push("");
|
|
2717
|
+
lines.push("(Results truncated. Consider using a more specific path or pattern.)");
|
|
2718
|
+
}
|
|
2719
|
+
return lines.join("\n");
|
|
2720
|
+
}
|
|
2721
|
+
/**
|
|
2654
2722
|
* Node.js command template for glob operations.
|
|
2655
2723
|
* Uses web-standard atob() for base64 decoding.
|
|
2656
2724
|
*/ function buildGlobCommand(searchPath, pattern) {
|
|
2657
|
-
const pathB64 =
|
|
2658
|
-
const patternB64 =
|
|
2725
|
+
const pathB64 = utf8ToBase64(searchPath);
|
|
2726
|
+
const patternB64 = utf8ToBase64(pattern);
|
|
2659
2727
|
return `node -e "
|
|
2660
2728
|
const fs = require('fs');
|
|
2661
2729
|
const path = require('path');
|
|
2662
2730
|
|
|
2663
|
-
const searchPath =
|
|
2664
|
-
const pattern =
|
|
2731
|
+
const searchPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2732
|
+
const pattern = Buffer.from('${patternB64}', 'base64').toString('utf-8');
|
|
2665
2733
|
|
|
2666
2734
|
function globMatch(relativePath, pattern) {
|
|
2667
2735
|
const regexPattern = pattern
|
|
@@ -2706,12 +2774,12 @@ try {
|
|
|
2706
2774
|
/**
|
|
2707
2775
|
* Node.js command template for listing directory contents.
|
|
2708
2776
|
*/ function buildLsCommand(dirPath) {
|
|
2709
|
-
const pathB64 =
|
|
2777
|
+
const pathB64 = utf8ToBase64(dirPath);
|
|
2710
2778
|
return `node -e "
|
|
2711
2779
|
const fs = require('fs');
|
|
2712
2780
|
const path = require('path');
|
|
2713
2781
|
|
|
2714
|
-
const dirPath =
|
|
2782
|
+
const dirPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2715
2783
|
|
|
2716
2784
|
try {
|
|
2717
2785
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -2731,17 +2799,129 @@ try {
|
|
|
2731
2799
|
}
|
|
2732
2800
|
"`;
|
|
2733
2801
|
}
|
|
2802
|
+
const INDENTATION_SPACES = 2;
|
|
2803
|
+
const MAX_ENTRY_LENGTH = 500;
|
|
2804
|
+
/**
|
|
2805
|
+
* Node.js command template for recursive directory listing with depth control.
|
|
2806
|
+
*/ function buildListDirCommand(dirPath, offset, limit, depth) {
|
|
2807
|
+
const pathB64 = utf8ToBase64(dirPath);
|
|
2808
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;
|
|
2809
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 25;
|
|
2810
|
+
const safeDepth = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 2;
|
|
2811
|
+
return `node -e "
|
|
2812
|
+
const fs = require('fs');
|
|
2813
|
+
const path = require('path');
|
|
2814
|
+
|
|
2815
|
+
const dirPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2816
|
+
const offset = ${safeOffset};
|
|
2817
|
+
const limit = ${safeLimit};
|
|
2818
|
+
const maxDepth = ${safeDepth};
|
|
2819
|
+
const INDENTATION_SPACES = ${INDENTATION_SPACES};
|
|
2820
|
+
const MAX_ENTRY_LENGTH = ${MAX_ENTRY_LENGTH};
|
|
2821
|
+
|
|
2822
|
+
if (!path.isAbsolute(dirPath)) {
|
|
2823
|
+
console.error('Error: dir_path must be an absolute path');
|
|
2824
|
+
process.exit(1);
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
if (!fs.existsSync(dirPath)) {
|
|
2828
|
+
console.error('Error: Directory not found');
|
|
2829
|
+
process.exit(1);
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
if (!fs.statSync(dirPath).isDirectory()) {
|
|
2833
|
+
console.error('Error: Path is not a directory');
|
|
2834
|
+
process.exit(1);
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
function collectEntries(currentPath, relativePrefix, remainingDepth, entries) {
|
|
2838
|
+
if (remainingDepth === 0) return;
|
|
2839
|
+
|
|
2840
|
+
try {
|
|
2841
|
+
const dirEntries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
2842
|
+
const sortedEntries = dirEntries
|
|
2843
|
+
.map(entry => {
|
|
2844
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
2845
|
+
const relativePath = path.join(relativePrefix, entry.name);
|
|
2846
|
+
const displayName = entry.name.length > MAX_ENTRY_LENGTH
|
|
2847
|
+
? entry.name.substring(0, MAX_ENTRY_LENGTH)
|
|
2848
|
+
: entry.name;
|
|
2849
|
+
|
|
2850
|
+
let kind = 'file';
|
|
2851
|
+
if (entry.isSymbolicLink()) kind = 'symlink';
|
|
2852
|
+
else if (entry.isDirectory()) kind = 'directory';
|
|
2853
|
+
|
|
2854
|
+
return {
|
|
2855
|
+
fullPath,
|
|
2856
|
+
relativePath,
|
|
2857
|
+
displayName,
|
|
2858
|
+
kind,
|
|
2859
|
+
depth: relativePrefix.split(path.sep).filter(Boolean).length
|
|
2860
|
+
};
|
|
2861
|
+
})
|
|
2862
|
+
.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
2863
|
+
|
|
2864
|
+
for (const entry of sortedEntries) {
|
|
2865
|
+
entries.push(entry);
|
|
2866
|
+
if (entry.kind === 'directory' && remainingDepth > 1) {
|
|
2867
|
+
collectEntries(entry.fullPath, entry.relativePath, remainingDepth - 1, entries);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
} catch (e) {
|
|
2871
|
+
// Skip unreadable directories
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
const allEntries = [];
|
|
2876
|
+
collectEntries(dirPath, '', maxDepth, allEntries);
|
|
2877
|
+
|
|
2878
|
+
if (allEntries.length === 0) {
|
|
2879
|
+
console.log('Absolute path: ' + dirPath);
|
|
2880
|
+
console.log('(Empty directory)');
|
|
2881
|
+
process.exit(0);
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
const startIndex = offset - 1;
|
|
2885
|
+
if (startIndex >= allEntries.length) {
|
|
2886
|
+
console.error('Error: offset exceeds directory entry count');
|
|
2887
|
+
process.exit(1);
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
const endIndex = Math.min(startIndex + limit, allEntries.length);
|
|
2891
|
+
const selectedEntries = allEntries.slice(startIndex, endIndex);
|
|
2892
|
+
|
|
2893
|
+
console.log('Absolute path: ' + dirPath);
|
|
2894
|
+
|
|
2895
|
+
for (const entry of selectedEntries) {
|
|
2896
|
+
const indent = ' '.repeat(entry.depth * INDENTATION_SPACES);
|
|
2897
|
+
let name = entry.displayName;
|
|
2898
|
+
if (entry.kind === 'directory') name += '/';
|
|
2899
|
+
else if (entry.kind === 'symlink') name += '@';
|
|
2900
|
+
console.log(indent + name);
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
if (endIndex < allEntries.length) {
|
|
2904
|
+
console.log('More than ' + limit + ' entries found');
|
|
2905
|
+
}
|
|
2906
|
+
"`;
|
|
2907
|
+
}
|
|
2908
|
+
const TAB_WIDTH = 4;
|
|
2909
|
+
const COMMENT_PREFIXES = [
|
|
2910
|
+
'#',
|
|
2911
|
+
'//',
|
|
2912
|
+
'--'
|
|
2913
|
+
];
|
|
2734
2914
|
/**
|
|
2735
|
-
* Node.js command template for reading
|
|
2736
|
-
*/ function
|
|
2737
|
-
const pathB64 =
|
|
2738
|
-
|
|
2739
|
-
const
|
|
2740
|
-
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 0;
|
|
2915
|
+
* Node.js command template for slice mode reading.
|
|
2916
|
+
*/ function buildSliceReadCommand(filePath, offset, limit) {
|
|
2917
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
2918
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) - 1 : 0;
|
|
2919
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 2000;
|
|
2741
2920
|
return `node -e "
|
|
2742
2921
|
const fs = require('fs');
|
|
2922
|
+
const MAX_LINE_LENGTH = ${MAX_LINE_LENGTH};
|
|
2743
2923
|
|
|
2744
|
-
const filePath =
|
|
2924
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2745
2925
|
const offset = ${safeOffset};
|
|
2746
2926
|
const limit = ${safeLimit};
|
|
2747
2927
|
|
|
@@ -2758,28 +2938,188 @@ if (stat.size === 0) {
|
|
|
2758
2938
|
|
|
2759
2939
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
2760
2940
|
const lines = content.split('\\n');
|
|
2941
|
+
|
|
2942
|
+
if (offset >= lines.length) {
|
|
2943
|
+
console.log('Error: offset exceeds file length');
|
|
2944
|
+
process.exit(1);
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2761
2947
|
const selected = lines.slice(offset, offset + limit);
|
|
2762
2948
|
|
|
2763
2949
|
for (let i = 0; i < selected.length; i++) {
|
|
2764
2950
|
const lineNum = offset + i + 1;
|
|
2765
|
-
|
|
2951
|
+
let line = selected[i];
|
|
2952
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
2953
|
+
line = line.substring(0, MAX_LINE_LENGTH);
|
|
2954
|
+
}
|
|
2955
|
+
console.log('L' + lineNum + ': ' + line);
|
|
2956
|
+
}
|
|
2957
|
+
"`;
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Node.js command template for indentation mode reading.
|
|
2961
|
+
*/ function buildIndentationReadCommand(filePath, offset, limit, options) {
|
|
2962
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
2963
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;
|
|
2964
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 2000;
|
|
2965
|
+
var _options_anchor_line;
|
|
2966
|
+
const anchorLine = (_options_anchor_line = options.anchor_line) != null ? _options_anchor_line : safeOffset;
|
|
2967
|
+
var _options_max_levels;
|
|
2968
|
+
const maxLevels = (_options_max_levels = options.max_levels) != null ? _options_max_levels : 0;
|
|
2969
|
+
var _options_include_siblings;
|
|
2970
|
+
const includeSiblings = (_options_include_siblings = options.include_siblings) != null ? _options_include_siblings : false;
|
|
2971
|
+
var _options_include_header;
|
|
2972
|
+
const includeHeader = (_options_include_header = options.include_header) != null ? _options_include_header : true;
|
|
2973
|
+
var _options_max_lines;
|
|
2974
|
+
const maxLines = (_options_max_lines = options.max_lines) != null ? _options_max_lines : safeLimit;
|
|
2975
|
+
return `node -e "
|
|
2976
|
+
const fs = require('fs');
|
|
2977
|
+
const MAX_LINE_LENGTH = ${MAX_LINE_LENGTH};
|
|
2978
|
+
const TAB_WIDTH = ${TAB_WIDTH};
|
|
2979
|
+
const COMMENT_PREFIXES = ${JSON.stringify(COMMENT_PREFIXES)};
|
|
2980
|
+
|
|
2981
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2982
|
+
const anchorLine = ${anchorLine};
|
|
2983
|
+
const limit = ${safeLimit};
|
|
2984
|
+
const maxLevels = ${maxLevels};
|
|
2985
|
+
const includeSiblings = ${includeSiblings};
|
|
2986
|
+
const includeHeader = ${includeHeader};
|
|
2987
|
+
const maxLines = ${maxLines};
|
|
2988
|
+
|
|
2989
|
+
if (!fs.existsSync(filePath)) {
|
|
2990
|
+
console.log('Error: File not found');
|
|
2991
|
+
process.exit(1);
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
2995
|
+
const rawLines = content.split('\\n');
|
|
2996
|
+
|
|
2997
|
+
if (rawLines.length === 0 || anchorLine > rawLines.length) {
|
|
2998
|
+
console.log('Error: anchor_line exceeds file length');
|
|
2999
|
+
process.exit(1);
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
function measureIndent(line) {
|
|
3003
|
+
let indent = 0;
|
|
3004
|
+
for (const c of line) {
|
|
3005
|
+
if (c === ' ') indent++;
|
|
3006
|
+
else if (c === '\\t') indent += TAB_WIDTH;
|
|
3007
|
+
else break;
|
|
3008
|
+
}
|
|
3009
|
+
return indent;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
function isBlank(line) { return line.trim() === ''; }
|
|
3013
|
+
function isComment(line) { return COMMENT_PREFIXES.some(p => line.trim().startsWith(p)); }
|
|
3014
|
+
function formatLine(line) { return line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) : line; }
|
|
3015
|
+
|
|
3016
|
+
const records = rawLines.map((raw, i) => ({
|
|
3017
|
+
number: i + 1,
|
|
3018
|
+
raw,
|
|
3019
|
+
display: formatLine(raw),
|
|
3020
|
+
indent: measureIndent(raw)
|
|
3021
|
+
}));
|
|
3022
|
+
|
|
3023
|
+
const effectiveIndents = [];
|
|
3024
|
+
let prevIndent = 0;
|
|
3025
|
+
for (const r of records) {
|
|
3026
|
+
if (isBlank(r.raw)) {
|
|
3027
|
+
effectiveIndents.push(prevIndent);
|
|
3028
|
+
} else {
|
|
3029
|
+
prevIndent = r.indent;
|
|
3030
|
+
effectiveIndents.push(prevIndent);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
const anchorIndex = anchorLine - 1;
|
|
3035
|
+
const anchorIndent = effectiveIndents[anchorIndex];
|
|
3036
|
+
const minIndent = maxLevels === 0 ? 0 : Math.max(0, anchorIndent - maxLevels * TAB_WIDTH);
|
|
3037
|
+
const finalLimit = Math.min(limit, maxLines, records.length);
|
|
3038
|
+
|
|
3039
|
+
if (finalLimit === 1) {
|
|
3040
|
+
console.log('L' + records[anchorIndex].number + ': ' + records[anchorIndex].display);
|
|
3041
|
+
process.exit(0);
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
const out = [records[anchorIndex]];
|
|
3045
|
+
let i = anchorIndex - 1;
|
|
3046
|
+
let j = anchorIndex + 1;
|
|
3047
|
+
let iCounterMinIndent = 0;
|
|
3048
|
+
let jCounterMinIndent = 0;
|
|
3049
|
+
|
|
3050
|
+
while (out.length < finalLimit) {
|
|
3051
|
+
let progressed = 0;
|
|
3052
|
+
|
|
3053
|
+
if (i >= 0) {
|
|
3054
|
+
if (effectiveIndents[i] >= minIndent) {
|
|
3055
|
+
out.unshift(records[i]);
|
|
3056
|
+
progressed++;
|
|
3057
|
+
const curI = i;
|
|
3058
|
+
i--;
|
|
3059
|
+
|
|
3060
|
+
if (effectiveIndents[curI] === minIndent && !includeSiblings) {
|
|
3061
|
+
const allowHeaderComment = includeHeader && isComment(records[curI].raw);
|
|
3062
|
+
const canTakeLine = allowHeaderComment || iCounterMinIndent === 0;
|
|
3063
|
+
if (canTakeLine) {
|
|
3064
|
+
iCounterMinIndent++;
|
|
3065
|
+
} else {
|
|
3066
|
+
out.shift();
|
|
3067
|
+
progressed--;
|
|
3068
|
+
i = -1;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
if (out.length >= finalLimit) break;
|
|
3073
|
+
} else {
|
|
3074
|
+
i = -1;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
if (j < records.length) {
|
|
3079
|
+
if (effectiveIndents[j] >= minIndent) {
|
|
3080
|
+
out.push(records[j]);
|
|
3081
|
+
progressed++;
|
|
3082
|
+
const curJ = j;
|
|
3083
|
+
j++;
|
|
3084
|
+
|
|
3085
|
+
if (effectiveIndents[curJ] === minIndent && !includeSiblings) {
|
|
3086
|
+
if (jCounterMinIndent > 0) {
|
|
3087
|
+
out.pop();
|
|
3088
|
+
progressed--;
|
|
3089
|
+
j = records.length;
|
|
3090
|
+
}
|
|
3091
|
+
jCounterMinIndent++;
|
|
3092
|
+
}
|
|
3093
|
+
} else {
|
|
3094
|
+
j = records.length;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
if (progressed === 0) break;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
while (out.length > 0 && isBlank(out[0].raw)) out.shift();
|
|
3102
|
+
while (out.length > 0 && isBlank(out[out.length - 1].raw)) out.pop();
|
|
3103
|
+
|
|
3104
|
+
for (const r of out) {
|
|
3105
|
+
console.log('L' + r.number + ': ' + r.display);
|
|
2766
3106
|
}
|
|
2767
3107
|
"`;
|
|
2768
3108
|
}
|
|
2769
3109
|
/**
|
|
2770
3110
|
* Node.js command template for writing files.
|
|
2771
3111
|
*/ function buildWriteCommand(filePath, content) {
|
|
2772
|
-
const pathB64 =
|
|
2773
|
-
const contentB64 =
|
|
3112
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3113
|
+
const contentB64 = utf8ToBase64(content);
|
|
2774
3114
|
return `node -e "
|
|
2775
3115
|
const fs = require('fs');
|
|
2776
3116
|
const path = require('path');
|
|
2777
3117
|
|
|
2778
|
-
const filePath =
|
|
2779
|
-
const content =
|
|
3118
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3119
|
+
const content = Buffer.from('${contentB64}', 'base64').toString('utf-8');
|
|
2780
3120
|
|
|
2781
3121
|
if (fs.existsSync(filePath)) {
|
|
2782
|
-
console.error('
|
|
3122
|
+
console.error('${WRITE_EXISTS_OUTPUT}');
|
|
2783
3123
|
process.exit(1);
|
|
2784
3124
|
}
|
|
2785
3125
|
|
|
@@ -2790,18 +3130,45 @@ fs.writeFileSync(filePath, content, 'utf-8');
|
|
|
2790
3130
|
console.log('OK');
|
|
2791
3131
|
"`;
|
|
2792
3132
|
}
|
|
3133
|
+
function buildExistsCommand(filePath) {
|
|
3134
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3135
|
+
return `node -e "
|
|
3136
|
+
const fs = require('fs');
|
|
3137
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3138
|
+
process.exit(fs.existsSync(filePath) ? 0 : 1);
|
|
3139
|
+
"`;
|
|
3140
|
+
}
|
|
3141
|
+
/**
|
|
3142
|
+
* Node.js command template for appending to files.
|
|
3143
|
+
*/ function buildAppendCommand(filePath, content) {
|
|
3144
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3145
|
+
const contentB64 = utf8ToBase64(content);
|
|
3146
|
+
return `node -e "
|
|
3147
|
+
const fs = require('fs');
|
|
3148
|
+
const path = require('path');
|
|
3149
|
+
|
|
3150
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3151
|
+
const content = Buffer.from('${contentB64}', 'base64').toString('utf-8');
|
|
3152
|
+
|
|
3153
|
+
const parentDir = path.dirname(filePath) || '.';
|
|
3154
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
3155
|
+
|
|
3156
|
+
fs.appendFileSync(filePath, content, 'utf-8');
|
|
3157
|
+
console.log('OK');
|
|
3158
|
+
"`;
|
|
3159
|
+
}
|
|
2793
3160
|
/**
|
|
2794
3161
|
* Node.js command template for editing files.
|
|
2795
3162
|
*/ function buildEditCommand(filePath, oldStr, newStr, replaceAll) {
|
|
2796
|
-
const pathB64 =
|
|
2797
|
-
const oldB64 =
|
|
2798
|
-
const newB64 =
|
|
3163
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3164
|
+
const oldB64 = utf8ToBase64(oldStr);
|
|
3165
|
+
const newB64 = utf8ToBase64(newStr);
|
|
2799
3166
|
return `node -e "
|
|
2800
3167
|
const fs = require('fs');
|
|
2801
3168
|
|
|
2802
|
-
const filePath =
|
|
2803
|
-
const oldStr =
|
|
2804
|
-
const newStr =
|
|
3169
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3170
|
+
const oldStr = Buffer.from('${oldB64}', 'base64').toString('utf-8');
|
|
3171
|
+
const newStr = Buffer.from('${newB64}', 'base64').toString('utf-8');
|
|
2805
3172
|
const replaceAll = ${Boolean(replaceAll)};
|
|
2806
3173
|
|
|
2807
3174
|
let text;
|
|
@@ -2828,16 +3195,16 @@ console.log(count);
|
|
|
2828
3195
|
/**
|
|
2829
3196
|
* Node.js command template for grep operations.
|
|
2830
3197
|
*/ function buildGrepCommand(pattern, searchPath, globPattern) {
|
|
2831
|
-
const patternB64 =
|
|
2832
|
-
const pathB64 =
|
|
2833
|
-
const globB64 = globPattern ?
|
|
3198
|
+
const patternB64 = utf8ToBase64(pattern);
|
|
3199
|
+
const pathB64 = utf8ToBase64(searchPath);
|
|
3200
|
+
const globB64 = globPattern ? utf8ToBase64(globPattern) : "";
|
|
2834
3201
|
return `node -e "
|
|
2835
3202
|
const fs = require('fs');
|
|
2836
3203
|
const path = require('path');
|
|
2837
3204
|
|
|
2838
|
-
const pattern =
|
|
2839
|
-
const searchPath =
|
|
2840
|
-
const globPattern = ${globPattern ? `
|
|
3205
|
+
const pattern = Buffer.from('${patternB64}', 'base64').toString('utf-8');
|
|
3206
|
+
const searchPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3207
|
+
const globPattern = ${globPattern ? `Buffer.from('${globB64}', 'base64').toString('utf-8')` : "null"};
|
|
2841
3208
|
|
|
2842
3209
|
let regex;
|
|
2843
3210
|
try {
|
|
@@ -2906,6 +3273,13 @@ try {
|
|
|
2906
3273
|
*
|
|
2907
3274
|
* Requires Node.js 20+ on the sandbox host.
|
|
2908
3275
|
*/ class BaseSandbox {
|
|
3276
|
+
async streamExecute(command, onLine) {
|
|
3277
|
+
const result = await this.execute(command);
|
|
3278
|
+
if (result.output) {
|
|
3279
|
+
onLine(result.output);
|
|
3280
|
+
}
|
|
3281
|
+
return result;
|
|
3282
|
+
}
|
|
2909
3283
|
/**
|
|
2910
3284
|
* List files and directories in the specified directory (non-recursive).
|
|
2911
3285
|
*
|
|
@@ -2935,24 +3309,51 @@ try {
|
|
|
2935
3309
|
return infos;
|
|
2936
3310
|
}
|
|
2937
3311
|
/**
|
|
3312
|
+
* List directory contents recursively with depth control and pagination.
|
|
3313
|
+
*
|
|
3314
|
+
* @param dirPath - Absolute path to directory
|
|
3315
|
+
* @param offset - 1-indexed entry number to start from (default: 1)
|
|
3316
|
+
* @param limit - Maximum number of entries to return (default: 25)
|
|
3317
|
+
* @param depth - Maximum depth to traverse (default: 2)
|
|
3318
|
+
* @returns Human-readable directory tree with indentation
|
|
3319
|
+
*/ async listDir(dirPath, offset = 1, limit = 25, depth = 2) {
|
|
3320
|
+
if (offset < 1) {
|
|
3321
|
+
return "Error: offset must be a 1-indexed entry number";
|
|
3322
|
+
}
|
|
3323
|
+
if (limit < 1) {
|
|
3324
|
+
return "Error: limit must be greater than zero";
|
|
3325
|
+
}
|
|
3326
|
+
if (depth < 1) {
|
|
3327
|
+
return "Error: depth must be greater than zero";
|
|
3328
|
+
}
|
|
3329
|
+
const command = buildListDirCommand(dirPath, offset, limit, depth);
|
|
3330
|
+
const result = await this.execute(command);
|
|
3331
|
+
if (result.exitCode !== 0) {
|
|
3332
|
+
return result.output || `Error: Failed to list directory '${dirPath}'`;
|
|
3333
|
+
}
|
|
3334
|
+
return result.output;
|
|
3335
|
+
}
|
|
3336
|
+
/**
|
|
2938
3337
|
* Read file content with line numbers.
|
|
2939
3338
|
*
|
|
2940
3339
|
* @param filePath - Absolute file path
|
|
2941
|
-
* @param offset - Line offset to start reading from (
|
|
3340
|
+
* @param offset - Line offset to start reading from (1-indexed)
|
|
2942
3341
|
* @param limit - Maximum number of lines to read
|
|
3342
|
+
* @param mode - Read mode: 'slice' (default) or 'indentation'
|
|
3343
|
+
* @param indentation - Configuration for indentation mode
|
|
2943
3344
|
* @returns Formatted file content with line numbers, or error message
|
|
2944
|
-
*/ async read(filePath, offset =
|
|
2945
|
-
const command =
|
|
3345
|
+
*/ async read(filePath, offset = 1, limit = 2000, mode = 'slice', indentation) {
|
|
3346
|
+
const command = mode === 'indentation' ? buildIndentationReadCommand(filePath, offset, limit, indentation != null ? indentation : {}) : buildSliceReadCommand(filePath, offset, limit);
|
|
2946
3347
|
const result = await this.execute(command);
|
|
2947
3348
|
if (result.exitCode !== 0) {
|
|
2948
|
-
return `Error: File '${filePath}' not found`;
|
|
3349
|
+
return result.output || `Error: File '${filePath}' not found`;
|
|
2949
3350
|
}
|
|
2950
3351
|
return result.output;
|
|
2951
3352
|
}
|
|
2952
3353
|
/**
|
|
2953
3354
|
* Structured search results or error string for invalid input.
|
|
2954
|
-
*/ async grepRaw(pattern, path = "/",
|
|
2955
|
-
const command = buildGrepCommand(pattern, path,
|
|
3355
|
+
*/ async grepRaw(pattern, path = "/", include = null) {
|
|
3356
|
+
const command = buildGrepCommand(pattern, path, include);
|
|
2956
3357
|
const result = await this.execute(command);
|
|
2957
3358
|
if (result.exitCode === 1) {
|
|
2958
3359
|
// Check if it's a regex error
|
|
@@ -2977,6 +3378,18 @@ try {
|
|
|
2977
3378
|
return matches;
|
|
2978
3379
|
}
|
|
2979
3380
|
/**
|
|
3381
|
+
* Search file contents for a regex pattern, returning human-readable output.
|
|
3382
|
+
*/ async grep(pattern, path = "/", include = null) {
|
|
3383
|
+
const result = await this.grepRaw(pattern, path, include);
|
|
3384
|
+
if (typeof result === "string") {
|
|
3385
|
+
return result;
|
|
3386
|
+
}
|
|
3387
|
+
if (result.length === 0) {
|
|
3388
|
+
return "No matches found";
|
|
3389
|
+
}
|
|
3390
|
+
return formatGrepOutput(result);
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
2980
3393
|
* Structured glob matching returning FileInfo objects.
|
|
2981
3394
|
*/ async globInfo(pattern, path = "/") {
|
|
2982
3395
|
const command = buildGlobCommand(path, pattern);
|
|
@@ -2999,15 +3412,79 @@ try {
|
|
|
2999
3412
|
return infos;
|
|
3000
3413
|
}
|
|
3001
3414
|
/**
|
|
3415
|
+
* Find files matching a glob pattern, returning human-readable output.
|
|
3416
|
+
*/ async glob(pattern, path = "/") {
|
|
3417
|
+
const files = await this.globInfo(pattern, path);
|
|
3418
|
+
if (files.length === 0) {
|
|
3419
|
+
return "No files found";
|
|
3420
|
+
}
|
|
3421
|
+
return formatGlobOutput(files);
|
|
3422
|
+
}
|
|
3423
|
+
/**
|
|
3002
3424
|
* Create a new file with content.
|
|
3003
3425
|
*/ async write(filePath, content) {
|
|
3004
3426
|
const command = buildWriteCommand(filePath, content);
|
|
3005
3427
|
const result = await this.execute(command);
|
|
3006
3428
|
if (result.exitCode !== 0) {
|
|
3429
|
+
const output = (result.output || "").trim();
|
|
3430
|
+
if (output.includes(WRITE_EXISTS_OUTPUT)) {
|
|
3431
|
+
return {
|
|
3432
|
+
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
const fallback = await this.writeViaUpload(filePath, content);
|
|
3436
|
+
if (!fallback.error) {
|
|
3437
|
+
return fallback;
|
|
3438
|
+
}
|
|
3439
|
+
return {
|
|
3440
|
+
error: output ? `Failed to write file '${filePath}': ${output}` : `Failed to write file '${filePath}'.`
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
return {
|
|
3444
|
+
path: filePath,
|
|
3445
|
+
filesUpdate: null
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
async writeViaUpload(filePath, content) {
|
|
3449
|
+
const existsCheck = await this.execute(buildExistsCommand(filePath));
|
|
3450
|
+
if (existsCheck.exitCode === 0) {
|
|
3007
3451
|
return {
|
|
3008
3452
|
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
3009
3453
|
};
|
|
3010
3454
|
}
|
|
3455
|
+
const uploads = await this.uploadFiles([
|
|
3456
|
+
[
|
|
3457
|
+
filePath,
|
|
3458
|
+
Buffer.from(content, "utf-8")
|
|
3459
|
+
]
|
|
3460
|
+
]);
|
|
3461
|
+
const first = uploads[0];
|
|
3462
|
+
if (!first) {
|
|
3463
|
+
return {
|
|
3464
|
+
error: `Failed to write file '${filePath}': upload returned no result.`
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
if (first.error) {
|
|
3468
|
+
return {
|
|
3469
|
+
error: `Failed to write file '${filePath}': ${first.error}`
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
return {
|
|
3473
|
+
path: filePath,
|
|
3474
|
+
filesUpdate: null
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3477
|
+
/**
|
|
3478
|
+
* Append content to a file. Creates the file if it doesn't exist.
|
|
3479
|
+
*/ async append(filePath, content) {
|
|
3480
|
+
const command = buildAppendCommand(filePath, content);
|
|
3481
|
+
const result = await this.execute(command);
|
|
3482
|
+
if (result.exitCode !== 0) {
|
|
3483
|
+
const output = (result.output || "").trim();
|
|
3484
|
+
return {
|
|
3485
|
+
error: output ? `Failed to append to file '${filePath}': ${output}` : `Failed to append to file '${filePath}'.`
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3011
3488
|
return {
|
|
3012
3489
|
path: filePath,
|
|
3013
3490
|
filesUpdate: null
|
|
@@ -3046,6 +3523,38 @@ try {
|
|
|
3046
3523
|
};
|
|
3047
3524
|
}
|
|
3048
3525
|
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Perform multiple sequential edits on a single file.
|
|
3528
|
+
* All edits are applied sequentially, with each edit operating on the result of the previous edit.
|
|
3529
|
+
* All edits must succeed for the operation to succeed (atomic).
|
|
3530
|
+
*/ async multiEdit(filePath, edits) {
|
|
3531
|
+
if (!edits || edits.length === 0) {
|
|
3532
|
+
return {
|
|
3533
|
+
error: "No edits provided"
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
const results = [];
|
|
3537
|
+
for (const edit of edits){
|
|
3538
|
+
var _edit_replaceAll;
|
|
3539
|
+
const result = await this.edit(filePath, edit.oldString, edit.newString, (_edit_replaceAll = edit.replaceAll) != null ? _edit_replaceAll : false);
|
|
3540
|
+
results.push(result);
|
|
3541
|
+
// If any edit fails, return immediately with error
|
|
3542
|
+
if (result.error) {
|
|
3543
|
+
return {
|
|
3544
|
+
error: `Multi-edit failed at edit ${results.length}: ${result.error}`,
|
|
3545
|
+
path: filePath,
|
|
3546
|
+
filesUpdate: null,
|
|
3547
|
+
results
|
|
3548
|
+
};
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
// All edits succeeded
|
|
3552
|
+
return {
|
|
3553
|
+
path: filePath,
|
|
3554
|
+
filesUpdate: null,
|
|
3555
|
+
results
|
|
3556
|
+
};
|
|
3557
|
+
}
|
|
3049
3558
|
}
|
|
3050
3559
|
|
|
3051
3560
|
const SANDBOX_PROVIDER = 'SANDBOX_PROVIDER';
|