@vybestack/llxprt-code-tools 0.10.0-nightly.260613.1adad3b34
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/LICENSE +202 -0
- package/README.md +294 -0
- package/dist/.last_build +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.d.ts +29 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js +30 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js.map +1 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.d.ts +39 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.js +53 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.js.map +1 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.d.ts +140 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js +157 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js.map +1 -0
- package/dist/src/__tests__/red-test-helpers.d.ts +14 -0
- package/dist/src/__tests__/red-test-helpers.js +18 -0
- package/dist/src/__tests__/red-test-helpers.js.map +1 -0
- package/dist/src/formatters/IToolFormatter.d.ts +84 -0
- package/dist/src/formatters/IToolFormatter.js +6 -0
- package/dist/src/formatters/IToolFormatter.js.map +1 -0
- package/dist/src/formatters/ToolFormatter.d.ts +94 -0
- package/dist/src/formatters/ToolFormatter.js +379 -0
- package/dist/src/formatters/ToolFormatter.js.map +1 -0
- package/dist/src/formatters/ToolIdStrategy.d.ts +79 -0
- package/dist/src/formatters/ToolIdStrategy.js +173 -0
- package/dist/src/formatters/ToolIdStrategy.js.map +1 -0
- package/dist/src/formatters/doubleEscapeUtils.d.ts +46 -0
- package/dist/src/formatters/doubleEscapeUtils.js +223 -0
- package/dist/src/formatters/doubleEscapeUtils.js.map +1 -0
- package/dist/src/formatters/index.d.ts +21 -0
- package/dist/src/formatters/index.js +12 -0
- package/dist/src/formatters/index.js.map +1 -0
- package/dist/src/formatters/toolIdNormalization.d.ts +28 -0
- package/dist/src/formatters/toolIdNormalization.js +97 -0
- package/dist/src/formatters/toolIdNormalization.js.map +1 -0
- package/dist/src/formatters/toolNameUtils.d.ts +43 -0
- package/dist/src/formatters/toolNameUtils.js +143 -0
- package/dist/src/formatters/toolNameUtils.js.map +1 -0
- package/dist/src/index.d.ts +88 -0
- package/dist/src/index.js +68 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces/IAsyncTaskService.d.ts +57 -0
- package/dist/src/interfaces/IAsyncTaskService.js +6 -0
- package/dist/src/interfaces/IAsyncTaskService.js.map +1 -0
- package/dist/src/interfaces/IIdeService.d.ts +62 -0
- package/dist/src/interfaces/IIdeService.js +6 -0
- package/dist/src/interfaces/IIdeService.js.map +1 -0
- package/dist/src/interfaces/ILspService.d.ts +54 -0
- package/dist/src/interfaces/ILspService.js +6 -0
- package/dist/src/interfaces/ILspService.js.map +1 -0
- package/dist/src/interfaces/IMcpToolService.d.ts +47 -0
- package/dist/src/interfaces/IMcpToolService.js +6 -0
- package/dist/src/interfaces/IMcpToolService.js.map +1 -0
- package/dist/src/interfaces/IPromptRegistryService.d.ts +51 -0
- package/dist/src/interfaces/IPromptRegistryService.js +6 -0
- package/dist/src/interfaces/IPromptRegistryService.js.map +1 -0
- package/dist/src/interfaces/ISettingsService.d.ts +50 -0
- package/dist/src/interfaces/ISettingsService.js +6 -0
- package/dist/src/interfaces/ISettingsService.js.map +1 -0
- package/dist/src/interfaces/IShellExecutionService.d.ts +55 -0
- package/dist/src/interfaces/IShellExecutionService.js +6 -0
- package/dist/src/interfaces/IShellExecutionService.js.map +1 -0
- package/dist/src/interfaces/IShellToolHost.d.ts +176 -0
- package/dist/src/interfaces/IShellToolHost.js +6 -0
- package/dist/src/interfaces/IShellToolHost.js.map +1 -0
- package/dist/src/interfaces/ISkillService.d.ts +69 -0
- package/dist/src/interfaces/ISkillService.js +6 -0
- package/dist/src/interfaces/ISkillService.js.map +1 -0
- package/dist/src/interfaces/IStorageService.d.ts +42 -0
- package/dist/src/interfaces/IStorageService.js +6 -0
- package/dist/src/interfaces/IStorageService.js.map +1 -0
- package/dist/src/interfaces/ISubagentService.d.ts +112 -0
- package/dist/src/interfaces/ISubagentService.js +6 -0
- package/dist/src/interfaces/ISubagentService.js.map +1 -0
- package/dist/src/interfaces/ITaskToolHost.d.ts +185 -0
- package/dist/src/interfaces/ITaskToolHost.js +6 -0
- package/dist/src/interfaces/ITaskToolHost.js.map +1 -0
- package/dist/src/interfaces/ITodoService.d.ts +79 -0
- package/dist/src/interfaces/ITodoService.js +6 -0
- package/dist/src/interfaces/ITodoService.js.map +1 -0
- package/dist/src/interfaces/IToolHost.d.ts +91 -0
- package/dist/src/interfaces/IToolHost.js +6 -0
- package/dist/src/interfaces/IToolHost.js.map +1 -0
- package/dist/src/interfaces/IToolKeyStorage.d.ts +62 -0
- package/dist/src/interfaces/IToolKeyStorage.js +6 -0
- package/dist/src/interfaces/IToolKeyStorage.js.map +1 -0
- package/dist/src/interfaces/IToolMessageBus.d.ts +63 -0
- package/dist/src/interfaces/IToolMessageBus.js +6 -0
- package/dist/src/interfaces/IToolMessageBus.js.map +1 -0
- package/dist/src/interfaces/IToolRegistryHost.d.ts +43 -0
- package/dist/src/interfaces/IToolRegistryHost.js +6 -0
- package/dist/src/interfaces/IToolRegistryHost.js.map +1 -0
- package/dist/src/interfaces/IWebSearchService.d.ts +16 -0
- package/dist/src/interfaces/IWebSearchService.js +7 -0
- package/dist/src/interfaces/IWebSearchService.js.map +1 -0
- package/dist/src/interfaces/index.d.ts +33 -0
- package/dist/src/interfaces/index.js +6 -0
- package/dist/src/interfaces/index.js.map +1 -0
- package/dist/src/tools/activate-skill.d.ts +26 -0
- package/dist/src/tools/activate-skill.js +121 -0
- package/dist/src/tools/activate-skill.js.map +1 -0
- package/dist/src/tools/apply-patch.d.ts +65 -0
- package/dist/src/tools/apply-patch.js +528 -0
- package/dist/src/tools/apply-patch.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-config.d.ts +67 -0
- package/dist/src/tools/ast-edit/ast-config.js +72 -0
- package/dist/src/tools/ast-edit/ast-config.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.d.ts +40 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.js +410 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.d.ts +21 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.js +178 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.d.ts +26 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.js +149 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.js.map +1 -0
- package/dist/src/tools/ast-edit/constants.d.ts +30 -0
- package/dist/src/tools/ast-edit/constants.js +36 -0
- package/dist/src/tools/ast-edit/constants.js.map +1 -0
- package/dist/src/tools/ast-edit/context-collector.d.ts +25 -0
- package/dist/src/tools/ast-edit/context-collector.js +115 -0
- package/dist/src/tools/ast-edit/context-collector.js.map +1 -0
- package/dist/src/tools/ast-edit/context-optimizer.d.ts +29 -0
- package/dist/src/tools/ast-edit/context-optimizer.js +86 -0
- package/dist/src/tools/ast-edit/context-optimizer.js.map +1 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.d.ts +41 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.js +294 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.js.map +1 -0
- package/dist/src/tools/ast-edit/edit-calculator.d.ts +71 -0
- package/dist/src/tools/ast-edit/edit-calculator.js +249 -0
- package/dist/src/tools/ast-edit/edit-calculator.js.map +1 -0
- package/dist/src/tools/ast-edit/edit-helpers.d.ts +22 -0
- package/dist/src/tools/ast-edit/edit-helpers.js +36 -0
- package/dist/src/tools/ast-edit/edit-helpers.js.map +1 -0
- package/dist/src/tools/ast-edit/language-analysis.d.ts +19 -0
- package/dist/src/tools/ast-edit/language-analysis.js +123 -0
- package/dist/src/tools/ast-edit/language-analysis.js.map +1 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.d.ts +84 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.js +267 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.js.map +1 -0
- package/dist/src/tools/ast-edit/repository-context-provider.d.ts +22 -0
- package/dist/src/tools/ast-edit/repository-context-provider.js +139 -0
- package/dist/src/tools/ast-edit/repository-context-provider.js.map +1 -0
- package/dist/src/tools/ast-edit/types.d.ts +155 -0
- package/dist/src/tools/ast-edit/types.js +7 -0
- package/dist/src/tools/ast-edit/types.js.map +1 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.d.ts +22 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.js +39 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.js.map +1 -0
- package/dist/src/tools/ast-edit.d.ts +43 -0
- package/dist/src/tools/ast-edit.js +183 -0
- package/dist/src/tools/ast-edit.js.map +1 -0
- package/dist/src/tools/ast-grep.d.ts +22 -0
- package/dist/src/tools/ast-grep.js +291 -0
- package/dist/src/tools/ast-grep.js.map +1 -0
- package/dist/src/tools/check-async-tasks.d.ts +46 -0
- package/dist/src/tools/check-async-tasks.js +241 -0
- package/dist/src/tools/check-async-tasks.js.map +1 -0
- package/dist/src/tools/codesearch.d.ts +25 -0
- package/dist/src/tools/codesearch.js +153 -0
- package/dist/src/tools/codesearch.js.map +1 -0
- package/dist/src/tools/delete_line_range.d.ts +41 -0
- package/dist/src/tools/delete_line_range.js +238 -0
- package/dist/src/tools/delete_line_range.js.map +1 -0
- package/dist/src/tools/direct-web-fetch.d.ts +22 -0
- package/dist/src/tools/direct-web-fetch.js +215 -0
- package/dist/src/tools/direct-web-fetch.js.map +1 -0
- package/dist/src/tools/edit-utils.d.ts +53 -0
- package/dist/src/tools/edit-utils.js +250 -0
- package/dist/src/tools/edit-utils.js.map +1 -0
- package/dist/src/tools/edit.d.ts +70 -0
- package/dist/src/tools/edit.js +816 -0
- package/dist/src/tools/edit.js.map +1 -0
- package/dist/src/tools/exa-web-search.d.ts +27 -0
- package/dist/src/tools/exa-web-search.js +153 -0
- package/dist/src/tools/exa-web-search.js.map +1 -0
- package/dist/src/tools/glob.d.ts +55 -0
- package/dist/src/tools/glob.js +284 -0
- package/dist/src/tools/glob.js.map +1 -0
- package/dist/src/tools/google-web-fetch.d.ts +34 -0
- package/dist/src/tools/google-web-fetch.js +417 -0
- package/dist/src/tools/google-web-fetch.js.map +1 -0
- package/dist/src/tools/google-web-search-invocation.d.ts +53 -0
- package/dist/src/tools/google-web-search-invocation.js +180 -0
- package/dist/src/tools/google-web-search-invocation.js.map +1 -0
- package/dist/src/tools/google-web-search.d.ts +16 -0
- package/dist/src/tools/google-web-search.js +34 -0
- package/dist/src/tools/google-web-search.js.map +1 -0
- package/dist/src/tools/grep.d.ts +56 -0
- package/dist/src/tools/grep.js +883 -0
- package/dist/src/tools/grep.js.map +1 -0
- package/dist/src/tools/insert_at_line.d.ts +41 -0
- package/dist/src/tools/insert_at_line.js +287 -0
- package/dist/src/tools/insert_at_line.js.map +1 -0
- package/dist/src/tools/list-subagents.d.ts +31 -0
- package/dist/src/tools/list-subagents.js +122 -0
- package/dist/src/tools/list-subagents.js.map +1 -0
- package/dist/src/tools/ls.d.ts +71 -0
- package/dist/src/tools/ls.js +238 -0
- package/dist/src/tools/ls.js.map +1 -0
- package/dist/src/tools/memoryTool.d.ts +66 -0
- package/dist/src/tools/memoryTool.js +435 -0
- package/dist/src/tools/memoryTool.js.map +1 -0
- package/dist/src/tools/modifiable-tool.d.ts +37 -0
- package/dist/src/tools/modifiable-tool.js +120 -0
- package/dist/src/tools/modifiable-tool.js.map +1 -0
- package/dist/src/tools/read-file.d.ts +49 -0
- package/dist/src/tools/read-file.js +279 -0
- package/dist/src/tools/read-file.js.map +1 -0
- package/dist/src/tools/read-many-files.d.ts +60 -0
- package/dist/src/tools/read-many-files.js +529 -0
- package/dist/src/tools/read-many-files.js.map +1 -0
- package/dist/src/tools/read_line_range.d.ts +47 -0
- package/dist/src/tools/read_line_range.js +248 -0
- package/dist/src/tools/read_line_range.js.map +1 -0
- package/dist/src/tools/ripGrep.d.ts +41 -0
- package/dist/src/tools/ripGrep.js +395 -0
- package/dist/src/tools/ripGrep.js.map +1 -0
- package/dist/src/tools/shell.d.ts +60 -0
- package/dist/src/tools/shell.js +735 -0
- package/dist/src/tools/shell.js.map +1 -0
- package/dist/src/tools/structural-analysis.d.ts +27 -0
- package/dist/src/tools/structural-analysis.js +1089 -0
- package/dist/src/tools/structural-analysis.js.map +1 -0
- package/dist/src/tools/stubs.d.ts +10 -0
- package/dist/src/tools/stubs.js +6 -0
- package/dist/src/tools/stubs.js.map +1 -0
- package/dist/src/tools/task.d.ts +41 -0
- package/dist/src/tools/task.js +195 -0
- package/dist/src/tools/task.js.map +1 -0
- package/dist/src/tools/todo-events.d.ts +22 -0
- package/dist/src/tools/todo-events.js +24 -0
- package/dist/src/tools/todo-events.js.map +1 -0
- package/dist/src/tools/todo-pause.d.ts +29 -0
- package/dist/src/tools/todo-pause.js +172 -0
- package/dist/src/tools/todo-pause.js.map +1 -0
- package/dist/src/tools/todo-read.d.ts +18 -0
- package/dist/src/tools/todo-read.js +98 -0
- package/dist/src/tools/todo-read.js.map +1 -0
- package/dist/src/tools/todo-store.d.ts +35 -0
- package/dist/src/tools/todo-store.js +124 -0
- package/dist/src/tools/todo-store.js.map +1 -0
- package/dist/src/tools/todo-write.d.ts +29 -0
- package/dist/src/tools/todo-write.js +366 -0
- package/dist/src/tools/todo-write.js.map +1 -0
- package/dist/src/tools/tool-registry.d.ts +156 -0
- package/dist/src/tools/tool-registry.js +623 -0
- package/dist/src/tools/tool-registry.js.map +1 -0
- package/dist/src/tools/tools.d.ts +403 -0
- package/dist/src/tools/tools.js +519 -0
- package/dist/src/tools/tools.js.map +1 -0
- package/dist/src/tools/write-file.d.ts +45 -0
- package/dist/src/tools/write-file.js +320 -0
- package/dist/src/tools/write-file.js.map +1 -0
- package/dist/src/types/index.d.ts +17 -0
- package/dist/src/types/index.js +9 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/provider-content-types.d.ts +56 -0
- package/dist/src/types/provider-content-types.js +6 -0
- package/dist/src/types/provider-content-types.js.map +1 -0
- package/dist/src/types/todo-schemas.d.ts +263 -0
- package/dist/src/types/todo-schemas.js +32 -0
- package/dist/src/types/todo-schemas.js.map +1 -0
- package/dist/src/types/tool-confirmation-types.d.ts +34 -0
- package/dist/src/types/tool-confirmation-types.js +29 -0
- package/dist/src/types/tool-confirmation-types.js.map +1 -0
- package/dist/src/types/tool-context.d.ts +31 -0
- package/dist/src/types/tool-context.js +6 -0
- package/dist/src/types/tool-context.js.map +1 -0
- package/dist/src/types/tool-error.d.ts +69 -0
- package/dist/src/types/tool-error.js +94 -0
- package/dist/src/types/tool-error.js.map +1 -0
- package/dist/src/types/tool-names.d.ts +57 -0
- package/dist/src/types/tool-names.js +64 -0
- package/dist/src/types/tool-names.js.map +1 -0
- package/dist/src/utils/EmojiFilter.d.ts +134 -0
- package/dist/src/utils/EmojiFilter.js +370 -0
- package/dist/src/utils/EmojiFilter.js.map +1 -0
- package/dist/src/utils/ast-grep-utils.d.ts +42 -0
- package/dist/src/utils/ast-grep-utils.js +140 -0
- package/dist/src/utils/ast-grep-utils.js.map +1 -0
- package/dist/src/utils/debugLogger.d.ts +11 -0
- package/dist/src/utils/debugLogger.js +16 -0
- package/dist/src/utils/debugLogger.js.map +1 -0
- package/dist/src/utils/diffOptions.d.ts +14 -0
- package/dist/src/utils/diffOptions.js +46 -0
- package/dist/src/utils/diffOptions.js.map +1 -0
- package/dist/src/utils/editor.d.ts +39 -0
- package/dist/src/utils/editor.js +212 -0
- package/dist/src/utils/editor.js.map +1 -0
- package/dist/src/utils/ensure-dirs.d.ts +10 -0
- package/dist/src/utils/ensure-dirs.js +16 -0
- package/dist/src/utils/ensure-dirs.js.map +1 -0
- package/dist/src/utils/errors.d.ts +59 -0
- package/dist/src/utils/errors.js +178 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/fetch.d.ts +11 -0
- package/dist/src/utils/fetch.js +74 -0
- package/dist/src/utils/fetch.js.map +1 -0
- package/dist/src/utils/fileUtils.d.ts +32 -0
- package/dist/src/utils/fileUtils.js +363 -0
- package/dist/src/utils/fileUtils.js.map +1 -0
- package/dist/src/utils/fuzzy-replacer.d.ts +61 -0
- package/dist/src/utils/fuzzy-replacer.js +492 -0
- package/dist/src/utils/fuzzy-replacer.js.map +1 -0
- package/dist/src/utils/gitLineChanges.d.ts +12 -0
- package/dist/src/utils/gitLineChanges.js +171 -0
- package/dist/src/utils/gitLineChanges.js.map +1 -0
- package/dist/src/utils/gitUtils.d.ts +6 -0
- package/dist/src/utils/gitUtils.js +31 -0
- package/dist/src/utils/gitUtils.js.map +1 -0
- package/dist/src/utils/lsp-diagnostics-helper.d.ts +27 -0
- package/dist/src/utils/lsp-diagnostics-helper.js +62 -0
- package/dist/src/utils/lsp-diagnostics-helper.js.map +1 -0
- package/dist/src/utils/mediaUtils.d.ts +38 -0
- package/dist/src/utils/mediaUtils.js +22 -0
- package/dist/src/utils/mediaUtils.js.map +1 -0
- package/dist/src/utils/pathValidation.d.ts +7 -0
- package/dist/src/utils/pathValidation.js +39 -0
- package/dist/src/utils/pathValidation.js.map +1 -0
- package/dist/src/utils/paths.d.ts +7 -0
- package/dist/src/utils/paths.js +158 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/resolveTextSearchTarget.d.ts +17 -0
- package/dist/src/utils/resolveTextSearchTarget.js +45 -0
- package/dist/src/utils/resolveTextSearchTarget.js.map +1 -0
- package/dist/src/utils/retry.d.ts +17 -0
- package/dist/src/utils/retry.js +185 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/utils/ripgrepPathResolver.d.ts +23 -0
- package/dist/src/utils/ripgrepPathResolver.js +179 -0
- package/dist/src/utils/ripgrepPathResolver.js.map +1 -0
- package/dist/src/utils/safeJsonStringify.d.ts +9 -0
- package/dist/src/utils/safeJsonStringify.js +21 -0
- package/dist/src/utils/safeJsonStringify.js.map +1 -0
- package/dist/src/utils/schemaValidator.d.ts +13 -0
- package/dist/src/utils/schemaValidator.js +275 -0
- package/dist/src/utils/schemaValidator.js.map +1 -0
- package/dist/src/utils/terminalSerializer.d.ts +33 -0
- package/dist/src/utils/terminalSerializer.js +6 -0
- package/dist/src/utils/terminalSerializer.js.map +1 -0
- package/dist/src/utils/todoContextTracker.d.ts +12 -0
- package/dist/src/utils/todoContextTracker.js +28 -0
- package/dist/src/utils/todoContextTracker.js.map +1 -0
- package/dist/src/utils/todoFormatter.d.ts +24 -0
- package/dist/src/utils/todoFormatter.js +157 -0
- package/dist/src/utils/todoFormatter.js.map +1 -0
- package/dist/src/utils/todoReminderService.d.ts +22 -0
- package/dist/src/utils/todoReminderService.js +43 -0
- package/dist/src/utils/todoReminderService.js.map +1 -0
- package/dist/src/utils/tool-key-storage-facade.d.ts +32 -0
- package/dist/src/utils/tool-key-storage-facade.js +56 -0
- package/dist/src/utils/tool-key-storage-facade.js.map +1 -0
- package/dist/src/utils/tool-key-storage-types.d.ts +56 -0
- package/dist/src/utils/tool-key-storage-types.js +56 -0
- package/dist/src/utils/tool-key-storage-types.js.map +1 -0
- package/dist/src/utils/toolOutputLimiter.d.ts +29 -0
- package/dist/src/utils/toolOutputLimiter.js +107 -0
- package/dist/src/utils/toolOutputLimiter.js.map +1 -0
- package/dist/src/utils/unicodeUtils.d.ts +55 -0
- package/dist/src/utils/unicodeUtils.js +129 -0
- package/dist/src/utils/unicodeUtils.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @plan:PLAN-20260608-ISSUE1585.P11
|
|
3
|
+
* @requirement:REQ-INTERFACE-OWNERSHIP, REQ-API-001
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @license
|
|
7
|
+
* Copyright 2025 Vybestack LLC
|
|
8
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
9
|
+
*/
|
|
10
|
+
/* eslint-disable complexity, max-lines -- Shell tool delegated through IShellToolHost. */
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
import crypto from 'node:crypto';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import os, { EOL } from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { BaseDeclarativeTool, BaseToolInvocation, Kind, ToolConfirmationOutcome, } from './tools.js';
|
|
17
|
+
import { ToolErrorType } from '../types/tool-error.js';
|
|
18
|
+
/** Throttle interval for shell output updates. */
|
|
19
|
+
export const OUTPUT_UPDATE_INTERVAL_MS = 100;
|
|
20
|
+
function isShellToolHost(host) {
|
|
21
|
+
return 'executeShellCommand' in host;
|
|
22
|
+
}
|
|
23
|
+
function unwrapCommandForExecutionService(command) {
|
|
24
|
+
const match = /^\{ ([\s\S]*) \}; __code=\$\?; pgrep -g 0 >/.exec(command);
|
|
25
|
+
if (!match) {
|
|
26
|
+
return command;
|
|
27
|
+
}
|
|
28
|
+
const innerCommand = match[1].trim();
|
|
29
|
+
return innerCommand.endsWith(';')
|
|
30
|
+
? innerCommand.slice(0, -1).trimEnd()
|
|
31
|
+
: innerCommand;
|
|
32
|
+
}
|
|
33
|
+
function createShellToolHostFromExecutionService(service) {
|
|
34
|
+
const targetDir = process.cwd();
|
|
35
|
+
return {
|
|
36
|
+
getTargetDir: () => targetDir,
|
|
37
|
+
getWorkspaceContext: () => ({
|
|
38
|
+
getDirectories: () => [targetDir],
|
|
39
|
+
isPathWithinWorkspace: (resolvedPath) => resolvedPath === targetDir ||
|
|
40
|
+
resolvedPath.startsWith(`${targetDir}${path.sep}`),
|
|
41
|
+
}),
|
|
42
|
+
isCommandAllowed: (command) => {
|
|
43
|
+
const allowed = service.isCommandAllowed(command);
|
|
44
|
+
return allowed
|
|
45
|
+
? { allowed: true }
|
|
46
|
+
: {
|
|
47
|
+
allowed: false,
|
|
48
|
+
reason: `Command denied by shell policy: ${command}`,
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
isShellInvocationAllowlisted: () => false,
|
|
52
|
+
isInteractive: () => true,
|
|
53
|
+
isYoloMode: () => false,
|
|
54
|
+
getDebugMode: () => false,
|
|
55
|
+
getShellExecutionConfig: () => ({
|
|
56
|
+
shouldUseNodePty: false,
|
|
57
|
+
executionOptions: {},
|
|
58
|
+
}),
|
|
59
|
+
getTimeoutConfig: () => ({
|
|
60
|
+
timeoutSeconds: undefined,
|
|
61
|
+
defaultTimeoutSeconds: 60,
|
|
62
|
+
}),
|
|
63
|
+
getOutputLimits: () => ({}),
|
|
64
|
+
executeShellCommand: async (command) => {
|
|
65
|
+
const result = await service.execute(unwrapCommandForExecutionService(command));
|
|
66
|
+
const error = result.stderr.trim() !== '' || result.exitCode !== 0
|
|
67
|
+
? new Error(result.stderr.trim() !== ''
|
|
68
|
+
? result.stderr.trim()
|
|
69
|
+
: `Command failed with exit code ${result.exitCode}`)
|
|
70
|
+
: null;
|
|
71
|
+
return {
|
|
72
|
+
output: result.stdout,
|
|
73
|
+
exitCode: result.exitCode,
|
|
74
|
+
signal: null,
|
|
75
|
+
error,
|
|
76
|
+
aborted: result.aborted,
|
|
77
|
+
pid: undefined,
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
getCommandRoots: (command) => {
|
|
81
|
+
const root = command.trim().split(/\s+/)[0];
|
|
82
|
+
return root ? [root] : [];
|
|
83
|
+
},
|
|
84
|
+
stripShellWrapper: (command) => command,
|
|
85
|
+
validatePathWithinWorkspace: (_workspaceContext, dirPath) => {
|
|
86
|
+
const resolvedPath = path.isAbsolute(dirPath)
|
|
87
|
+
? dirPath
|
|
88
|
+
: path.resolve(targetDir, dirPath);
|
|
89
|
+
return resolvedPath === targetDir ||
|
|
90
|
+
resolvedPath.startsWith(`${targetDir}${path.sep}`)
|
|
91
|
+
? null
|
|
92
|
+
: `Directory '${dirPath}' is not a registered workspace directory`;
|
|
93
|
+
},
|
|
94
|
+
isPtyActive: () => false,
|
|
95
|
+
formatMemoryUsage: (bytes) => {
|
|
96
|
+
if (bytes < 1024)
|
|
97
|
+
return `${bytes} bytes`;
|
|
98
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
99
|
+
},
|
|
100
|
+
trySummarizeOutput: async (content) => content,
|
|
101
|
+
getSummarizeConfig: () => undefined,
|
|
102
|
+
limitOutputTokens: (content) => ({ content, wasTruncated: false }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function applyGrepFilter(content, params, descriptionParts) {
|
|
106
|
+
const grepPattern = typeof params.grep_pattern === 'string' && params.grep_pattern !== ''
|
|
107
|
+
? params.grep_pattern
|
|
108
|
+
: undefined;
|
|
109
|
+
if (grepPattern === undefined) {
|
|
110
|
+
return content;
|
|
111
|
+
}
|
|
112
|
+
const invertMatch = params.grep_flags?.includes('-v') === true;
|
|
113
|
+
const options = params.grep_flags?.includes('-i') === true ? 'i' : '';
|
|
114
|
+
const regex = new RegExp(grepPattern, options);
|
|
115
|
+
const filteredLines = content
|
|
116
|
+
.split('\n')
|
|
117
|
+
.filter((line) => (invertMatch ? !regex.test(line) : regex.test(line)));
|
|
118
|
+
descriptionParts.push(`grep_pattern filter: "${grepPattern}"`);
|
|
119
|
+
if (params.grep_flags !== undefined && params.grep_flags.length > 0) {
|
|
120
|
+
descriptionParts.push(`flags: [${params.grep_flags.join(', ')}]`);
|
|
121
|
+
}
|
|
122
|
+
return filteredLines.join('\n');
|
|
123
|
+
}
|
|
124
|
+
function applyOutputFilters(output, params) {
|
|
125
|
+
let content = output;
|
|
126
|
+
const descriptionParts = [];
|
|
127
|
+
content = applyGrepFilter(content, params, descriptionParts);
|
|
128
|
+
if (params.head_lines !== undefined && params.head_lines !== 0) {
|
|
129
|
+
validatePositiveInteger(params.head_lines, 'head_lines');
|
|
130
|
+
const lines = content.split('\n');
|
|
131
|
+
const headLines = lines.slice(0, params.head_lines);
|
|
132
|
+
const wasTruncated = lines.length > params.head_lines;
|
|
133
|
+
content = headLines.join('\n');
|
|
134
|
+
descriptionParts.push(`head_lines filter: showing first ${params.head_lines} lines${wasTruncated ? ` (of ${lines.length} total)` : ''}`);
|
|
135
|
+
}
|
|
136
|
+
if (params.tail_lines !== undefined && params.tail_lines !== 0) {
|
|
137
|
+
validatePositiveInteger(params.tail_lines, 'tail_lines');
|
|
138
|
+
const lines = content.split('\n');
|
|
139
|
+
const tailLines = lines.slice(-params.tail_lines);
|
|
140
|
+
const wasTruncated = lines.length > params.tail_lines;
|
|
141
|
+
content = tailLines.join('\n');
|
|
142
|
+
descriptionParts.push(`tail_lines filter: showing last ${params.tail_lines} lines${wasTruncated ? ` (of ${lines.length} total)` : ''}`);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content,
|
|
146
|
+
description: descriptionParts.length > 0 ? descriptionParts.join('; ') : undefined,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function validatePositiveInteger(value, paramName) {
|
|
150
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
151
|
+
throw new Error(`${paramName} must be a positive integer, got: ${value}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function validateGrepFlags(flags) {
|
|
155
|
+
const validFlags = ['-i', '-v', '-E', '-F', '-x', '-w'];
|
|
156
|
+
for (const flag of flags) {
|
|
157
|
+
if (!validFlags.includes(flag)) {
|
|
158
|
+
throw new Error(`Invalid grep flag: ${flag}. Valid flags: ${validFlags.join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function isValidBackgroundPid(linePid, mainPid) {
|
|
163
|
+
if (mainPid === undefined || mainPid === 0) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return linePid !== mainPid;
|
|
167
|
+
}
|
|
168
|
+
function buildCommandToExecute(strippedCommand, isWindows, tempFilePath) {
|
|
169
|
+
if (isWindows) {
|
|
170
|
+
return strippedCommand;
|
|
171
|
+
}
|
|
172
|
+
let command = strippedCommand.trim();
|
|
173
|
+
if (!command.endsWith('&')) {
|
|
174
|
+
command += ';';
|
|
175
|
+
}
|
|
176
|
+
return `{ ${command} }; __code=$?; pgrep -g 0 >${tempFilePath} 2>&1; exit $__code;`;
|
|
177
|
+
}
|
|
178
|
+
function parsePgrepFile(tempFilePath, mainPid) {
|
|
179
|
+
const pids = [];
|
|
180
|
+
if (!fs.existsSync(tempFilePath)) {
|
|
181
|
+
return pids;
|
|
182
|
+
}
|
|
183
|
+
const pgrepLines = fs
|
|
184
|
+
.readFileSync(tempFilePath, 'utf8')
|
|
185
|
+
.split(EOL)
|
|
186
|
+
.filter(Boolean);
|
|
187
|
+
for (const line of pgrepLines) {
|
|
188
|
+
if (!/^\d+$/.test(line)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const linePid = Number(line);
|
|
192
|
+
if (isValidBackgroundPid(linePid, mainPid)) {
|
|
193
|
+
pids.push(linePid);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return pids;
|
|
197
|
+
}
|
|
198
|
+
function collectProcessInfo(result, tempFilePath, signal) {
|
|
199
|
+
const backgroundPIDs = result.backgroundPIDs ?? [];
|
|
200
|
+
let pgid = result.pgid ?? null;
|
|
201
|
+
if (os.platform() !== 'win32') {
|
|
202
|
+
backgroundPIDs.push(...parsePgrepFile(tempFilePath, result.pid));
|
|
203
|
+
if (pgid === null &&
|
|
204
|
+
result.pid !== undefined &&
|
|
205
|
+
result.pid !== 0 &&
|
|
206
|
+
signal.aborted === false) {
|
|
207
|
+
try {
|
|
208
|
+
const psResult = spawnSync('ps', [
|
|
209
|
+
'-o',
|
|
210
|
+
'pgid=',
|
|
211
|
+
'-p',
|
|
212
|
+
String(result.pid),
|
|
213
|
+
]);
|
|
214
|
+
if (psResult.status === 0 && psResult.stdout.toString().trim()) {
|
|
215
|
+
pgid = parseInt(psResult.stdout.toString().trim(), 10);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
pgid = null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { backgroundPIDs, pgid };
|
|
224
|
+
}
|
|
225
|
+
export class ShellToolInvocation extends BaseToolInvocation {
|
|
226
|
+
host;
|
|
227
|
+
allowlist = new Set();
|
|
228
|
+
constructor(host, params, allowlist, messageBus) {
|
|
229
|
+
super(params, messageBus);
|
|
230
|
+
this.host = host;
|
|
231
|
+
this.allowlist = allowlist;
|
|
232
|
+
}
|
|
233
|
+
getToolName() {
|
|
234
|
+
return ShellTool.Name;
|
|
235
|
+
}
|
|
236
|
+
getDirPath() {
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing
|
|
238
|
+
return this.params.dir_path || this.params.directory;
|
|
239
|
+
}
|
|
240
|
+
getDescription() {
|
|
241
|
+
let description = `${this.params.command}`;
|
|
242
|
+
const dirPath = this.getDirPath();
|
|
243
|
+
if (dirPath) {
|
|
244
|
+
description += ` [in ${dirPath}]`;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
description += ` [current working directory ${process.cwd()}]`;
|
|
248
|
+
}
|
|
249
|
+
if (this.params.description) {
|
|
250
|
+
description += ` (${this.params.description.replace(/\n/g, ' ')})`;
|
|
251
|
+
}
|
|
252
|
+
return description;
|
|
253
|
+
}
|
|
254
|
+
getPolicyUpdateOptions(outcome) {
|
|
255
|
+
if (outcome === ToolConfirmationOutcome.ProceedAlwaysAndSave ||
|
|
256
|
+
outcome === ToolConfirmationOutcome.ProceedAlways) {
|
|
257
|
+
const command = this.host.stripShellWrapper(this.params.command);
|
|
258
|
+
const rootCommands = [...new Set(this.host.getCommandRoots(command))];
|
|
259
|
+
if (rootCommands.length > 0) {
|
|
260
|
+
return { commandPrefix: rootCommands };
|
|
261
|
+
}
|
|
262
|
+
return { commandPrefix: this.params.command };
|
|
263
|
+
}
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
async shouldConfirmExecute(_abortSignal) {
|
|
267
|
+
const command = this.host.stripShellWrapper(this.params.command);
|
|
268
|
+
const rootCommands = [...new Set(this.host.getCommandRoots(command))];
|
|
269
|
+
if (!this.host.isInteractive() && !this.host.isYoloMode()) {
|
|
270
|
+
if (this.isInvocationAllowlisted(command)) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
throw new Error(`Command "${command}" is not in the list of allowed tools for non-interactive mode.`);
|
|
274
|
+
}
|
|
275
|
+
const commandsToConfirm = rootCommands.filter((cmd) => !this.allowlist.has(cmd));
|
|
276
|
+
if (commandsToConfirm.length === 0) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const confirmationDetails = {
|
|
280
|
+
type: 'exec',
|
|
281
|
+
title: 'Confirm Shell Command',
|
|
282
|
+
command: this.params.command,
|
|
283
|
+
rootCommand: commandsToConfirm.join(', '),
|
|
284
|
+
rootCommands: commandsToConfirm,
|
|
285
|
+
onConfirm: async (outcome, payload) => {
|
|
286
|
+
if (outcome === ToolConfirmationOutcome.ProceedAlways ||
|
|
287
|
+
outcome === ToolConfirmationOutcome.ProceedAlwaysAndSave) {
|
|
288
|
+
commandsToConfirm.forEach((cmd) => {
|
|
289
|
+
this.allowlist.add(cmd);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
await this.publishPolicyUpdate(outcome);
|
|
293
|
+
if (outcome === ToolConfirmationOutcome.SuggestEdit) {
|
|
294
|
+
const editedCommand = payload?.editedCommand?.trim();
|
|
295
|
+
if (!editedCommand) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
this.params.command = editedCommand;
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
return confirmationDetails;
|
|
303
|
+
}
|
|
304
|
+
async execute(signal, updateOutput) {
|
|
305
|
+
this.validateFilterParams();
|
|
306
|
+
const strippedCommand = this.host.stripShellWrapper(this.params.command);
|
|
307
|
+
const timeoutConfig = this.host.getTimeoutConfig();
|
|
308
|
+
const timeoutSeconds = this.resolveTimeoutSeconds(this.params.timeout_seconds, timeoutConfig.defaultTimeoutSeconds, timeoutConfig.timeoutSeconds);
|
|
309
|
+
const timeoutMs = timeoutSeconds === undefined ? undefined : timeoutSeconds * 1000;
|
|
310
|
+
const timeoutController = new AbortController();
|
|
311
|
+
const timeoutId = timeoutMs === undefined
|
|
312
|
+
? null
|
|
313
|
+
: setTimeout(() => timeoutController.abort(), timeoutMs);
|
|
314
|
+
const onUserAbort = () => {
|
|
315
|
+
if (timeoutId !== null) {
|
|
316
|
+
clearTimeout(timeoutId);
|
|
317
|
+
}
|
|
318
|
+
timeoutController.abort();
|
|
319
|
+
};
|
|
320
|
+
signal.addEventListener('abort', onUserAbort, { once: true });
|
|
321
|
+
if (signal.aborted) {
|
|
322
|
+
onUserAbort();
|
|
323
|
+
return this.createPreCancelledResult();
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
return await this.executeShell(strippedCommand, signal, timeoutController, timeoutSeconds, timeoutConfig.defaultTimeoutSeconds, timeoutId, updateOutput);
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
if (timeoutId !== null) {
|
|
330
|
+
clearTimeout(timeoutId);
|
|
331
|
+
}
|
|
332
|
+
signal.removeEventListener('abort', onUserAbort);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async executeShell(strippedCommand, signal, timeoutController, timeoutSeconds, defaultTimeoutSeconds, _timeoutId, updateOutput) {
|
|
336
|
+
const combinedSignal = timeoutController.signal;
|
|
337
|
+
const cwd = this.resolveCwd();
|
|
338
|
+
const isWindows = os.platform() === 'win32';
|
|
339
|
+
const tempFileName = `shell_pgrep_${crypto
|
|
340
|
+
.randomBytes(6)
|
|
341
|
+
.toString('hex')}.tmp`;
|
|
342
|
+
const tempFilePath = path.join(os.tmpdir(), tempFileName);
|
|
343
|
+
const commandToExecute = buildCommandToExecute(strippedCommand, isWindows, tempFilePath);
|
|
344
|
+
try {
|
|
345
|
+
let cumulativeOutput = '';
|
|
346
|
+
let lastUpdateTime = 0;
|
|
347
|
+
let isBinaryStream = false;
|
|
348
|
+
const executionResult = await this.host.executeShellCommand(commandToExecute, cwd, (event) => {
|
|
349
|
+
if (!updateOutput)
|
|
350
|
+
return;
|
|
351
|
+
let shouldUpdate = false;
|
|
352
|
+
switch (event.type) {
|
|
353
|
+
case 'data': {
|
|
354
|
+
if (isBinaryStream)
|
|
355
|
+
break;
|
|
356
|
+
cumulativeOutput = event.chunk ?? '';
|
|
357
|
+
shouldUpdate = true;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case 'binary_detected':
|
|
361
|
+
isBinaryStream = true;
|
|
362
|
+
cumulativeOutput = '[Binary output detected. Halting stream...]';
|
|
363
|
+
shouldUpdate = true;
|
|
364
|
+
break;
|
|
365
|
+
case 'binary_progress':
|
|
366
|
+
isBinaryStream = true;
|
|
367
|
+
cumulativeOutput = `[Receiving binary output... ${this.host.formatMemoryUsage(event.bytesReceived ?? 0)} received]`;
|
|
368
|
+
if (Date.now() - lastUpdateTime > OUTPUT_UPDATE_INTERVAL_MS) {
|
|
369
|
+
shouldUpdate = true;
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
default: {
|
|
373
|
+
throw new Error('An unhandled ShellOutputEvent was found.');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (shouldUpdate) {
|
|
377
|
+
updateOutput(cumulativeOutput);
|
|
378
|
+
lastUpdateTime = Date.now();
|
|
379
|
+
}
|
|
380
|
+
}, combinedSignal);
|
|
381
|
+
const result = executionResult;
|
|
382
|
+
const { backgroundPIDs, pgid } = collectProcessInfo(result, tempFilePath, signal);
|
|
383
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- defensive boundary
|
|
384
|
+
const rawOutput = result?.output ?? '';
|
|
385
|
+
const filterInfo = applyOutputFilters(rawOutput, this.params);
|
|
386
|
+
const filteredOutput = filterInfo.content;
|
|
387
|
+
const timeoutTriggered = timeoutController.signal.aborted === true &&
|
|
388
|
+
signal.aborted === false;
|
|
389
|
+
const { llmContent, returnDisplayMessage } = this.formatOutputContent(result, rawOutput, filteredOutput, commandToExecute, backgroundPIDs, pgid, timeoutTriggered, timeoutSeconds, defaultTimeoutSeconds);
|
|
390
|
+
const displayWithFilter = this.applyFilterDescription(returnDisplayMessage, filterInfo);
|
|
391
|
+
const executionError = this.buildExecutionError(result, llmContent, timeoutTriggered);
|
|
392
|
+
let llmPayload = llmContent;
|
|
393
|
+
const shellToolConfig = this.host.getSummarizeConfig();
|
|
394
|
+
if (shellToolConfig !== undefined && result.aborted !== true) {
|
|
395
|
+
llmPayload = await this.host.trySummarizeOutput(llmContent, signal, shellToolConfig.tokenBudget);
|
|
396
|
+
}
|
|
397
|
+
const limitedResult = this.host.limitOutputTokens(llmPayload);
|
|
398
|
+
if (limitedResult.wasTruncated) {
|
|
399
|
+
return {
|
|
400
|
+
llmContent: `${limitedResult.content}\n\n(output exceeded token limit)`,
|
|
401
|
+
returnDisplay: displayWithFilter,
|
|
402
|
+
...executionError,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
llmContent: limitedResult.content,
|
|
407
|
+
returnDisplay: displayWithFilter,
|
|
408
|
+
...executionError,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
finally {
|
|
412
|
+
if (fs.existsSync(tempFilePath)) {
|
|
413
|
+
fs.unlinkSync(tempFilePath);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
validateFilterParams() {
|
|
418
|
+
if (this.params.head_lines !== undefined && this.params.head_lines !== 0) {
|
|
419
|
+
validatePositiveInteger(this.params.head_lines, 'head_lines');
|
|
420
|
+
}
|
|
421
|
+
if (this.params.tail_lines !== undefined && this.params.tail_lines !== 0) {
|
|
422
|
+
validatePositiveInteger(this.params.tail_lines, 'tail_lines');
|
|
423
|
+
}
|
|
424
|
+
const grepPattern = typeof this.params.grep_pattern === 'string' &&
|
|
425
|
+
this.params.grep_pattern !== ''
|
|
426
|
+
? this.params.grep_pattern
|
|
427
|
+
: undefined;
|
|
428
|
+
if (grepPattern !== undefined && grepPattern.trim() === '') {
|
|
429
|
+
throw new Error('grep_pattern cannot be empty');
|
|
430
|
+
}
|
|
431
|
+
if (this.params.grep_flags) {
|
|
432
|
+
validateGrepFlags(this.params.grep_flags);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
resolveCwd() {
|
|
436
|
+
const dirPath = this.getDirPath();
|
|
437
|
+
if (!dirPath) {
|
|
438
|
+
return this.host.getTargetDir();
|
|
439
|
+
}
|
|
440
|
+
const targetDir = this.host.getTargetDir();
|
|
441
|
+
const resolved = this.resolveDirPath(dirPath, targetDir);
|
|
442
|
+
const pathError = this.host.validatePathWithinWorkspace(this.host.getWorkspaceContext(), resolved, 'Directory');
|
|
443
|
+
if (pathError) {
|
|
444
|
+
throw new Error(pathError);
|
|
445
|
+
}
|
|
446
|
+
return resolved;
|
|
447
|
+
}
|
|
448
|
+
resolveDirPath(dirPath, targetDir) {
|
|
449
|
+
if (path.isAbsolute(dirPath)) {
|
|
450
|
+
return dirPath;
|
|
451
|
+
}
|
|
452
|
+
return path.resolve(targetDir, dirPath);
|
|
453
|
+
}
|
|
454
|
+
formatOutputContent(result, rawOutput, filteredOutput, commandToExecute, backgroundPIDs, pgid, timeoutTriggered, timeoutSeconds, defaultTimeoutSeconds) {
|
|
455
|
+
let llmContent = '';
|
|
456
|
+
let returnDisplayMessage = '';
|
|
457
|
+
if (result.aborted === true) {
|
|
458
|
+
if (timeoutTriggered) {
|
|
459
|
+
llmContent = `Command timed out after ${timeoutSeconds ?? defaultTimeoutSeconds}s (timeout_seconds).`;
|
|
460
|
+
if (rawOutput.trim() !== '') {
|
|
461
|
+
llmContent += ` Partial output:\n${rawOutput}`;
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
llmContent += ' There was no output before timeout.';
|
|
465
|
+
}
|
|
466
|
+
returnDisplayMessage = llmContent;
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
llmContent = 'Command was cancelled by user before it could complete.';
|
|
470
|
+
if (rawOutput.trim() !== '') {
|
|
471
|
+
llmContent += ` Below is the output before it was cancelled:\n${rawOutput}`;
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
llmContent += ' There was no output before it was cancelled.';
|
|
475
|
+
}
|
|
476
|
+
if (this.host.getDebugMode()) {
|
|
477
|
+
returnDisplayMessage = llmContent;
|
|
478
|
+
}
|
|
479
|
+
else if (filteredOutput.trim() !== '') {
|
|
480
|
+
returnDisplayMessage = filteredOutput;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
returnDisplayMessage = 'Command cancelled by user.';
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
const finalError = result.error
|
|
489
|
+
? result.error.message.replace(commandToExecute, this.params.command)
|
|
490
|
+
: '(none)';
|
|
491
|
+
llmContent = [
|
|
492
|
+
`Command: ${this.params.command}`,
|
|
493
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing
|
|
494
|
+
`Directory: ${this.getDirPath() || '(root)'}`,
|
|
495
|
+
`Stdout: ${filteredOutput || '(empty)'}`,
|
|
496
|
+
`Stderr: (empty)`,
|
|
497
|
+
`Error: ${finalError}`,
|
|
498
|
+
`Exit Code: ${result.exitCode ?? '(none)'}`,
|
|
499
|
+
`Signal: ${result.signal ?? '(none)'}`,
|
|
500
|
+
`Background PIDs: ${backgroundPIDs.length > 0 ? backgroundPIDs.join(', ') : '(none)'}`,
|
|
501
|
+
`Process Group PGID: ${pgid ?? result.pid ?? '(none)'}`,
|
|
502
|
+
].join('\n');
|
|
503
|
+
if (this.host.getDebugMode()) {
|
|
504
|
+
returnDisplayMessage = llmContent;
|
|
505
|
+
}
|
|
506
|
+
else if (filteredOutput.trim() !== '') {
|
|
507
|
+
returnDisplayMessage = filteredOutput;
|
|
508
|
+
}
|
|
509
|
+
else if (result.signal !== null) {
|
|
510
|
+
returnDisplayMessage = `Command terminated by signal: ${result.signal}`;
|
|
511
|
+
}
|
|
512
|
+
else if (result.error !== null) {
|
|
513
|
+
returnDisplayMessage = `Command failed: ${result.error.message}`;
|
|
514
|
+
}
|
|
515
|
+
else if (result.exitCode !== null && result.exitCode !== 0) {
|
|
516
|
+
returnDisplayMessage = `Command exited with code: ${result.exitCode}`;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return { llmContent, returnDisplayMessage };
|
|
520
|
+
}
|
|
521
|
+
applyFilterDescription(returnDisplayMessage, filterInfo) {
|
|
522
|
+
if (filterInfo.description !== undefined &&
|
|
523
|
+
filterInfo.description !== '' &&
|
|
524
|
+
!this.host.getDebugMode()) {
|
|
525
|
+
return returnDisplayMessage !== ''
|
|
526
|
+
? `[${filterInfo.description}]\n${returnDisplayMessage}`
|
|
527
|
+
: `[${filterInfo.description}]`;
|
|
528
|
+
}
|
|
529
|
+
return returnDisplayMessage;
|
|
530
|
+
}
|
|
531
|
+
buildExecutionError(result, llmContent, timeoutTriggered) {
|
|
532
|
+
const commandError = result.error;
|
|
533
|
+
if (commandError !== undefined && commandError !== null) {
|
|
534
|
+
return {
|
|
535
|
+
error: {
|
|
536
|
+
message: commandError.message,
|
|
537
|
+
type: ToolErrorType.SHELL_EXECUTE_ERROR,
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
else if (result.aborted === true && timeoutTriggered) {
|
|
542
|
+
return {
|
|
543
|
+
error: {
|
|
544
|
+
message: llmContent,
|
|
545
|
+
type: ToolErrorType.TIMEOUT,
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
else if (result.aborted === true) {
|
|
550
|
+
return {
|
|
551
|
+
error: {
|
|
552
|
+
message: llmContent,
|
|
553
|
+
type: ToolErrorType.EXECUTION_FAILED,
|
|
554
|
+
},
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
return {};
|
|
558
|
+
}
|
|
559
|
+
createPreCancelledResult() {
|
|
560
|
+
return {
|
|
561
|
+
llmContent: 'Command was cancelled by user before it could start.',
|
|
562
|
+
returnDisplay: 'Command cancelled by user.',
|
|
563
|
+
error: {
|
|
564
|
+
message: 'Command was cancelled by user before it could start.',
|
|
565
|
+
type: ToolErrorType.EXECUTION_FAILED,
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
resolveTimeoutSeconds(requestedTimeoutSeconds, defaultTimeoutSeconds, maxTimeoutSeconds) {
|
|
570
|
+
if (requestedTimeoutSeconds === -1 || defaultTimeoutSeconds === -1) {
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
const effectiveTimeout = requestedTimeoutSeconds ?? defaultTimeoutSeconds;
|
|
574
|
+
if (maxTimeoutSeconds === undefined || maxTimeoutSeconds === -1) {
|
|
575
|
+
return effectiveTimeout;
|
|
576
|
+
}
|
|
577
|
+
if (effectiveTimeout > maxTimeoutSeconds) {
|
|
578
|
+
return maxTimeoutSeconds;
|
|
579
|
+
}
|
|
580
|
+
return effectiveTimeout;
|
|
581
|
+
}
|
|
582
|
+
isInvocationAllowlisted(command) {
|
|
583
|
+
return this.host.isShellInvocationAllowlisted(command, ShellTool.Name);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function getShellToolDescription() {
|
|
587
|
+
const returnedInfo = `\n\n The following information is returned:\n\n Command: Executed command.\n Directory: Directory (relative to project root) where command was executed, or \`(root)\`.\n Stdout: Output on stdout stream. Can be \`(empty)\` or partial on error and for any unwaited background processes.\n Stderr: Output on stderr stream. Can be \`(empty)\` or partial on error and for any unwaited background processes.\n Error: Error or \`(none)\` if no error was reported for the subprocess.\n Exit Code: Exit code or \`(none)\` if terminated by signal.\n Signal: Signal number or \`(none)\` if no signal was received.\n Background PIDs: List of background processes started or \`(none)\`.\n Process Group PGID: Process group started or \`(none)\``;
|
|
588
|
+
if (os.platform() === 'win32') {
|
|
589
|
+
return `This tool executes a given shell command as \`cmd.exe /c <command>\`. Command can start background processes using \`start /b\`.${returnedInfo}`;
|
|
590
|
+
}
|
|
591
|
+
return `This tool executes a given shell command as \`bash -c <command>\`. Command can start background processes using \`&\`. Command is executed as a subprocess that leads its own process group. Command process group can be terminated as \`kill -- -PGID\` or signaled as \`kill -s SIGNAL -- -PGID\`.${returnedInfo}`;
|
|
592
|
+
}
|
|
593
|
+
function getCommandDescription() {
|
|
594
|
+
const cmd_substitution_warning = '\n*** WARNING: Command substitution using $(), `` ` ``, <(), or >() is not allowed for security reasons.';
|
|
595
|
+
if (os.platform() === 'win32') {
|
|
596
|
+
return ('Exact command to execute as `cmd.exe /c <command>`' +
|
|
597
|
+
cmd_substitution_warning);
|
|
598
|
+
}
|
|
599
|
+
return ('Exact bash command to execute as `bash -c <command>`' +
|
|
600
|
+
cmd_substitution_warning);
|
|
601
|
+
}
|
|
602
|
+
export class ShellTool extends BaseDeclarativeTool {
|
|
603
|
+
static Name = 'run_shell_command';
|
|
604
|
+
allowlist = new Set();
|
|
605
|
+
host;
|
|
606
|
+
constructor(host, messageBus) {
|
|
607
|
+
super(ShellTool.Name, 'Shell', getShellToolDescription(), Kind.Execute, {
|
|
608
|
+
type: 'object',
|
|
609
|
+
properties: {
|
|
610
|
+
command: {
|
|
611
|
+
type: 'string',
|
|
612
|
+
description: getCommandDescription(),
|
|
613
|
+
},
|
|
614
|
+
description: {
|
|
615
|
+
type: 'string',
|
|
616
|
+
description: 'Brief description of the command for the user. Be specific and concise. Ideally a single sentence. Can be up to 3 sentences for clarity. No line breaks.',
|
|
617
|
+
},
|
|
618
|
+
dir_path: {
|
|
619
|
+
type: 'string',
|
|
620
|
+
description: '(OPTIONAL) Directory to run the command in. Provide a workspace directory name (e.g., "packages"), a relative path (e.g., "src/utils"), or an absolute path within the workspace.',
|
|
621
|
+
},
|
|
622
|
+
directory: {
|
|
623
|
+
type: 'string',
|
|
624
|
+
description: 'Alternative parameter name for dir_path (for backward compatibility).',
|
|
625
|
+
},
|
|
626
|
+
timeout_seconds: {
|
|
627
|
+
type: 'number',
|
|
628
|
+
description: '(OPTIONAL) Timeout in seconds for command execution (-1 for unlimited).',
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
required: ['command'],
|
|
632
|
+
}, false, true, messageBus);
|
|
633
|
+
this.host = isShellToolHost(host)
|
|
634
|
+
? host
|
|
635
|
+
: createShellToolHostFromExecutionService(host);
|
|
636
|
+
}
|
|
637
|
+
validateToolParamValues(params) {
|
|
638
|
+
if (!params.command.trim()) {
|
|
639
|
+
return 'Command cannot be empty.';
|
|
640
|
+
}
|
|
641
|
+
const commandCheck = this.host.isCommandAllowed(params.command);
|
|
642
|
+
if (!commandCheck.allowed) {
|
|
643
|
+
if (!commandCheck.reason) {
|
|
644
|
+
return `Command is not allowed: ${params.command}`;
|
|
645
|
+
}
|
|
646
|
+
return commandCheck.reason;
|
|
647
|
+
}
|
|
648
|
+
if (this.host.getCommandRoots(params.command).length === 0) {
|
|
649
|
+
return 'Could not identify command root to obtain permission from user.';
|
|
650
|
+
}
|
|
651
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional falsy coalescing
|
|
652
|
+
const dirPath = params.dir_path || params.directory;
|
|
653
|
+
if (dirPath) {
|
|
654
|
+
const workspaceContext = this.host.getWorkspaceContext();
|
|
655
|
+
if (path.isAbsolute(dirPath)) {
|
|
656
|
+
const pathError = this.host.validatePathWithinWorkspace(workspaceContext, dirPath, 'Directory');
|
|
657
|
+
if (pathError) {
|
|
658
|
+
return pathError;
|
|
659
|
+
}
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
// Multi-segment relative paths resolve against targetDir
|
|
663
|
+
if (dirPath.includes(path.sep) || dirPath.includes('/')) {
|
|
664
|
+
const resolvedPath = path.resolve(this.host.getTargetDir(), dirPath);
|
|
665
|
+
const pathError = this.host.validatePathWithinWorkspace(workspaceContext, resolvedPath, 'Directory');
|
|
666
|
+
if (pathError) {
|
|
667
|
+
return pathError;
|
|
668
|
+
}
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
// Single-segment: try workspace basename matching first
|
|
672
|
+
const workspaceDirs = workspaceContext.getDirectories();
|
|
673
|
+
const matchingDirs = workspaceDirs.filter((dir) => path.basename(dir) === dirPath);
|
|
674
|
+
if (matchingDirs.length === 1) {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
if (matchingDirs.length > 1) {
|
|
678
|
+
return `Directory name '${dirPath}' is ambiguous as it matches multiple workspace directories.`;
|
|
679
|
+
}
|
|
680
|
+
// No basename match — try resolving as relative path within workspace
|
|
681
|
+
const resolvedPath = path.resolve(this.host.getTargetDir(), dirPath);
|
|
682
|
+
if (workspaceContext.isPathWithinWorkspace(resolvedPath)) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
return `Directory '${dirPath}' is not a registered workspace directory. Provide a workspace directory name, a relative path, or an absolute path within the workspace.`;
|
|
686
|
+
}
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
async execute(params, signal = new AbortController().signal) {
|
|
690
|
+
let invocation;
|
|
691
|
+
try {
|
|
692
|
+
invocation = this.build(params);
|
|
693
|
+
}
|
|
694
|
+
catch (error) {
|
|
695
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
696
|
+
return {
|
|
697
|
+
llmContent: `Shell command blocked: ${params.command}\n${message}`,
|
|
698
|
+
returnDisplay: `Command blocked: ${message}`,
|
|
699
|
+
error: {
|
|
700
|
+
message,
|
|
701
|
+
type: ToolErrorType.EXECUTION_FAILED,
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const confirmation = await invocation.shouldConfirmExecute(signal);
|
|
706
|
+
if (confirmation !== false) {
|
|
707
|
+
const outcome = this.messageBus
|
|
708
|
+
? (await this.messageBus.requestConfirmation(confirmation, signal))
|
|
709
|
+
: ToolConfirmationOutcome.ProceedOnce;
|
|
710
|
+
if (outcome === ToolConfirmationOutcome.Cancel) {
|
|
711
|
+
return {
|
|
712
|
+
llmContent: `Shell command cancelled: ${params.command}`,
|
|
713
|
+
returnDisplay: 'Command cancelled by user.',
|
|
714
|
+
error: {
|
|
715
|
+
message: 'Shell command cancelled by user.',
|
|
716
|
+
type: ToolErrorType.EXECUTION_FAILED,
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
if ('onConfirm' in confirmation) {
|
|
721
|
+
await confirmation.onConfirm(outcome);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return invocation.execute(signal);
|
|
725
|
+
}
|
|
726
|
+
createInvocation(params, messageBus) {
|
|
727
|
+
const normalizedParams = { ...params };
|
|
728
|
+
if (!normalizedParams.dir_path && normalizedParams.directory) {
|
|
729
|
+
normalizedParams.dir_path = normalizedParams.directory;
|
|
730
|
+
}
|
|
731
|
+
return new ShellToolInvocation(this.host, normalizedParams, this.allowlist, messageBus);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/* eslint-enable complexity, max-lines -- Shell tool delegated through IShellToolHost. */
|
|
735
|
+
//# sourceMappingURL=shell.js.map
|