@xpert-ai/plugin-sdk 3.8.0 → 3.8.3
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 +661 -63
- package/index.esm.js +641 -64
- package/package.json +1 -1
- package/src/index.d.ts +1 -0
- package/src/lib/core/index.d.ts +1 -0
- package/src/lib/core/plugin-config.d.ts +9 -0
- package/src/lib/file/file-storage/index.d.ts +3 -0
- package/src/lib/file/file-storage/provider.decorator.d.ts +2 -0
- package/src/lib/file/file-storage/provider.interface.d.ts +14 -0
- package/src/lib/file/file-storage/provider.registry.d.ts +6 -0
- package/src/lib/file/file-upload/index.d.ts +3 -0
- package/src/lib/file/file-upload/strategy.decorator.d.ts +2 -0
- package/src/lib/file/file-upload/strategy.interface.d.ts +25 -0
- package/src/lib/file/file-upload/strategy.registry.d.ts +6 -0
- package/src/lib/file/index.d.ts +2 -0
- package/src/lib/sandbox/execution.d.ts +27 -0
- package/src/lib/sandbox/index.d.ts +1 -0
- package/src/lib/sandbox/protocol.d.ts +128 -7
- package/src/lib/sandbox/sandbox.d.ts +39 -5
- package/src/lib/types.d.ts +3 -1
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));
|
|
@@ -952,6 +956,8 @@ BuiltinToolset.provider = '';
|
|
|
952
956
|
}
|
|
953
957
|
}
|
|
954
958
|
|
|
959
|
+
const PLUGIN_CONFIG_RESOLVER_TOKEN = 'XPERT_PLUGIN_CONFIG_RESOLVER';
|
|
960
|
+
|
|
955
961
|
/**
|
|
956
962
|
* System token for resolving integration read service from plugin context.
|
|
957
963
|
*/ const INTEGRATION_PERMISSION_SERVICE_TOKEN = 'XPERT_PLUGIN_INTEGRATION_PERMISSION_SERVICE';
|
|
@@ -2384,6 +2390,7 @@ function normalizeContextSize(value) {
|
|
|
2384
2390
|
return undefined;
|
|
2385
2391
|
}
|
|
2386
2392
|
|
|
2393
|
+
const COMMAND_METADATA = '__command__';
|
|
2387
2394
|
/**
|
|
2388
2395
|
* Get a Chat Model of copilot model and check it's token limitation, record the token usage
|
|
2389
2396
|
*/ class CreateModelClientCommand extends cqrs.Command {
|
|
@@ -2394,6 +2401,9 @@ function normalizeContextSize(value) {
|
|
|
2394
2401
|
}
|
|
2395
2402
|
}
|
|
2396
2403
|
CreateModelClientCommand.type = '[AI Model] Create Model Client';
|
|
2404
|
+
Reflect.defineMetadata(COMMAND_METADATA, {
|
|
2405
|
+
id: CreateModelClientCommand.type
|
|
2406
|
+
}, CreateModelClientCommand);
|
|
2397
2407
|
|
|
2398
2408
|
const AGENT_MIDDLEWARE_STRATEGY = 'AGENT_MIDDLEWARE_STRATEGY';
|
|
2399
2409
|
const AgentMiddlewareStrategy = (provider)=>common.applyDecorators(common.SetMetadata(AGENT_MIDDLEWARE_STRATEGY, provider), common.SetMetadata(STRATEGY_META_KEY, AGENT_MIDDLEWARE_STRATEGY));
|
|
@@ -2627,41 +2637,167 @@ class CancelConversationCommand {
|
|
|
2627
2637
|
}
|
|
2628
2638
|
CancelConversationCommand.type = '[Chat Conversation] Cancel';
|
|
2629
2639
|
|
|
2640
|
+
const FILE_STORAGE_PROVIDER = 'FILE_STORAGE_PROVIDER';
|
|
2641
|
+
const FileStorageProvider = (provider)=>common.applyDecorators(common.SetMetadata(FILE_STORAGE_PROVIDER, provider), common.SetMetadata(STRATEGY_META_KEY, FILE_STORAGE_PROVIDER));
|
|
2642
|
+
|
|
2643
|
+
exports.FileStorageProviderRegistry = class FileStorageProviderRegistry extends BaseStrategyRegistry {
|
|
2644
|
+
constructor(discoveryService, reflector){
|
|
2645
|
+
super(FILE_STORAGE_PROVIDER, discoveryService, reflector);
|
|
2646
|
+
}
|
|
2647
|
+
};
|
|
2648
|
+
exports.FileStorageProviderRegistry = __decorate([
|
|
2649
|
+
common.Injectable(),
|
|
2650
|
+
__metadata("design:type", Function),
|
|
2651
|
+
__metadata("design:paramtypes", [
|
|
2652
|
+
typeof core.DiscoveryService === "undefined" ? Object : core.DiscoveryService,
|
|
2653
|
+
typeof core.Reflector === "undefined" ? Object : core.Reflector
|
|
2654
|
+
])
|
|
2655
|
+
], exports.FileStorageProviderRegistry);
|
|
2656
|
+
|
|
2657
|
+
const FILE_UPLOAD_TARGET_STRATEGY = 'FILE_UPLOAD_TARGET_STRATEGY';
|
|
2658
|
+
const FileUploadTargetStrategy = (type)=>common.applyDecorators(common.SetMetadata(FILE_UPLOAD_TARGET_STRATEGY, type), common.SetMetadata(STRATEGY_META_KEY, FILE_UPLOAD_TARGET_STRATEGY));
|
|
2659
|
+
|
|
2660
|
+
exports.FileUploadTargetRegistry = class FileUploadTargetRegistry extends BaseStrategyRegistry {
|
|
2661
|
+
constructor(discoveryService, reflector){
|
|
2662
|
+
super(FILE_UPLOAD_TARGET_STRATEGY, discoveryService, reflector);
|
|
2663
|
+
}
|
|
2664
|
+
};
|
|
2665
|
+
exports.FileUploadTargetRegistry = __decorate([
|
|
2666
|
+
common.Injectable(),
|
|
2667
|
+
__metadata("design:type", Function),
|
|
2668
|
+
__metadata("design:paramtypes", [
|
|
2669
|
+
typeof core.DiscoveryService === "undefined" ? Object : core.DiscoveryService,
|
|
2670
|
+
typeof core.Reflector === "undefined" ? Object : core.Reflector
|
|
2671
|
+
])
|
|
2672
|
+
], exports.FileUploadTargetRegistry);
|
|
2673
|
+
|
|
2674
|
+
const SANDBOX_SHELL_TIMEOUT_LIMITS_SEC = {
|
|
2675
|
+
min: 1,
|
|
2676
|
+
max: 3600
|
|
2677
|
+
};
|
|
2678
|
+
const DEFAULT_SANDBOX_SHELL_TIMEOUT_SEC = 600;
|
|
2679
|
+
const DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_SEC = 120;
|
|
2680
|
+
const DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_SEC = 120;
|
|
2681
|
+
const DEFAULT_SANDBOX_SHELL_TIMEOUT_MS = DEFAULT_SANDBOX_SHELL_TIMEOUT_SEC * 1000;
|
|
2682
|
+
const DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_MS = DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_SEC * 1000;
|
|
2683
|
+
const DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_MS = DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_SEC * 1000;
|
|
2684
|
+
const DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
2685
|
+
const DEFAULT_SANDBOX_SHELL_EXECUTION_OPTIONS = {
|
|
2686
|
+
timeoutMs: DEFAULT_SANDBOX_SHELL_TIMEOUT_MS,
|
|
2687
|
+
maxOutputBytes: DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES
|
|
2688
|
+
};
|
|
2689
|
+
const DEFAULT_SANDBOX_FILE_SEARCH_EXECUTION_OPTIONS = {
|
|
2690
|
+
timeoutMs: DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_MS,
|
|
2691
|
+
maxOutputBytes: DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES
|
|
2692
|
+
};
|
|
2693
|
+
const DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS = {
|
|
2694
|
+
timeoutMs: DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_MS,
|
|
2695
|
+
maxOutputBytes: DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES
|
|
2696
|
+
};
|
|
2697
|
+
function secondsToMilliseconds(seconds) {
|
|
2698
|
+
return Math.trunc(seconds * 1000);
|
|
2699
|
+
}
|
|
2700
|
+
function formatSandboxTimeout(timeoutMs) {
|
|
2701
|
+
const timeoutSeconds = timeoutMs / 1000;
|
|
2702
|
+
const secondsLabel = Number.isInteger(timeoutSeconds) ? String(timeoutSeconds) : Number(timeoutSeconds.toFixed(3)).toString();
|
|
2703
|
+
return `${secondsLabel}s (${timeoutMs}ms)`;
|
|
2704
|
+
}
|
|
2705
|
+
function buildSandboxTimeoutMessage(subject, timeoutMs) {
|
|
2706
|
+
return `${subject} timed out after ${formatSandboxTimeout(timeoutMs)}`;
|
|
2707
|
+
}
|
|
2708
|
+
function appendSandboxMessage(output, message) {
|
|
2709
|
+
return output ? `${output}\n${message}` : message;
|
|
2710
|
+
}
|
|
2711
|
+
function resolveSandboxExecutionOptions(options, defaults = DEFAULT_SANDBOX_SHELL_EXECUTION_OPTIONS) {
|
|
2712
|
+
const timeoutMs = typeof (options == null ? void 0 : options.timeoutMs) === 'number' && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? Math.trunc(options.timeoutMs) : typeof defaults.timeoutMs === 'number' && Number.isFinite(defaults.timeoutMs) && defaults.timeoutMs > 0 ? Math.trunc(defaults.timeoutMs) : DEFAULT_SANDBOX_SHELL_TIMEOUT_MS;
|
|
2713
|
+
const maxOutputBytes = typeof (options == null ? void 0 : options.maxOutputBytes) === 'number' && Number.isFinite(options.maxOutputBytes) && options.maxOutputBytes > 0 ? Math.trunc(options.maxOutputBytes) : typeof defaults.maxOutputBytes === 'number' && Number.isFinite(defaults.maxOutputBytes) && defaults.maxOutputBytes > 0 ? Math.trunc(defaults.maxOutputBytes) : DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES;
|
|
2714
|
+
return {
|
|
2715
|
+
timeoutMs,
|
|
2716
|
+
maxOutputBytes
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2630
2720
|
/**
|
|
2631
|
-
* Protocol definition for pluggable memory backends.
|
|
2632
|
-
*
|
|
2633
|
-
* This module defines the BackendProtocol that all backend implementations
|
|
2634
|
-
* must follow. Backends can store files in different locations (state, filesystem,
|
|
2635
|
-
* database, etc.) and provide a uniform interface for file operations.
|
|
2636
|
-
*/ /**
|
|
2637
2721
|
* Type guard to check if a backend supports execution.
|
|
2638
2722
|
*
|
|
2639
2723
|
* @param backend - Backend instance to check
|
|
2640
2724
|
* @returns True if the backend implements SandboxBackendProtocol
|
|
2641
2725
|
*/ function isSandboxBackend(backend) {
|
|
2642
|
-
return typeof backend.execute ===
|
|
2726
|
+
return typeof backend.execute === 'function' && typeof backend.id === 'string';
|
|
2643
2727
|
}
|
|
2644
2728
|
|
|
2729
|
+
const MAX_LINE_LENGTH = 500;
|
|
2730
|
+
const MAX_GREP_LINE_LENGTH = 2000;
|
|
2731
|
+
const GREP_RESULT_LIMIT = 100;
|
|
2732
|
+
const GLOB_RESULT_LIMIT = 100;
|
|
2733
|
+
const WRITE_EXISTS_OUTPUT = 'Error: File already exists';
|
|
2734
|
+
/**
|
|
2735
|
+
* UTF-8 safe Base64 encoding function.
|
|
2736
|
+
* Replaces btoa() which only supports Latin1 characters.
|
|
2737
|
+
*/ function utf8ToBase64(str) {
|
|
2738
|
+
return Buffer.from(str, 'utf-8').toString('base64');
|
|
2739
|
+
}
|
|
2740
|
+
/**
|
|
2741
|
+
* Format glob results into human-readable output.
|
|
2742
|
+
*/ function formatGlobOutput(files, pattern) {
|
|
2743
|
+
const limit = GLOB_RESULT_LIMIT;
|
|
2744
|
+
const truncated = files.length > limit;
|
|
2745
|
+
const finalFiles = truncated ? files.slice(0, limit) : files;
|
|
2746
|
+
if (finalFiles.length === 0) {
|
|
2747
|
+
return 'No files found';
|
|
2748
|
+
}
|
|
2749
|
+
const lines = [];
|
|
2750
|
+
for (const file of finalFiles){
|
|
2751
|
+
lines.push(file.path);
|
|
2752
|
+
}
|
|
2753
|
+
if (truncated) {
|
|
2754
|
+
lines.push('');
|
|
2755
|
+
lines.push('(Results truncated. Consider using a more specific path or pattern.)');
|
|
2756
|
+
}
|
|
2757
|
+
return lines.join('\n');
|
|
2758
|
+
}
|
|
2759
|
+
/**
|
|
2760
|
+
* Format grep matches into human-readable output grouped by file.
|
|
2761
|
+
*/ function formatGrepOutput(matches) {
|
|
2762
|
+
const limit = GREP_RESULT_LIMIT;
|
|
2763
|
+
const truncated = matches.length > limit;
|
|
2764
|
+
const finalMatches = truncated ? matches.slice(0, limit) : matches;
|
|
2765
|
+
if (finalMatches.length === 0) {
|
|
2766
|
+
return 'No matches found';
|
|
2767
|
+
}
|
|
2768
|
+
const lines = [
|
|
2769
|
+
`Found ${finalMatches.length} matches`
|
|
2770
|
+
];
|
|
2771
|
+
let currentFile = '';
|
|
2772
|
+
for (const match of finalMatches){
|
|
2773
|
+
if (currentFile !== match.path) {
|
|
2774
|
+
if (currentFile !== '') {
|
|
2775
|
+
lines.push('');
|
|
2776
|
+
}
|
|
2777
|
+
currentFile = match.path;
|
|
2778
|
+
lines.push(`${match.path}:`);
|
|
2779
|
+
}
|
|
2780
|
+
const text = match.text.length > MAX_GREP_LINE_LENGTH ? match.text.substring(0, MAX_GREP_LINE_LENGTH) + '...' : match.text;
|
|
2781
|
+
lines.push(` Line ${match.line}: ${text}`);
|
|
2782
|
+
}
|
|
2783
|
+
if (truncated) {
|
|
2784
|
+
lines.push('');
|
|
2785
|
+
lines.push('(Results truncated. Consider using a more specific path or pattern.)');
|
|
2786
|
+
}
|
|
2787
|
+
return lines.join('\n');
|
|
2788
|
+
}
|
|
2645
2789
|
/**
|
|
2646
|
-
* BaseSandbox: Abstract base class for sandbox backends with command execution.
|
|
2647
|
-
*
|
|
2648
|
-
* This class provides default implementations for all SandboxBackendProtocol
|
|
2649
|
-
* methods using shell commands executed via execute(). Concrete implementations
|
|
2650
|
-
* only need to implement the execute() method.
|
|
2651
|
-
*
|
|
2652
|
-
* Requires Node.js 20+ on the sandbox host.
|
|
2653
|
-
*/ /**
|
|
2654
2790
|
* Node.js command template for glob operations.
|
|
2655
2791
|
* Uses web-standard atob() for base64 decoding.
|
|
2656
2792
|
*/ function buildGlobCommand(searchPath, pattern) {
|
|
2657
|
-
const pathB64 =
|
|
2658
|
-
const patternB64 =
|
|
2793
|
+
const pathB64 = utf8ToBase64(searchPath);
|
|
2794
|
+
const patternB64 = utf8ToBase64(pattern);
|
|
2659
2795
|
return `node -e "
|
|
2660
2796
|
const fs = require('fs');
|
|
2661
2797
|
const path = require('path');
|
|
2662
2798
|
|
|
2663
|
-
const searchPath =
|
|
2664
|
-
const pattern =
|
|
2799
|
+
const searchPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2800
|
+
const pattern = Buffer.from('${patternB64}', 'base64').toString('utf-8');
|
|
2665
2801
|
|
|
2666
2802
|
function globMatch(relativePath, pattern) {
|
|
2667
2803
|
const regexPattern = pattern
|
|
@@ -2706,12 +2842,12 @@ try {
|
|
|
2706
2842
|
/**
|
|
2707
2843
|
* Node.js command template for listing directory contents.
|
|
2708
2844
|
*/ function buildLsCommand(dirPath) {
|
|
2709
|
-
const pathB64 =
|
|
2845
|
+
const pathB64 = utf8ToBase64(dirPath);
|
|
2710
2846
|
return `node -e "
|
|
2711
2847
|
const fs = require('fs');
|
|
2712
2848
|
const path = require('path');
|
|
2713
2849
|
|
|
2714
|
-
const dirPath =
|
|
2850
|
+
const dirPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2715
2851
|
|
|
2716
2852
|
try {
|
|
2717
2853
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -2731,17 +2867,129 @@ try {
|
|
|
2731
2867
|
}
|
|
2732
2868
|
"`;
|
|
2733
2869
|
}
|
|
2870
|
+
const INDENTATION_SPACES = 2;
|
|
2871
|
+
const MAX_ENTRY_LENGTH = 500;
|
|
2872
|
+
/**
|
|
2873
|
+
* Node.js command template for recursive directory listing with depth control.
|
|
2874
|
+
*/ function buildListDirCommand(dirPath, offset, limit, depth) {
|
|
2875
|
+
const pathB64 = utf8ToBase64(dirPath);
|
|
2876
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;
|
|
2877
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 25;
|
|
2878
|
+
const safeDepth = Number.isFinite(depth) && depth > 0 ? Math.floor(depth) : 2;
|
|
2879
|
+
return `node -e "
|
|
2880
|
+
const fs = require('fs');
|
|
2881
|
+
const path = require('path');
|
|
2882
|
+
|
|
2883
|
+
const dirPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2884
|
+
const offset = ${safeOffset};
|
|
2885
|
+
const limit = ${safeLimit};
|
|
2886
|
+
const maxDepth = ${safeDepth};
|
|
2887
|
+
const INDENTATION_SPACES = ${INDENTATION_SPACES};
|
|
2888
|
+
const MAX_ENTRY_LENGTH = ${MAX_ENTRY_LENGTH};
|
|
2889
|
+
|
|
2890
|
+
if (!path.isAbsolute(dirPath)) {
|
|
2891
|
+
console.error('Error: dir_path must be an absolute path');
|
|
2892
|
+
process.exit(1);
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
if (!fs.existsSync(dirPath)) {
|
|
2896
|
+
console.error('Error: Directory not found');
|
|
2897
|
+
process.exit(1);
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
if (!fs.statSync(dirPath).isDirectory()) {
|
|
2901
|
+
console.error('Error: Path is not a directory');
|
|
2902
|
+
process.exit(1);
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
function collectEntries(currentPath, relativePrefix, remainingDepth, entries) {
|
|
2906
|
+
if (remainingDepth === 0) return;
|
|
2907
|
+
|
|
2908
|
+
try {
|
|
2909
|
+
const dirEntries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
2910
|
+
const sortedEntries = dirEntries
|
|
2911
|
+
.map(entry => {
|
|
2912
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
2913
|
+
const relativePath = path.join(relativePrefix, entry.name);
|
|
2914
|
+
const displayName = entry.name.length > MAX_ENTRY_LENGTH
|
|
2915
|
+
? entry.name.substring(0, MAX_ENTRY_LENGTH)
|
|
2916
|
+
: entry.name;
|
|
2917
|
+
|
|
2918
|
+
let kind = 'file';
|
|
2919
|
+
if (entry.isSymbolicLink()) kind = 'symlink';
|
|
2920
|
+
else if (entry.isDirectory()) kind = 'directory';
|
|
2921
|
+
|
|
2922
|
+
return {
|
|
2923
|
+
fullPath,
|
|
2924
|
+
relativePath,
|
|
2925
|
+
displayName,
|
|
2926
|
+
kind,
|
|
2927
|
+
depth: relativePrefix.split(path.sep).filter(Boolean).length
|
|
2928
|
+
};
|
|
2929
|
+
})
|
|
2930
|
+
.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
2931
|
+
|
|
2932
|
+
for (const entry of sortedEntries) {
|
|
2933
|
+
entries.push(entry);
|
|
2934
|
+
if (entry.kind === 'directory' && remainingDepth > 1) {
|
|
2935
|
+
collectEntries(entry.fullPath, entry.relativePath, remainingDepth - 1, entries);
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
} catch (e) {
|
|
2939
|
+
// Skip unreadable directories
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
const allEntries = [];
|
|
2944
|
+
collectEntries(dirPath, '', maxDepth, allEntries);
|
|
2945
|
+
|
|
2946
|
+
if (allEntries.length === 0) {
|
|
2947
|
+
console.log('Absolute path: ' + dirPath);
|
|
2948
|
+
console.log('(Empty directory)');
|
|
2949
|
+
process.exit(0);
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
const startIndex = offset - 1;
|
|
2953
|
+
if (startIndex >= allEntries.length) {
|
|
2954
|
+
console.error('Error: offset exceeds directory entry count');
|
|
2955
|
+
process.exit(1);
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
const endIndex = Math.min(startIndex + limit, allEntries.length);
|
|
2959
|
+
const selectedEntries = allEntries.slice(startIndex, endIndex);
|
|
2960
|
+
|
|
2961
|
+
console.log('Absolute path: ' + dirPath);
|
|
2962
|
+
|
|
2963
|
+
for (const entry of selectedEntries) {
|
|
2964
|
+
const indent = ' '.repeat(entry.depth * INDENTATION_SPACES);
|
|
2965
|
+
let name = entry.displayName;
|
|
2966
|
+
if (entry.kind === 'directory') name += '/';
|
|
2967
|
+
else if (entry.kind === 'symlink') name += '@';
|
|
2968
|
+
console.log(indent + name);
|
|
2969
|
+
}
|
|
2970
|
+
|
|
2971
|
+
if (endIndex < allEntries.length) {
|
|
2972
|
+
console.log('More than ' + limit + ' entries found');
|
|
2973
|
+
}
|
|
2974
|
+
"`;
|
|
2975
|
+
}
|
|
2976
|
+
const TAB_WIDTH = 4;
|
|
2977
|
+
const COMMENT_PREFIXES = [
|
|
2978
|
+
'#',
|
|
2979
|
+
'//',
|
|
2980
|
+
'--'
|
|
2981
|
+
];
|
|
2734
2982
|
/**
|
|
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;
|
|
2983
|
+
* Node.js command template for slice mode reading.
|
|
2984
|
+
*/ function buildSliceReadCommand(filePath, offset, limit) {
|
|
2985
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
2986
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) - 1 : 0;
|
|
2987
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 2000;
|
|
2741
2988
|
return `node -e "
|
|
2742
2989
|
const fs = require('fs');
|
|
2990
|
+
const MAX_LINE_LENGTH = ${MAX_LINE_LENGTH};
|
|
2743
2991
|
|
|
2744
|
-
const filePath =
|
|
2992
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
2745
2993
|
const offset = ${safeOffset};
|
|
2746
2994
|
const limit = ${safeLimit};
|
|
2747
2995
|
|
|
@@ -2758,28 +3006,188 @@ if (stat.size === 0) {
|
|
|
2758
3006
|
|
|
2759
3007
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
2760
3008
|
const lines = content.split('\\n');
|
|
3009
|
+
|
|
3010
|
+
if (offset >= lines.length) {
|
|
3011
|
+
console.log('Error: offset exceeds file length');
|
|
3012
|
+
process.exit(1);
|
|
3013
|
+
}
|
|
3014
|
+
|
|
2761
3015
|
const selected = lines.slice(offset, offset + limit);
|
|
2762
3016
|
|
|
2763
3017
|
for (let i = 0; i < selected.length; i++) {
|
|
2764
3018
|
const lineNum = offset + i + 1;
|
|
2765
|
-
|
|
3019
|
+
let line = selected[i];
|
|
3020
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
3021
|
+
line = line.substring(0, MAX_LINE_LENGTH);
|
|
3022
|
+
}
|
|
3023
|
+
console.log('L' + lineNum + ': ' + line);
|
|
3024
|
+
}
|
|
3025
|
+
"`;
|
|
3026
|
+
}
|
|
3027
|
+
/**
|
|
3028
|
+
* Node.js command template for indentation mode reading.
|
|
3029
|
+
*/ function buildIndentationReadCommand(filePath, offset, limit, options) {
|
|
3030
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3031
|
+
const safeOffset = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;
|
|
3032
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 && limit < Number.MAX_SAFE_INTEGER ? Math.floor(limit) : 2000;
|
|
3033
|
+
var _options_anchor_line;
|
|
3034
|
+
const anchorLine = (_options_anchor_line = options.anchor_line) != null ? _options_anchor_line : safeOffset;
|
|
3035
|
+
var _options_max_levels;
|
|
3036
|
+
const maxLevels = (_options_max_levels = options.max_levels) != null ? _options_max_levels : 0;
|
|
3037
|
+
var _options_include_siblings;
|
|
3038
|
+
const includeSiblings = (_options_include_siblings = options.include_siblings) != null ? _options_include_siblings : false;
|
|
3039
|
+
var _options_include_header;
|
|
3040
|
+
const includeHeader = (_options_include_header = options.include_header) != null ? _options_include_header : true;
|
|
3041
|
+
var _options_max_lines;
|
|
3042
|
+
const maxLines = (_options_max_lines = options.max_lines) != null ? _options_max_lines : safeLimit;
|
|
3043
|
+
return `node -e "
|
|
3044
|
+
const fs = require('fs');
|
|
3045
|
+
const MAX_LINE_LENGTH = ${MAX_LINE_LENGTH};
|
|
3046
|
+
const TAB_WIDTH = ${TAB_WIDTH};
|
|
3047
|
+
const COMMENT_PREFIXES = ${JSON.stringify(COMMENT_PREFIXES)};
|
|
3048
|
+
|
|
3049
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3050
|
+
const anchorLine = ${anchorLine};
|
|
3051
|
+
const limit = ${safeLimit};
|
|
3052
|
+
const maxLevels = ${maxLevels};
|
|
3053
|
+
const includeSiblings = ${includeSiblings};
|
|
3054
|
+
const includeHeader = ${includeHeader};
|
|
3055
|
+
const maxLines = ${maxLines};
|
|
3056
|
+
|
|
3057
|
+
if (!fs.existsSync(filePath)) {
|
|
3058
|
+
console.log('Error: File not found');
|
|
3059
|
+
process.exit(1);
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
3063
|
+
const rawLines = content.split('\\n');
|
|
3064
|
+
|
|
3065
|
+
if (rawLines.length === 0 || anchorLine > rawLines.length) {
|
|
3066
|
+
console.log('Error: anchor_line exceeds file length');
|
|
3067
|
+
process.exit(1);
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
function measureIndent(line) {
|
|
3071
|
+
let indent = 0;
|
|
3072
|
+
for (const c of line) {
|
|
3073
|
+
if (c === ' ') indent++;
|
|
3074
|
+
else if (c === '\\t') indent += TAB_WIDTH;
|
|
3075
|
+
else break;
|
|
3076
|
+
}
|
|
3077
|
+
return indent;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
function isBlank(line) { return line.trim() === ''; }
|
|
3081
|
+
function isComment(line) { return COMMENT_PREFIXES.some(p => line.trim().startsWith(p)); }
|
|
3082
|
+
function formatLine(line) { return line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) : line; }
|
|
3083
|
+
|
|
3084
|
+
const records = rawLines.map((raw, i) => ({
|
|
3085
|
+
number: i + 1,
|
|
3086
|
+
raw,
|
|
3087
|
+
display: formatLine(raw),
|
|
3088
|
+
indent: measureIndent(raw)
|
|
3089
|
+
}));
|
|
3090
|
+
|
|
3091
|
+
const effectiveIndents = [];
|
|
3092
|
+
let prevIndent = 0;
|
|
3093
|
+
for (const r of records) {
|
|
3094
|
+
if (isBlank(r.raw)) {
|
|
3095
|
+
effectiveIndents.push(prevIndent);
|
|
3096
|
+
} else {
|
|
3097
|
+
prevIndent = r.indent;
|
|
3098
|
+
effectiveIndents.push(prevIndent);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
const anchorIndex = anchorLine - 1;
|
|
3103
|
+
const anchorIndent = effectiveIndents[anchorIndex];
|
|
3104
|
+
const minIndent = maxLevels === 0 ? 0 : Math.max(0, anchorIndent - maxLevels * TAB_WIDTH);
|
|
3105
|
+
const finalLimit = Math.min(limit, maxLines, records.length);
|
|
3106
|
+
|
|
3107
|
+
if (finalLimit === 1) {
|
|
3108
|
+
console.log('L' + records[anchorIndex].number + ': ' + records[anchorIndex].display);
|
|
3109
|
+
process.exit(0);
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
const out = [records[anchorIndex]];
|
|
3113
|
+
let i = anchorIndex - 1;
|
|
3114
|
+
let j = anchorIndex + 1;
|
|
3115
|
+
let iCounterMinIndent = 0;
|
|
3116
|
+
let jCounterMinIndent = 0;
|
|
3117
|
+
|
|
3118
|
+
while (out.length < finalLimit) {
|
|
3119
|
+
let progressed = 0;
|
|
3120
|
+
|
|
3121
|
+
if (i >= 0) {
|
|
3122
|
+
if (effectiveIndents[i] >= minIndent) {
|
|
3123
|
+
out.unshift(records[i]);
|
|
3124
|
+
progressed++;
|
|
3125
|
+
const curI = i;
|
|
3126
|
+
i--;
|
|
3127
|
+
|
|
3128
|
+
if (effectiveIndents[curI] === minIndent && !includeSiblings) {
|
|
3129
|
+
const allowHeaderComment = includeHeader && isComment(records[curI].raw);
|
|
3130
|
+
const canTakeLine = allowHeaderComment || iCounterMinIndent === 0;
|
|
3131
|
+
if (canTakeLine) {
|
|
3132
|
+
iCounterMinIndent++;
|
|
3133
|
+
} else {
|
|
3134
|
+
out.shift();
|
|
3135
|
+
progressed--;
|
|
3136
|
+
i = -1;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
if (out.length >= finalLimit) break;
|
|
3141
|
+
} else {
|
|
3142
|
+
i = -1;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
if (j < records.length) {
|
|
3147
|
+
if (effectiveIndents[j] >= minIndent) {
|
|
3148
|
+
out.push(records[j]);
|
|
3149
|
+
progressed++;
|
|
3150
|
+
const curJ = j;
|
|
3151
|
+
j++;
|
|
3152
|
+
|
|
3153
|
+
if (effectiveIndents[curJ] === minIndent && !includeSiblings) {
|
|
3154
|
+
if (jCounterMinIndent > 0) {
|
|
3155
|
+
out.pop();
|
|
3156
|
+
progressed--;
|
|
3157
|
+
j = records.length;
|
|
3158
|
+
}
|
|
3159
|
+
jCounterMinIndent++;
|
|
3160
|
+
}
|
|
3161
|
+
} else {
|
|
3162
|
+
j = records.length;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
if (progressed === 0) break;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
while (out.length > 0 && isBlank(out[0].raw)) out.shift();
|
|
3170
|
+
while (out.length > 0 && isBlank(out[out.length - 1].raw)) out.pop();
|
|
3171
|
+
|
|
3172
|
+
for (const r of out) {
|
|
3173
|
+
console.log('L' + r.number + ': ' + r.display);
|
|
2766
3174
|
}
|
|
2767
3175
|
"`;
|
|
2768
3176
|
}
|
|
2769
3177
|
/**
|
|
2770
3178
|
* Node.js command template for writing files.
|
|
2771
3179
|
*/ function buildWriteCommand(filePath, content) {
|
|
2772
|
-
const pathB64 =
|
|
2773
|
-
const contentB64 =
|
|
3180
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3181
|
+
const contentB64 = utf8ToBase64(content);
|
|
2774
3182
|
return `node -e "
|
|
2775
3183
|
const fs = require('fs');
|
|
2776
3184
|
const path = require('path');
|
|
2777
3185
|
|
|
2778
|
-
const filePath =
|
|
2779
|
-
const content =
|
|
3186
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3187
|
+
const content = Buffer.from('${contentB64}', 'base64').toString('utf-8');
|
|
2780
3188
|
|
|
2781
3189
|
if (fs.existsSync(filePath)) {
|
|
2782
|
-
console.error('
|
|
3190
|
+
console.error('${WRITE_EXISTS_OUTPUT}');
|
|
2783
3191
|
process.exit(1);
|
|
2784
3192
|
}
|
|
2785
3193
|
|
|
@@ -2790,18 +3198,45 @@ fs.writeFileSync(filePath, content, 'utf-8');
|
|
|
2790
3198
|
console.log('OK');
|
|
2791
3199
|
"`;
|
|
2792
3200
|
}
|
|
3201
|
+
function buildExistsCommand(filePath) {
|
|
3202
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3203
|
+
return `node -e "
|
|
3204
|
+
const fs = require('fs');
|
|
3205
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3206
|
+
process.exit(fs.existsSync(filePath) ? 0 : 1);
|
|
3207
|
+
"`;
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Node.js command template for appending to files.
|
|
3211
|
+
*/ function buildAppendCommand(filePath, content) {
|
|
3212
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3213
|
+
const contentB64 = utf8ToBase64(content);
|
|
3214
|
+
return `node -e "
|
|
3215
|
+
const fs = require('fs');
|
|
3216
|
+
const path = require('path');
|
|
3217
|
+
|
|
3218
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3219
|
+
const content = Buffer.from('${contentB64}', 'base64').toString('utf-8');
|
|
3220
|
+
|
|
3221
|
+
const parentDir = path.dirname(filePath) || '.';
|
|
3222
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
3223
|
+
|
|
3224
|
+
fs.appendFileSync(filePath, content, 'utf-8');
|
|
3225
|
+
console.log('OK');
|
|
3226
|
+
"`;
|
|
3227
|
+
}
|
|
2793
3228
|
/**
|
|
2794
3229
|
* Node.js command template for editing files.
|
|
2795
3230
|
*/ function buildEditCommand(filePath, oldStr, newStr, replaceAll) {
|
|
2796
|
-
const pathB64 =
|
|
2797
|
-
const oldB64 =
|
|
2798
|
-
const newB64 =
|
|
3231
|
+
const pathB64 = utf8ToBase64(filePath);
|
|
3232
|
+
const oldB64 = utf8ToBase64(oldStr);
|
|
3233
|
+
const newB64 = utf8ToBase64(newStr);
|
|
2799
3234
|
return `node -e "
|
|
2800
3235
|
const fs = require('fs');
|
|
2801
3236
|
|
|
2802
|
-
const filePath =
|
|
2803
|
-
const oldStr =
|
|
2804
|
-
const newStr =
|
|
3237
|
+
const filePath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3238
|
+
const oldStr = Buffer.from('${oldB64}', 'base64').toString('utf-8');
|
|
3239
|
+
const newStr = Buffer.from('${newB64}', 'base64').toString('utf-8');
|
|
2805
3240
|
const replaceAll = ${Boolean(replaceAll)};
|
|
2806
3241
|
|
|
2807
3242
|
let text;
|
|
@@ -2828,16 +3263,16 @@ console.log(count);
|
|
|
2828
3263
|
/**
|
|
2829
3264
|
* Node.js command template for grep operations.
|
|
2830
3265
|
*/ function buildGrepCommand(pattern, searchPath, globPattern) {
|
|
2831
|
-
const patternB64 =
|
|
2832
|
-
const pathB64 =
|
|
2833
|
-
const globB64 = globPattern ?
|
|
3266
|
+
const patternB64 = utf8ToBase64(pattern);
|
|
3267
|
+
const pathB64 = utf8ToBase64(searchPath);
|
|
3268
|
+
const globB64 = globPattern ? utf8ToBase64(globPattern) : '';
|
|
2834
3269
|
return `node -e "
|
|
2835
3270
|
const fs = require('fs');
|
|
2836
3271
|
const path = require('path');
|
|
2837
3272
|
|
|
2838
|
-
const pattern =
|
|
2839
|
-
const searchPath =
|
|
2840
|
-
const globPattern = ${globPattern ? `
|
|
3273
|
+
const pattern = Buffer.from('${patternB64}', 'base64').toString('utf-8');
|
|
3274
|
+
const searchPath = Buffer.from('${pathB64}', 'base64').toString('utf-8');
|
|
3275
|
+
const globPattern = ${globPattern ? `Buffer.from('${globB64}', 'base64').toString('utf-8')` : 'null'};
|
|
2841
3276
|
|
|
2842
3277
|
let regex;
|
|
2843
3278
|
try {
|
|
@@ -2906,6 +3341,13 @@ try {
|
|
|
2906
3341
|
*
|
|
2907
3342
|
* Requires Node.js 20+ on the sandbox host.
|
|
2908
3343
|
*/ class BaseSandbox {
|
|
3344
|
+
async streamExecute(command, onLine, options) {
|
|
3345
|
+
const result = await this.execute(command, options);
|
|
3346
|
+
if (result.output) {
|
|
3347
|
+
onLine(result.output);
|
|
3348
|
+
}
|
|
3349
|
+
return result;
|
|
3350
|
+
}
|
|
2909
3351
|
/**
|
|
2910
3352
|
* List files and directories in the specified directory (non-recursive).
|
|
2911
3353
|
*
|
|
@@ -2913,12 +3355,12 @@ try {
|
|
|
2913
3355
|
* @returns List of FileInfo objects for files and directories directly in the directory.
|
|
2914
3356
|
*/ async lsInfo(path) {
|
|
2915
3357
|
const command = buildLsCommand(path);
|
|
2916
|
-
const result = await this.execute(command);
|
|
3358
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS);
|
|
2917
3359
|
if (result.exitCode !== 0) {
|
|
2918
3360
|
return [];
|
|
2919
3361
|
}
|
|
2920
3362
|
const infos = [];
|
|
2921
|
-
const lines = result.output.trim().split(
|
|
3363
|
+
const lines = result.output.trim().split('\n').filter(Boolean);
|
|
2922
3364
|
for (const line of lines){
|
|
2923
3365
|
try {
|
|
2924
3366
|
const parsed = JSON.parse(line);
|
|
@@ -2935,33 +3377,60 @@ try {
|
|
|
2935
3377
|
return infos;
|
|
2936
3378
|
}
|
|
2937
3379
|
/**
|
|
3380
|
+
* List directory contents recursively with depth control and pagination.
|
|
3381
|
+
*
|
|
3382
|
+
* @param dirPath - Absolute path to directory
|
|
3383
|
+
* @param offset - 1-indexed entry number to start from (default: 1)
|
|
3384
|
+
* @param limit - Maximum number of entries to return (default: 25)
|
|
3385
|
+
* @param depth - Maximum depth to traverse (default: 2)
|
|
3386
|
+
* @returns Human-readable directory tree with indentation
|
|
3387
|
+
*/ async listDir(dirPath, offset = 1, limit = 25, depth = 2) {
|
|
3388
|
+
if (offset < 1) {
|
|
3389
|
+
return 'Error: offset must be a 1-indexed entry number';
|
|
3390
|
+
}
|
|
3391
|
+
if (limit < 1) {
|
|
3392
|
+
return 'Error: limit must be greater than zero';
|
|
3393
|
+
}
|
|
3394
|
+
if (depth < 1) {
|
|
3395
|
+
return 'Error: depth must be greater than zero';
|
|
3396
|
+
}
|
|
3397
|
+
const command = buildListDirCommand(dirPath, offset, limit, depth);
|
|
3398
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS);
|
|
3399
|
+
if (result.exitCode !== 0) {
|
|
3400
|
+
return result.output || `Error: Failed to list directory '${dirPath}'`;
|
|
3401
|
+
}
|
|
3402
|
+
return result.output;
|
|
3403
|
+
}
|
|
3404
|
+
/**
|
|
2938
3405
|
* Read file content with line numbers.
|
|
2939
3406
|
*
|
|
2940
3407
|
* @param filePath - Absolute file path
|
|
2941
|
-
* @param offset - Line offset to start reading from (
|
|
3408
|
+
* @param offset - Line offset to start reading from (1-indexed)
|
|
2942
3409
|
* @param limit - Maximum number of lines to read
|
|
3410
|
+
* @param mode - Read mode: 'slice' (default) or 'indentation'
|
|
3411
|
+
* @param indentation - Configuration for indentation mode
|
|
2943
3412
|
* @returns Formatted file content with line numbers, or error message
|
|
2944
|
-
*/ async read(filePath, offset =
|
|
2945
|
-
const command =
|
|
2946
|
-
const result = await this.execute(command);
|
|
3413
|
+
*/ async read(filePath, offset = 1, limit = 2000, mode = 'slice', indentation) {
|
|
3414
|
+
const command = mode === 'indentation' ? buildIndentationReadCommand(filePath, offset, limit, indentation != null ? indentation : {}) : buildSliceReadCommand(filePath, offset, limit);
|
|
3415
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS);
|
|
2947
3416
|
if (result.exitCode !== 0) {
|
|
2948
|
-
return `Error: File '${filePath}' not found`;
|
|
3417
|
+
return result.output || `Error: File '${filePath}' not found`;
|
|
2949
3418
|
}
|
|
2950
3419
|
return result.output;
|
|
2951
3420
|
}
|
|
2952
3421
|
/**
|
|
2953
3422
|
* Structured search results or error string for invalid input.
|
|
2954
|
-
*/ async grepRaw(pattern, path =
|
|
2955
|
-
const command = buildGrepCommand(pattern, path,
|
|
2956
|
-
const result = await this.execute(command);
|
|
3423
|
+
*/ async grepRaw(pattern, path = '/', include = null) {
|
|
3424
|
+
const command = buildGrepCommand(pattern, path, include);
|
|
3425
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_SEARCH_EXECUTION_OPTIONS);
|
|
2957
3426
|
if (result.exitCode === 1) {
|
|
2958
3427
|
// Check if it's a regex error
|
|
2959
|
-
if (result.output.includes(
|
|
3428
|
+
if (result.output.includes('Invalid regex:')) {
|
|
2960
3429
|
return result.output.trim();
|
|
2961
3430
|
}
|
|
2962
3431
|
}
|
|
2963
3432
|
const matches = [];
|
|
2964
|
-
const lines = result.output.trim().split(
|
|
3433
|
+
const lines = result.output.trim().split('\n').filter(Boolean);
|
|
2965
3434
|
for (const line of lines){
|
|
2966
3435
|
try {
|
|
2967
3436
|
const parsed = JSON.parse(line);
|
|
@@ -2977,12 +3446,24 @@ try {
|
|
|
2977
3446
|
return matches;
|
|
2978
3447
|
}
|
|
2979
3448
|
/**
|
|
3449
|
+
* Search file contents for a regex pattern, returning human-readable output.
|
|
3450
|
+
*/ async grep(pattern, path = '/', include = null) {
|
|
3451
|
+
const result = await this.grepRaw(pattern, path, include);
|
|
3452
|
+
if (typeof result === 'string') {
|
|
3453
|
+
return result;
|
|
3454
|
+
}
|
|
3455
|
+
if (result.length === 0) {
|
|
3456
|
+
return 'No matches found';
|
|
3457
|
+
}
|
|
3458
|
+
return formatGrepOutput(result);
|
|
3459
|
+
}
|
|
3460
|
+
/**
|
|
2980
3461
|
* Structured glob matching returning FileInfo objects.
|
|
2981
|
-
*/ async globInfo(pattern, path =
|
|
3462
|
+
*/ async globInfo(pattern, path = '/') {
|
|
2982
3463
|
const command = buildGlobCommand(path, pattern);
|
|
2983
|
-
const result = await this.execute(command);
|
|
3464
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_SEARCH_EXECUTION_OPTIONS);
|
|
2984
3465
|
const infos = [];
|
|
2985
|
-
const lines = result.output.trim().split(
|
|
3466
|
+
const lines = result.output.trim().split('\n').filter(Boolean);
|
|
2986
3467
|
for (const line of lines){
|
|
2987
3468
|
try {
|
|
2988
3469
|
const parsed = JSON.parse(line);
|
|
@@ -2999,15 +3480,79 @@ try {
|
|
|
2999
3480
|
return infos;
|
|
3000
3481
|
}
|
|
3001
3482
|
/**
|
|
3483
|
+
* Find files matching a glob pattern, returning human-readable output.
|
|
3484
|
+
*/ async glob(pattern, path = '/') {
|
|
3485
|
+
const files = await this.globInfo(pattern, path);
|
|
3486
|
+
if (files.length === 0) {
|
|
3487
|
+
return 'No files found';
|
|
3488
|
+
}
|
|
3489
|
+
return formatGlobOutput(files);
|
|
3490
|
+
}
|
|
3491
|
+
/**
|
|
3002
3492
|
* Create a new file with content.
|
|
3003
3493
|
*/ async write(filePath, content) {
|
|
3004
3494
|
const command = buildWriteCommand(filePath, content);
|
|
3005
|
-
const result = await this.execute(command);
|
|
3495
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS);
|
|
3006
3496
|
if (result.exitCode !== 0) {
|
|
3497
|
+
const output = (result.output || '').trim();
|
|
3498
|
+
if (output.includes(WRITE_EXISTS_OUTPUT)) {
|
|
3499
|
+
return {
|
|
3500
|
+
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
const fallback = await this.writeViaUpload(filePath, content);
|
|
3504
|
+
if (!fallback.error) {
|
|
3505
|
+
return fallback;
|
|
3506
|
+
}
|
|
3507
|
+
return {
|
|
3508
|
+
error: output ? `Failed to write file '${filePath}': ${output}` : `Failed to write file '${filePath}'.`
|
|
3509
|
+
};
|
|
3510
|
+
}
|
|
3511
|
+
return {
|
|
3512
|
+
path: filePath,
|
|
3513
|
+
filesUpdate: null
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
async writeViaUpload(filePath, content) {
|
|
3517
|
+
const existsCheck = await this.execute(buildExistsCommand(filePath), DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS);
|
|
3518
|
+
if (existsCheck.exitCode === 0) {
|
|
3007
3519
|
return {
|
|
3008
3520
|
error: `Cannot write to ${filePath} because it already exists. Read and then make an edit, or write to a new path.`
|
|
3009
3521
|
};
|
|
3010
3522
|
}
|
|
3523
|
+
const uploads = await this.uploadFiles([
|
|
3524
|
+
[
|
|
3525
|
+
filePath,
|
|
3526
|
+
Buffer.from(content, 'utf-8')
|
|
3527
|
+
]
|
|
3528
|
+
]);
|
|
3529
|
+
const first = uploads[0];
|
|
3530
|
+
if (!first) {
|
|
3531
|
+
return {
|
|
3532
|
+
error: `Failed to write file '${filePath}': upload returned no result.`
|
|
3533
|
+
};
|
|
3534
|
+
}
|
|
3535
|
+
if (first.error) {
|
|
3536
|
+
return {
|
|
3537
|
+
error: `Failed to write file '${filePath}': ${first.error}`
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
return {
|
|
3541
|
+
path: filePath,
|
|
3542
|
+
filesUpdate: null
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Append content to a file. Creates the file if it doesn't exist.
|
|
3547
|
+
*/ async append(filePath, content) {
|
|
3548
|
+
const command = buildAppendCommand(filePath, content);
|
|
3549
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS);
|
|
3550
|
+
if (result.exitCode !== 0) {
|
|
3551
|
+
const output = (result.output || '').trim();
|
|
3552
|
+
return {
|
|
3553
|
+
error: output ? `Failed to append to file '${filePath}': ${output}` : `Failed to append to file '${filePath}'.`
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3011
3556
|
return {
|
|
3012
3557
|
path: filePath,
|
|
3013
3558
|
filesUpdate: null
|
|
@@ -3017,7 +3562,7 @@ try {
|
|
|
3017
3562
|
* Edit a file by replacing string occurrences.
|
|
3018
3563
|
*/ async edit(filePath, oldString, newString, replaceAll = false) {
|
|
3019
3564
|
const command = buildEditCommand(filePath, oldString, newString, replaceAll);
|
|
3020
|
-
const result = await this.execute(command);
|
|
3565
|
+
const result = await this.execute(command, DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS);
|
|
3021
3566
|
switch(result.exitCode){
|
|
3022
3567
|
case 0:
|
|
3023
3568
|
{
|
|
@@ -3046,6 +3591,38 @@ try {
|
|
|
3046
3591
|
};
|
|
3047
3592
|
}
|
|
3048
3593
|
}
|
|
3594
|
+
/**
|
|
3595
|
+
* Perform multiple sequential edits on a single file.
|
|
3596
|
+
* All edits are applied sequentially, with each edit operating on the result of the previous edit.
|
|
3597
|
+
* All edits must succeed for the operation to succeed (atomic).
|
|
3598
|
+
*/ async multiEdit(filePath, edits) {
|
|
3599
|
+
if (!edits || edits.length === 0) {
|
|
3600
|
+
return {
|
|
3601
|
+
error: 'No edits provided'
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
const results = [];
|
|
3605
|
+
for (const edit of edits){
|
|
3606
|
+
var _edit_replaceAll;
|
|
3607
|
+
const result = await this.edit(filePath, edit.oldString, edit.newString, (_edit_replaceAll = edit.replaceAll) != null ? _edit_replaceAll : false);
|
|
3608
|
+
results.push(result);
|
|
3609
|
+
// If any edit fails, return immediately with error
|
|
3610
|
+
if (result.error) {
|
|
3611
|
+
return {
|
|
3612
|
+
error: `Multi-edit failed at edit ${results.length}: ${result.error}`,
|
|
3613
|
+
path: filePath,
|
|
3614
|
+
filesUpdate: null,
|
|
3615
|
+
results
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
// All edits succeeded
|
|
3620
|
+
return {
|
|
3621
|
+
path: filePath,
|
|
3622
|
+
filesUpdate: null,
|
|
3623
|
+
results
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3049
3626
|
}
|
|
3050
3627
|
|
|
3051
3628
|
const SANDBOX_PROVIDER = 'SANDBOX_PROVIDER';
|
|
@@ -3108,11 +3685,25 @@ exports.CreateModelClientCommand = CreateModelClientCommand;
|
|
|
3108
3685
|
exports.CredentialsValidateFailedError = CredentialsValidateFailedError;
|
|
3109
3686
|
exports.DATASOURCE_STRATEGY = DATASOURCE_STRATEGY;
|
|
3110
3687
|
exports.DEFAULT_EXECUTION_CONFIG = DEFAULT_EXECUTION_CONFIG;
|
|
3688
|
+
exports.DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES = DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES;
|
|
3689
|
+
exports.DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS = DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS;
|
|
3690
|
+
exports.DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_MS = DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_MS;
|
|
3691
|
+
exports.DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_SEC = DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_SEC;
|
|
3692
|
+
exports.DEFAULT_SANDBOX_FILE_SEARCH_EXECUTION_OPTIONS = DEFAULT_SANDBOX_FILE_SEARCH_EXECUTION_OPTIONS;
|
|
3693
|
+
exports.DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_MS = DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_MS;
|
|
3694
|
+
exports.DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_SEC = DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_SEC;
|
|
3695
|
+
exports.DEFAULT_SANDBOX_SHELL_EXECUTION_OPTIONS = DEFAULT_SANDBOX_SHELL_EXECUTION_OPTIONS;
|
|
3696
|
+
exports.DEFAULT_SANDBOX_SHELL_TIMEOUT_MS = DEFAULT_SANDBOX_SHELL_TIMEOUT_MS;
|
|
3697
|
+
exports.DEFAULT_SANDBOX_SHELL_TIMEOUT_SEC = DEFAULT_SANDBOX_SHELL_TIMEOUT_SEC;
|
|
3111
3698
|
exports.DOCUMENT_SOURCE_STRATEGY = DOCUMENT_SOURCE_STRATEGY;
|
|
3112
3699
|
exports.DOCUMENT_TRANSFORMER_STRATEGY = DOCUMENT_TRANSFORMER_STRATEGY;
|
|
3113
3700
|
exports.DataSourceStrategy = DataSourceStrategy;
|
|
3114
3701
|
exports.DocumentSourceStrategy = DocumentSourceStrategy;
|
|
3115
3702
|
exports.DocumentTransformerStrategy = DocumentTransformerStrategy;
|
|
3703
|
+
exports.FILE_STORAGE_PROVIDER = FILE_STORAGE_PROVIDER;
|
|
3704
|
+
exports.FILE_UPLOAD_TARGET_STRATEGY = FILE_UPLOAD_TARGET_STRATEGY;
|
|
3705
|
+
exports.FileStorageProvider = FileStorageProvider;
|
|
3706
|
+
exports.FileUploadTargetStrategy = FileUploadTargetStrategy;
|
|
3116
3707
|
exports.GLOBAL_ORGANIZATION_SCOPE = GLOBAL_ORGANIZATION_SCOPE;
|
|
3117
3708
|
exports.HANDOFF_PERMISSION_SERVICE_TOKEN = HANDOFF_PERMISSION_SERVICE_TOKEN;
|
|
3118
3709
|
exports.HANDOFF_PROCESSOR_STRATEGY = HANDOFF_PROCESSOR_STRATEGY;
|
|
@@ -3132,6 +3723,7 @@ exports.LargeLanguageModel = LargeLanguageModel;
|
|
|
3132
3723
|
exports.ORGANIZATION_METADATA_KEY = ORGANIZATION_METADATA_KEY;
|
|
3133
3724
|
exports.OpenAICompatibleReranker = OpenAICompatibleReranker;
|
|
3134
3725
|
exports.PERMISSION_OPERATION_METADATA_KEY = PERMISSION_OPERATION_METADATA_KEY;
|
|
3726
|
+
exports.PLUGIN_CONFIG_RESOLVER_TOKEN = PLUGIN_CONFIG_RESOLVER_TOKEN;
|
|
3135
3727
|
exports.PLUGIN_METADATA = PLUGIN_METADATA;
|
|
3136
3728
|
exports.PLUGIN_METADATA_KEY = PLUGIN_METADATA_KEY;
|
|
3137
3729
|
exports.PROVIDE_AI_MODEL_LLM = PROVIDE_AI_MODEL_LLM;
|
|
@@ -3146,6 +3738,7 @@ exports.RequirePermissionOperation = RequirePermissionOperation;
|
|
|
3146
3738
|
exports.RerankModel = RerankModel;
|
|
3147
3739
|
exports.RetrieverStrategy = RetrieverStrategy;
|
|
3148
3740
|
exports.SANDBOX_PROVIDER = SANDBOX_PROVIDER;
|
|
3741
|
+
exports.SANDBOX_SHELL_TIMEOUT_LIMITS_SEC = SANDBOX_SHELL_TIMEOUT_LIMITS_SEC;
|
|
3149
3742
|
exports.STRATEGY_META_KEY = STRATEGY_META_KEY;
|
|
3150
3743
|
exports.SandboxProviderStrategy = SandboxProviderStrategy;
|
|
3151
3744
|
exports.Speech2TextChatModel = Speech2TextChatModel;
|
|
@@ -3167,6 +3760,8 @@ exports.WrapWorkflowNodeExecutionCommand = WrapWorkflowNodeExecutionCommand;
|
|
|
3167
3760
|
exports.XpFileSystem = XpFileSystem;
|
|
3168
3761
|
exports.XpertServerPlugin = XpertServerPlugin;
|
|
3169
3762
|
exports.als = als;
|
|
3763
|
+
exports.appendSandboxMessage = appendSandboxMessage;
|
|
3764
|
+
exports.buildSandboxTimeoutMessage = buildSandboxTimeoutMessage;
|
|
3170
3765
|
exports.calcTokenUsage = calcTokenUsage;
|
|
3171
3766
|
exports.chunkText = chunkText;
|
|
3172
3767
|
exports.countTokensSafe = countTokensSafe;
|
|
@@ -3175,6 +3770,7 @@ exports.createPluginLogger = createPluginLogger;
|
|
|
3175
3770
|
exports.defineAgentMessageType = defineAgentMessageType;
|
|
3176
3771
|
exports.defineChannelMessageType = defineChannelMessageType;
|
|
3177
3772
|
exports.downloadRemoteFile = downloadRemoteFile;
|
|
3773
|
+
exports.formatSandboxTimeout = formatSandboxTimeout;
|
|
3178
3774
|
exports.getErrorMessage = getErrorMessage;
|
|
3179
3775
|
exports.getModelContextSize = getModelContextSize;
|
|
3180
3776
|
exports.getPermissionOperationMetadata = getPermissionOperationMetadata;
|
|
@@ -3189,5 +3785,7 @@ exports.loadYamlFile = loadYamlFile;
|
|
|
3189
3785
|
exports.mergeCredentials = mergeCredentials;
|
|
3190
3786
|
exports.mergeParentChildChunks = mergeParentChildChunks;
|
|
3191
3787
|
exports.normalizeContextSize = normalizeContextSize;
|
|
3788
|
+
exports.resolveSandboxExecutionOptions = resolveSandboxExecutionOptions;
|
|
3192
3789
|
exports.runWithRequestContext = runWithRequestContext;
|
|
3790
|
+
exports.secondsToMilliseconds = secondsToMilliseconds;
|
|
3193
3791
|
exports.sumTokenUsage = sumTokenUsage;
|