@theia/ai-ide 1.77.0-next.95 → 1.77.0
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/lib/browser/address-pr-review-command-contribution.js +15 -19
- package/lib/browser/address-pr-review-command-contribution.js.map +1 -1
- package/lib/browser/agent-mode-confirmation-service.js +20 -24
- package/lib/browser/agent-mode-confirmation-service.js.map +1 -1
- package/lib/browser/agents-md-agent.js +15 -19
- package/lib/browser/agents-md-agent.js.map +1 -1
- package/lib/browser/ai-allow-all-mode-chat-banner.js +32 -39
- package/lib/browser/ai-allow-all-mode-chat-banner.js.map +1 -1
- package/lib/browser/ai-allow-all-mode-chat-banner.spec.js +49 -51
- package/lib/browser/ai-allow-all-mode-chat-banner.spec.js.map +1 -1
- package/lib/browser/ai-aware-scm-commit-widget.d.ts +1 -1
- package/lib/browser/ai-aware-scm-commit-widget.d.ts.map +1 -1
- package/lib/browser/ai-aware-scm-commit-widget.js +36 -47
- package/lib/browser/ai-aware-scm-commit-widget.js.map +1 -1
- package/lib/browser/ai-configuration/agent-icon.js +5 -9
- package/lib/browser/ai-configuration/agent-icon.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-breadcrumbs-contribution.js +28 -37
- package/lib/browser/ai-configuration/ai-configuration-breadcrumbs-contribution.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-detail-widget.js +29 -39
- package/lib/browser/ai-configuration/ai-configuration-detail-widget.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-detail-widget.spec.js +22 -24
- package/lib/browser/ai-configuration/ai-configuration-detail-widget.spec.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-label-provider-contribution.js +12 -15
- package/lib/browser/ai-configuration/ai-configuration-label-provider-contribution.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-open-preference-renderer.js +20 -24
- package/lib/browser/ai-configuration/ai-configuration-open-preference-renderer.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-preference-ownership.spec.js +37 -39
- package/lib/browser/ai-configuration/ai-configuration-preference-ownership.spec.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-resource.js +3 -7
- package/lib/browser/ai-configuration/ai-configuration-resource.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-search-widget.js +20 -26
- package/lib/browser/ai-configuration/ai-configuration-search-widget.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-search.js +2 -5
- package/lib/browser/ai-configuration/ai-configuration-search.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-search.spec.js +15 -17
- package/lib/browser/ai-configuration/ai-configuration-search.spec.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-service.js +9 -12
- package/lib/browser/ai-configuration/ai-configuration-service.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-tree-model.js +7 -10
- package/lib/browser/ai-configuration/ai-configuration-tree-model.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-tree-nodes.js +14 -17
- package/lib/browser/ai-configuration/ai-configuration-tree-nodes.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-tree-nodes.spec.js +47 -49
- package/lib/browser/ai-configuration/ai-configuration-tree-nodes.spec.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-tree-widget.d.ts +1 -1
- package/lib/browser/ai-configuration/ai-configuration-tree-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-tree-widget.js +68 -79
- package/lib/browser/ai-configuration/ai-configuration-tree-widget.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts +2 -2
- package/lib/browser/ai-configuration/ai-configuration-view-contribution.d.ts.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-view-contribution.js +47 -54
- package/lib/browser/ai-configuration/ai-configuration-view-contribution.js.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.d.ts +4 -4
- package/lib/browser/ai-configuration/ai-configuration-widget.d.ts.map +1 -1
- package/lib/browser/ai-configuration/ai-configuration-widget.js +45 -57
- package/lib/browser/ai-configuration/ai-configuration-widget.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-capabilities-settings.d.ts +3 -0
- package/lib/browser/ai-configuration/categories/agent-capabilities-settings.d.ts.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-capabilities-settings.js +34 -40
- package/lib/browser/ai-configuration/categories/agent-capabilities-settings.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-capabilities-settings.spec.js +135 -140
- package/lib/browser/ai-configuration/categories/agent-capabilities-settings.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-chips.js +8 -13
- package/lib/browser/ai-configuration/categories/agent-chips.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-detail-model.js +7 -10
- package/lib/browser/ai-configuration/categories/agent-detail-model.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-detail-model.spec.js +13 -15
- package/lib/browser/ai-configuration/categories/agent-detail-model.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-detail-view.d.ts +1 -1
- package/lib/browser/ai-configuration/categories/agent-detail-view.d.ts.map +1 -1
- package/lib/browser/ai-configuration/categories/agent-detail-view.js +29 -34
- package/lib/browser/ai-configuration/categories/agent-detail-view.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agents-configuration-category.d.ts +1 -1
- package/lib/browser/ai-configuration/categories/agents-configuration-category.d.ts.map +1 -1
- package/lib/browser/ai-configuration/categories/agents-configuration-category.js +106 -128
- package/lib/browser/ai-configuration/categories/agents-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/agents-configuration-category.spec.js +32 -34
- package/lib/browser/ai-configuration/categories/agents-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/general-configuration-category.js +91 -101
- package/lib/browser/ai-configuration/categories/general-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/general-configuration-category.spec.js +38 -40
- package/lib/browser/ai-configuration/categories/general-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/model-aliases-configuration-category.js +53 -63
- package/lib/browser/ai-configuration/categories/model-aliases-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/model-aliases-configuration-category.spec.js +25 -27
- package/lib/browser/ai-configuration/categories/model-aliases-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/models-configuration-category.js +45 -56
- package/lib/browser/ai-configuration/categories/models-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/models-configuration-category.spec.js +31 -33
- package/lib/browser/ai-configuration/categories/models-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/prompt-snippets-configuration-category.js +68 -76
- package/lib/browser/ai-configuration/categories/prompt-snippets-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/prompt-snippets-configuration-category.spec.js +55 -57
- package/lib/browser/ai-configuration/categories/prompt-snippets-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/prompts-and-skills-configuration-category.js +85 -100
- package/lib/browser/ai-configuration/categories/prompts-and-skills-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/prompts-and-skills-configuration-category.spec.js +39 -41
- package/lib/browser/ai-configuration/categories/prompts-and-skills-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/provider-model-discovery-section.js +44 -49
- package/lib/browser/ai-configuration/categories/provider-model-discovery-section.js.map +1 -1
- package/lib/browser/ai-configuration/categories/provider-model-discovery-section.spec.js +71 -74
- package/lib/browser/ai-configuration/categories/provider-model-discovery-section.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/token-usage-configuration-category.js +34 -42
- package/lib/browser/ai-configuration/categories/token-usage-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/token-usage-configuration-category.spec.js +16 -18
- package/lib/browser/ai-configuration/categories/token-usage-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/tools-configuration-category.js +101 -112
- package/lib/browser/ai-configuration/categories/tools-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/tools-configuration-category.spec.js +46 -48
- package/lib/browser/ai-configuration/categories/tools-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/categories/variables-configuration-category.js +54 -63
- package/lib/browser/ai-configuration/categories/variables-configuration-category.js.map +1 -1
- package/lib/browser/ai-configuration/categories/variables-configuration-category.spec.js +27 -29
- package/lib/browser/ai-configuration/categories/variables-configuration-category.spec.js.map +1 -1
- package/lib/browser/ai-configuration/components/agent-notification-settings.js +12 -16
- package/lib/browser/ai-configuration/components/agent-notification-settings.js.map +1 -1
- package/lib/browser/ai-configuration/language-model-renderer.js +25 -31
- package/lib/browser/ai-configuration/language-model-renderer.js.map +1 -1
- package/lib/browser/ai-configuration/language-model-renderer.spec.js +29 -32
- package/lib/browser/ai-configuration/language-model-renderer.spec.js.map +1 -1
- package/lib/browser/ai-first-perspective-contribution.js +18 -21
- package/lib/browser/ai-first-perspective-contribution.js.map +1 -1
- package/lib/browser/ai-getting-started-commands.js +3 -6
- package/lib/browser/ai-getting-started-commands.js.map +1 -1
- package/lib/browser/ai-getting-started-contribution.js +24 -30
- package/lib/browser/ai-getting-started-contribution.js.map +1 -1
- package/lib/browser/ai-getting-started-walkthrough.js +54 -58
- package/lib/browser/ai-getting-started-walkthrough.js.map +1 -1
- package/lib/browser/ai-getting-started-walkthrough.spec.js +32 -34
- package/lib/browser/ai-getting-started-walkthrough.spec.js.map +1 -1
- package/lib/browser/ai-ide-activation-service.js +25 -34
- package/lib/browser/ai-ide-activation-service.js.map +1 -1
- package/lib/browser/ai-sessions-view-contribution.d.ts +1 -1
- package/lib/browser/ai-sessions-view-contribution.d.ts.map +1 -1
- package/lib/browser/ai-sessions-view-contribution.js +28 -33
- package/lib/browser/ai-sessions-view-contribution.js.map +1 -1
- package/lib/browser/ai-sessions-widget.d.ts +3 -3
- package/lib/browser/ai-sessions-widget.d.ts.map +1 -1
- package/lib/browser/ai-sessions-widget.js +42 -55
- package/lib/browser/ai-sessions-widget.js.map +1 -1
- package/lib/browser/ai-terminal-functions.js +18 -24
- package/lib/browser/ai-terminal-functions.js.map +1 -1
- package/lib/browser/ai-walkthrough-context-keys.js +35 -45
- package/lib/browser/ai-walkthrough-context-keys.js.map +1 -1
- package/lib/browser/ai-walkthrough-context-keys.spec.js +34 -36
- package/lib/browser/ai-walkthrough-context-keys.spec.js.map +1 -1
- package/lib/browser/ai-workspace-restriction-contribution.js +11 -15
- package/lib/browser/ai-workspace-restriction-contribution.js.map +1 -1
- package/lib/browser/analyze-gh-ticket-command-contribution.js +19 -23
- package/lib/browser/analyze-gh-ticket-command-contribution.js.map +1 -1
- package/lib/browser/app-tester-chat-agent.js +45 -51
- package/lib/browser/app-tester-chat-agent.js.map +1 -1
- package/lib/browser/app-tester-chat-functions.d.ts +1 -1
- package/lib/browser/app-tester-chat-functions.d.ts.map +1 -1
- package/lib/browser/app-tester-chat-functions.js +29 -34
- package/lib/browser/app-tester-chat-functions.js.map +1 -1
- package/lib/browser/app-tester-prompt-template.js +42 -45
- package/lib/browser/app-tester-prompt-template.js.map +1 -1
- package/lib/browser/apptester-capability-contribution.js +13 -17
- package/lib/browser/apptester-capability-contribution.js.map +1 -1
- package/lib/browser/architect-agent.d.ts +2 -2
- package/lib/browser/architect-agent.d.ts.map +1 -1
- package/lib/browser/architect-agent.js +38 -44
- package/lib/browser/architect-agent.js.map +1 -1
- package/lib/browser/architect-prompt-template.js +84 -87
- package/lib/browser/architect-prompt-template.js.map +1 -1
- package/lib/browser/chat-session-item-action-contribution.js +15 -18
- package/lib/browser/chat-session-item-action-contribution.js.map +1 -1
- package/lib/browser/chat-session-item.d.ts +1 -1
- package/lib/browser/chat-session-item.d.ts.map +1 -1
- package/lib/browser/chat-session-item.js +25 -29
- package/lib/browser/chat-session-item.js.map +1 -1
- package/lib/browser/chat-session-list-components.js +9 -14
- package/lib/browser/chat-session-list-components.js.map +1 -1
- package/lib/browser/chat-session-list-service.d.ts +2 -2
- package/lib/browser/chat-session-list-service.d.ts.map +1 -1
- package/lib/browser/chat-session-list-service.js +33 -43
- package/lib/browser/chat-session-list-service.js.map +1 -1
- package/lib/browser/chat-session-list-service.spec.js +56 -58
- package/lib/browser/chat-session-list-service.spec.js.map +1 -1
- package/lib/browser/chat-session-tooltip.js +31 -35
- package/lib/browser/chat-session-tooltip.js.map +1 -1
- package/lib/browser/chat-sessions-welcome-message-provider.d.ts +3 -3
- package/lib/browser/chat-sessions-welcome-message-provider.d.ts.map +1 -1
- package/lib/browser/chat-sessions-welcome-message-provider.js +54 -70
- package/lib/browser/chat-sessions-welcome-message-provider.js.map +1 -1
- package/lib/browser/code-review-capability-contribution.js +13 -17
- package/lib/browser/code-review-capability-contribution.js.map +1 -1
- package/lib/browser/code-reviewer-agent.js +17 -21
- package/lib/browser/code-reviewer-agent.js.map +1 -1
- package/lib/browser/code-reviewer-prompt-template.js +5 -8
- package/lib/browser/code-reviewer-prompt-template.js.map +1 -1
- package/lib/browser/coder-agent.d.ts +2 -2
- package/lib/browser/coder-agent.d.ts.map +1 -1
- package/lib/browser/coder-agent.js +44 -50
- package/lib/browser/coder-agent.js.map +1 -1
- package/lib/browser/commit-message-agent.js +26 -32
- package/lib/browser/commit-message-agent.js.map +1 -1
- package/lib/browser/commit-message-agent.spec.js +25 -28
- package/lib/browser/commit-message-agent.spec.js.map +1 -1
- package/lib/browser/commit-message-command-contribution.d.ts +1 -1
- package/lib/browser/commit-message-command-contribution.d.ts.map +1 -1
- package/lib/browser/commit-message-command-contribution.js +21 -28
- package/lib/browser/commit-message-command-contribution.js.map +1 -1
- package/lib/browser/commit-message-commands.js +4 -7
- package/lib/browser/commit-message-commands.js.map +1 -1
- package/lib/browser/commit-message-prompt-template.js +9 -12
- package/lib/browser/commit-message-prompt-template.js.map +1 -1
- package/lib/browser/commit-message-runner.d.ts +2 -2
- package/lib/browser/commit-message-runner.d.ts.map +1 -1
- package/lib/browser/commit-message-runner.js +33 -41
- package/lib/browser/commit-message-runner.js.map +1 -1
- package/lib/browser/commit-message-runner.spec.js +65 -68
- package/lib/browser/commit-message-runner.spec.js.map +1 -1
- package/lib/browser/context-file-validation-service-impl.d.ts +1 -1
- package/lib/browser/context-file-validation-service-impl.d.ts.map +1 -1
- package/lib/browser/context-file-validation-service-impl.js +20 -26
- package/lib/browser/context-file-validation-service-impl.js.map +1 -1
- package/lib/browser/context-file-validation-service-impl.spec.js +77 -79
- package/lib/browser/context-file-validation-service-impl.spec.js.map +1 -1
- package/lib/browser/context-functions.js +27 -31
- package/lib/browser/context-functions.js.map +1 -1
- package/lib/browser/context-functions.spec.js +64 -66
- package/lib/browser/context-functions.spec.js.map +1 -1
- package/lib/browser/create-skill-agent.d.ts +1 -1
- package/lib/browser/create-skill-agent.d.ts.map +1 -1
- package/lib/browser/create-skill-agent.js +19 -23
- package/lib/browser/create-skill-agent.js.map +1 -1
- package/lib/browser/default-chat-agent-recommendation-service.js +16 -19
- package/lib/browser/default-chat-agent-recommendation-service.js.map +1 -1
- package/lib/browser/explore-agent.d.ts +1 -1
- package/lib/browser/explore-agent.d.ts.map +1 -1
- package/lib/browser/explore-agent.js +18 -22
- package/lib/browser/explore-agent.js.map +1 -1
- package/lib/browser/explore-prompt-template.js +5 -8
- package/lib/browser/explore-prompt-template.js.map +1 -1
- package/lib/browser/file-changeset-function.spec.js +12 -14
- package/lib/browser/file-changeset-function.spec.js.map +1 -1
- package/lib/browser/file-changeset-functions.d.ts +1 -1
- package/lib/browser/file-changeset-functions.d.ts.map +1 -1
- package/lib/browser/file-changeset-functions.js +134 -164
- package/lib/browser/file-changeset-functions.js.map +1 -1
- package/lib/browser/file-changeset-functions.spec.js +136 -138
- package/lib/browser/file-changeset-functions.spec.js.map +1 -1
- package/lib/browser/frontend-module.js +293 -295
- package/lib/browser/frontend-module.js.map +1 -1
- package/lib/browser/git-changes-tool.js +22 -26
- package/lib/browser/git-changes-tool.js.map +1 -1
- package/lib/browser/git-changes-tool.spec.js +52 -55
- package/lib/browser/git-changes-tool.spec.js.map +1 -1
- package/lib/browser/github-capability-contribution.js +14 -18
- package/lib/browser/github-capability-contribution.js.map +1 -1
- package/lib/browser/github-chat-agent.js +58 -67
- package/lib/browser/github-chat-agent.js.map +1 -1
- package/lib/browser/github-prompt-template.js +6 -9
- package/lib/browser/github-prompt-template.js.map +1 -1
- package/lib/browser/github-repo-variable-contribution.d.ts +1 -1
- package/lib/browser/github-repo-variable-contribution.d.ts.map +1 -1
- package/lib/browser/github-repo-variable-contribution.js +23 -30
- package/lib/browser/github-repo-variable-contribution.js.map +1 -1
- package/lib/browser/ide-chat-welcome-message-provider.js +59 -71
- package/lib/browser/ide-chat-welcome-message-provider.js.map +1 -1
- package/lib/browser/implement-gh-ticket-command-contribution.js +15 -19
- package/lib/browser/implement-gh-ticket-command-contribution.js.map +1 -1
- package/lib/browser/memory-capability-contribution.d.ts.map +1 -1
- package/lib/browser/memory-capability-contribution.js +21 -25
- package/lib/browser/memory-capability-contribution.js.map +1 -1
- package/lib/browser/memory-capability-contribution.spec.js +30 -32
- package/lib/browser/memory-capability-contribution.spec.js.map +1 -1
- package/lib/browser/memory-directory-variable-contribution.d.ts +1 -1
- package/lib/browser/memory-directory-variable-contribution.d.ts.map +1 -1
- package/lib/browser/memory-directory-variable-contribution.js +19 -24
- package/lib/browser/memory-directory-variable-contribution.js.map +1 -1
- package/lib/browser/memory-directory-variable-contribution.spec.js +28 -30
- package/lib/browser/memory-directory-variable-contribution.spec.js.map +1 -1
- package/lib/browser/mode-aware-chat-agent.js +9 -12
- package/lib/browser/mode-aware-chat-agent.js.map +1 -1
- package/lib/browser/multi-root-paths.spec.js +86 -88
- package/lib/browser/multi-root-paths.spec.js.map +1 -1
- package/lib/browser/open-editors-hint-contribution.d.ts.map +1 -1
- package/lib/browser/open-editors-hint-contribution.js +11 -14
- package/lib/browser/open-editors-hint-contribution.js.map +1 -1
- package/lib/browser/remember-command-contribution.js +17 -21
- package/lib/browser/remember-command-contribution.js.map +1 -1
- package/lib/browser/review/pr-review-agent.js +17 -21
- package/lib/browser/review/pr-review-agent.js.map +1 -1
- package/lib/browser/review/pr-review-capability-contribution.js +41 -45
- package/lib/browser/review/pr-review-capability-contribution.js.map +1 -1
- package/lib/browser/review/pr-review-prompt-template.js +48 -51
- package/lib/browser/review/pr-review-prompt-template.js.map +1 -1
- package/lib/browser/shell-execution-capability-contribution.js +17 -21
- package/lib/browser/shell-execution-capability-contribution.js.map +1 -1
- package/lib/browser/skill-file-functions.js +27 -33
- package/lib/browser/skill-file-functions.js.map +1 -1
- package/lib/browser/skill-file-functions.spec.js +19 -21
- package/lib/browser/skill-file-functions.spec.js.map +1 -1
- package/lib/browser/summarize-session-command-contribution.d.ts +1 -1
- package/lib/browser/summarize-session-command-contribution.d.ts.map +1 -1
- package/lib/browser/summarize-session-command-contribution.js +38 -50
- package/lib/browser/summarize-session-command-contribution.js.map +1 -1
- package/lib/browser/task-background-summary-variable.js +17 -20
- package/lib/browser/task-background-summary-variable.js.map +1 -1
- package/lib/browser/task-context-file-storage-service.js +39 -51
- package/lib/browser/task-context-file-storage-service.js.map +1 -1
- package/lib/browser/task-context-file-storage-service.spec.js +41 -43
- package/lib/browser/task-context-file-storage-service.spec.js.map +1 -1
- package/lib/browser/task-context-functions.js +44 -52
- package/lib/browser/task-context-functions.js.map +1 -1
- package/lib/browser/template-preference-contribution.js +30 -37
- package/lib/browser/template-preference-contribution.js.map +1 -1
- package/lib/browser/test/tool-provider-cancellation-test-util.spec.js +8 -10
- package/lib/browser/test/tool-provider-cancellation-test-util.spec.js.map +1 -1
- package/lib/browser/todo-tool-renderer.d.ts +1 -1
- package/lib/browser/todo-tool-renderer.d.ts.map +1 -1
- package/lib/browser/todo-tool-renderer.js +42 -51
- package/lib/browser/todo-tool-renderer.js.map +1 -1
- package/lib/browser/todo-tool.js +8 -11
- package/lib/browser/todo-tool.js.map +1 -1
- package/lib/browser/todo-tool.spec.js +14 -16
- package/lib/browser/todo-tool.spec.js.map +1 -1
- package/lib/browser/user-interaction-tool-renderer.d.ts +1 -1
- package/lib/browser/user-interaction-tool-renderer.d.ts.map +1 -1
- package/lib/browser/user-interaction-tool-renderer.js +81 -95
- package/lib/browser/user-interaction-tool-renderer.js.map +1 -1
- package/lib/browser/user-interaction-tool.d.ts +2 -2
- package/lib/browser/user-interaction-tool.d.ts.map +1 -1
- package/lib/browser/user-interaction-tool.js +56 -65
- package/lib/browser/user-interaction-tool.js.map +1 -1
- package/lib/browser/user-interaction-tool.spec.js +118 -121
- package/lib/browser/user-interaction-tool.spec.js.map +1 -1
- package/lib/browser/workspace-functions.d.ts +1 -1
- package/lib/browser/workspace-functions.d.ts.map +1 -1
- package/lib/browser/workspace-functions.js +126 -155
- package/lib/browser/workspace-functions.js.map +1 -1
- package/lib/browser/workspace-functions.spec.js +607 -600
- package/lib/browser/workspace-functions.spec.js.map +1 -1
- package/lib/browser/workspace-launch-provider.d.ts +1 -1
- package/lib/browser/workspace-launch-provider.d.ts.map +1 -1
- package/lib/browser/workspace-launch-provider.js +46 -57
- package/lib/browser/workspace-launch-provider.js.map +1 -1
- package/lib/browser/workspace-launch-provider.spec.js +119 -122
- package/lib/browser/workspace-launch-provider.spec.js.map +1 -1
- package/lib/browser/workspace-search-provider.d.ts +1 -1
- package/lib/browser/workspace-search-provider.d.ts.map +1 -1
- package/lib/browser/workspace-search-provider.js +25 -32
- package/lib/browser/workspace-search-provider.js.map +1 -1
- package/lib/browser/workspace-search-provider.spec.js +35 -38
- package/lib/browser/workspace-search-provider.spec.js.map +1 -1
- package/lib/browser/workspace-task-provider.d.ts +1 -1
- package/lib/browser/workspace-task-provider.d.ts.map +1 -1
- package/lib/browser/workspace-task-provider.js +42 -51
- package/lib/browser/workspace-task-provider.js.map +1 -1
- package/lib/browser/workspace-task-provider.spec.js +102 -105
- package/lib/browser/workspace-task-provider.spec.js.map +1 -1
- package/lib/common/agent-ids.js +2 -5
- package/lib/common/agent-ids.js.map +1 -1
- package/lib/common/agents-md-prompt-template.js +38 -41
- package/lib/common/agents-md-prompt-template.js.map +1 -1
- package/lib/common/ai-configuration-preferences.js +5 -8
- package/lib/common/ai-configuration-preferences.js.map +1 -1
- package/lib/common/ai-ide-preferences.js +16 -19
- package/lib/common/ai-ide-preferences.js.map +1 -1
- package/lib/common/ai-terminal-functions.js +1 -4
- package/lib/common/ai-terminal-functions.js.map +1 -1
- package/lib/common/app-tester-chat-functions.js +4 -7
- package/lib/common/app-tester-chat-functions.js.map +1 -1
- package/lib/common/browser-automation-protocol.js +3 -6
- package/lib/common/browser-automation-protocol.js.map +1 -1
- package/lib/common/coder-replace-prompt-template.js +102 -108
- package/lib/common/coder-replace-prompt-template.js.map +1 -1
- package/lib/common/command-chat-agents.js +34 -40
- package/lib/common/command-chat-agents.js.map +1 -1
- package/lib/common/command-prompt-template.js +1 -4
- package/lib/common/command-prompt-template.js.map +1 -1
- package/lib/common/context-files-variable.js +17 -20
- package/lib/common/context-files-variable.js.map +1 -1
- package/lib/common/context-functions.js +3 -6
- package/lib/common/context-functions.js.map +1 -1
- package/lib/common/context-variables.js +2 -5
- package/lib/common/context-variables.js.map +1 -1
- package/lib/common/create-skill-prompt-template.js +22 -25
- package/lib/common/create-skill-prompt-template.js.map +1 -1
- package/lib/common/file-changeset-function-ids.js +8 -11
- package/lib/common/file-changeset-function-ids.js.map +1 -1
- package/lib/common/github-repo-protocol.js +2 -5
- package/lib/common/github-repo-protocol.js.map +1 -1
- package/lib/common/open-editors-hint-fragment-id.js +1 -4
- package/lib/common/open-editors-hint-fragment-id.js.map +1 -1
- package/lib/common/orchestrator-chat-agent.js +36 -42
- package/lib/common/orchestrator-chat-agent.js.map +1 -1
- package/lib/common/orchestrator-prompt-template.js +1 -4
- package/lib/common/orchestrator-prompt-template.js.map +1 -1
- package/lib/common/summarize-session-commands.js +2 -5
- package/lib/common/summarize-session-commands.js.map +1 -1
- package/lib/common/task-context-function-ids.js +5 -8
- package/lib/common/task-context-function-ids.js.map +1 -1
- package/lib/common/todo-tool.js +2 -6
- package/lib/common/todo-tool.js.map +1 -1
- package/lib/common/universal-chat-agent.js +17 -21
- package/lib/common/universal-chat-agent.js.map +1 -1
- package/lib/common/universal-prompt-template.js +6 -9
- package/lib/common/universal-prompt-template.js.map +1 -1
- package/lib/common/user-interaction-tool.js +11 -21
- package/lib/common/user-interaction-tool.js.map +1 -1
- package/lib/common/user-interaction-tool.spec.js +52 -54
- package/lib/common/user-interaction-tool.spec.js.map +1 -1
- package/lib/common/workspace-functions.js +13 -16
- package/lib/common/workspace-functions.js.map +1 -1
- package/lib/common/workspace-preferences.js +41 -44
- package/lib/common/workspace-preferences.js.map +1 -1
- package/lib/common/workspace-search-provider-util.js +3 -6
- package/lib/common/workspace-search-provider-util.js.map +1 -1
- package/lib/common/workspace-search-provider-util.spec.js +13 -15
- package/lib/common/workspace-search-provider-util.spec.js.map +1 -1
- package/lib/node/app-tester-agent/browser-automation-impl.d.ts +1 -1
- package/lib/node/app-tester-agent/browser-automation-impl.d.ts.map +1 -1
- package/lib/node/app-tester-agent/browser-automation-impl.js +7 -10
- package/lib/node/app-tester-agent/browser-automation-impl.js.map +1 -1
- package/lib/node/backend-module.js +21 -23
- package/lib/node/backend-module.js.map +1 -1
- package/lib/node/github-repo-service-impl.d.ts +1 -1
- package/lib/node/github-repo-service-impl.d.ts.map +1 -1
- package/lib/node/github-repo-service-impl.js +11 -15
- package/lib/node/github-repo-service-impl.js.map +1 -1
- package/lib/package.spec.d.ts +1 -0
- package/lib/package.spec.js +1 -1
- package/lib/package.spec.js.map +1 -1
- package/package.json +33 -25
- package/src/browser/address-pr-review-command-contribution.ts +1 -1
- package/src/browser/agent-mode-confirmation-service.ts +1 -1
- package/src/browser/agents-md-agent.ts +1 -1
- package/src/browser/ai-allow-all-mode-chat-banner.spec.ts +1 -1
- package/src/browser/ai-aware-scm-commit-widget.tsx +3 -3
- package/src/browser/ai-configuration/ai-configuration-breadcrumbs-contribution.ts +2 -2
- package/src/browser/ai-configuration/ai-configuration-detail-widget.spec.ts +1 -1
- package/src/browser/ai-configuration/ai-configuration-label-provider-contribution.ts +1 -1
- package/src/browser/ai-configuration/ai-configuration-open-preference-renderer.ts +1 -1
- package/src/browser/ai-configuration/ai-configuration-preference-ownership.spec.ts +6 -6
- package/src/browser/ai-configuration/ai-configuration-search.spec.ts +1 -1
- package/src/browser/ai-configuration/ai-configuration-tree-nodes.spec.ts +1 -1
- package/src/browser/ai-configuration/ai-configuration-tree-widget.tsx +3 -3
- package/src/browser/ai-configuration/ai-configuration-view-contribution.ts +2 -2
- package/src/browser/ai-configuration/ai-configuration-widget.tsx +5 -5
- package/src/browser/ai-configuration/categories/agent-capabilities-settings.spec.ts +9 -9
- package/src/browser/ai-configuration/categories/agent-capabilities-settings.tsx +5 -2
- package/src/browser/ai-configuration/categories/agent-chips.tsx +1 -1
- package/src/browser/ai-configuration/categories/agent-detail-model.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/agent-detail-view.tsx +6 -6
- package/src/browser/ai-configuration/categories/agents-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/agents-configuration-category.tsx +5 -5
- package/src/browser/ai-configuration/categories/general-configuration-category.spec.ts +2 -2
- package/src/browser/ai-configuration/categories/general-configuration-category.tsx +1 -1
- package/src/browser/ai-configuration/categories/model-aliases-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/model-aliases-configuration-category.tsx +1 -1
- package/src/browser/ai-configuration/categories/models-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/models-configuration-category.tsx +1 -1
- package/src/browser/ai-configuration/categories/prompt-snippets-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/prompt-snippets-configuration-category.tsx +1 -1
- package/src/browser/ai-configuration/categories/prompts-and-skills-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/prompts-and-skills-configuration-category.tsx +1 -1
- package/src/browser/ai-configuration/categories/provider-model-discovery-section.spec.tsx +1 -1
- package/src/browser/ai-configuration/categories/provider-model-discovery-section.tsx +1 -1
- package/src/browser/ai-configuration/categories/token-usage-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/token-usage-configuration-category.tsx +1 -1
- package/src/browser/ai-configuration/categories/tools-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/variables-configuration-category.spec.ts +1 -1
- package/src/browser/ai-configuration/categories/variables-configuration-category.tsx +1 -1
- package/src/browser/ai-configuration/language-model-renderer.spec.ts +1 -1
- package/src/browser/ai-first-perspective-contribution.ts +1 -1
- package/src/browser/ai-getting-started-contribution.ts +2 -2
- package/src/browser/ai-getting-started-walkthrough.spec.ts +2 -2
- package/src/browser/ai-getting-started-walkthrough.ts +3 -3
- package/src/browser/ai-ide-activation-service.ts +1 -1
- package/src/browser/ai-sessions-view-contribution.ts +1 -1
- package/src/browser/ai-sessions-widget.tsx +4 -4
- package/src/browser/ai-terminal-functions.ts +1 -1
- package/src/browser/ai-walkthrough-context-keys.spec.ts +1 -1
- package/src/browser/analyze-gh-ticket-command-contribution.ts +3 -3
- package/src/browser/app-tester-chat-agent.ts +2 -2
- package/src/browser/app-tester-chat-functions.ts +2 -2
- package/src/browser/app-tester-prompt-template.ts +2 -2
- package/src/browser/apptester-capability-contribution.ts +1 -1
- package/src/browser/architect-agent.ts +4 -4
- package/src/browser/architect-prompt-template.ts +6 -6
- package/src/browser/chat-session-item.tsx +2 -2
- package/src/browser/chat-session-list-service.spec.ts +2 -2
- package/src/browser/chat-session-list-service.ts +2 -2
- package/src/browser/chat-sessions-welcome-message-provider.tsx +5 -5
- package/src/browser/code-review-capability-contribution.ts +1 -1
- package/src/browser/code-reviewer-agent.ts +1 -1
- package/src/browser/coder-agent.ts +3 -3
- package/src/browser/commit-message-agent.spec.ts +2 -2
- package/src/browser/commit-message-agent.ts +1 -1
- package/src/browser/commit-message-command-contribution.ts +3 -3
- package/src/browser/commit-message-runner.spec.ts +3 -3
- package/src/browser/commit-message-runner.ts +2 -2
- package/src/browser/context-file-validation-service-impl.spec.ts +2 -2
- package/src/browser/context-file-validation-service-impl.ts +1 -1
- package/src/browser/context-functions.spec.ts +1 -1
- package/src/browser/context-functions.ts +1 -1
- package/src/browser/create-skill-agent.ts +2 -2
- package/src/browser/default-chat-agent-recommendation-service.ts +2 -2
- package/src/browser/explore-agent.ts +2 -2
- package/src/browser/file-changeset-function.spec.ts +1 -1
- package/src/browser/file-changeset-functions.spec.ts +3 -3
- package/src/browser/file-changeset-functions.ts +3 -3
- package/src/browser/frontend-module.ts +89 -89
- package/src/browser/git-changes-tool.spec.ts +1 -1
- package/src/browser/github-capability-contribution.ts +1 -1
- package/src/browser/github-chat-agent.ts +3 -3
- package/src/browser/github-repo-variable-contribution.ts +1 -1
- package/src/browser/ide-chat-welcome-message-provider.tsx +3 -3
- package/src/browser/implement-gh-ticket-command-contribution.ts +1 -1
- package/src/browser/memory-capability-contribution.spec.ts +4 -4
- package/src/browser/memory-capability-contribution.ts +5 -3
- package/src/browser/memory-directory-variable-contribution.spec.ts +1 -1
- package/src/browser/memory-directory-variable-contribution.ts +1 -1
- package/src/browser/multi-root-paths.spec.ts +2 -2
- package/src/browser/open-editors-hint-contribution.ts +2 -1
- package/src/browser/remember-command-contribution.ts +2 -2
- package/src/browser/review/pr-review-agent.ts +1 -1
- package/src/browser/review/pr-review-capability-contribution.ts +6 -6
- package/src/browser/review/pr-review-prompt-template.ts +4 -4
- package/src/browser/shell-execution-capability-contribution.ts +1 -1
- package/src/browser/skill-file-functions.spec.ts +1 -1
- package/src/browser/skill-file-functions.ts +1 -1
- package/src/browser/summarize-session-command-contribution.ts +2 -2
- package/src/browser/task-background-summary-variable.ts +1 -1
- package/src/browser/task-context-file-storage-service.spec.ts +1 -1
- package/src/browser/task-context-file-storage-service.ts +1 -1
- package/src/browser/task-context-functions.ts +1 -1
- package/src/browser/template-preference-contribution.ts +1 -1
- package/src/browser/todo-tool-renderer.tsx +1 -1
- package/src/browser/todo-tool.spec.ts +1 -1
- package/src/browser/todo-tool.ts +1 -1
- package/src/browser/user-interaction-tool-renderer.tsx +3 -3
- package/src/browser/user-interaction-tool.spec.ts +3 -3
- package/src/browser/user-interaction-tool.ts +2 -2
- package/src/browser/workspace-functions.spec.ts +16 -7
- package/src/browser/workspace-functions.ts +7 -6
- package/src/browser/workspace-launch-provider.spec.ts +2 -2
- package/src/browser/workspace-launch-provider.ts +2 -2
- package/src/browser/workspace-search-provider.spec.ts +2 -2
- package/src/browser/workspace-search-provider.ts +4 -4
- package/src/browser/workspace-task-provider.spec.ts +2 -2
- package/src/browser/workspace-task-provider.ts +2 -2
- package/src/common/agents-md-prompt-template.ts +4 -4
- package/src/common/coder-replace-prompt-template.ts +8 -8
- package/src/common/command-chat-agents.ts +1 -1
- package/src/common/context-files-variable.ts +1 -1
- package/src/common/create-skill-prompt-template.ts +4 -4
- package/src/common/orchestrator-chat-agent.ts +2 -2
- package/src/common/universal-chat-agent.ts +1 -1
- package/src/common/universal-prompt-template.ts +1 -1
- package/src/common/user-interaction-tool.spec.ts +1 -1
- package/src/common/workspace-search-provider-util.spec.ts +1 -1
- package/src/node/app-tester-agent/browser-automation-impl.ts +1 -1
- package/src/node/backend-module.ts +7 -7
- package/src/node/github-repo-service-impl.ts +1 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// *****************************************************************************
|
|
3
2
|
// Copyright (C) 2025 EclipseSource GmbH.
|
|
4
3
|
//
|
|
@@ -14,29 +13,28 @@
|
|
|
14
13
|
//
|
|
15
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
16
15
|
// *****************************************************************************
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const with_workspace_service_defaults_1 = require("@theia/workspace/lib/browser/test/with-workspace-service-defaults");
|
|
16
|
+
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
|
|
17
|
+
let disableJSDOM = enableJSDOM();
|
|
18
|
+
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
|
19
|
+
FrontendApplicationConfigProvider.set({});
|
|
20
|
+
import { expect } from 'chai';
|
|
21
|
+
import { CancellationTokenSource, OS, PreferenceService, ILogger } from '@theia/core';
|
|
22
|
+
import { AccessibleRootContribution, GetWorkspaceDirectoryStructure, FileContentFunction, GetWorkspaceFileList, FileDiagnosticProvider, WorkspaceFunctionScope, FindFilesByPattern } from './workspace-functions.js';
|
|
23
|
+
import { bindRootContributionProvider } from '@theia/core/lib/common/contribution-provider';
|
|
24
|
+
import { AiConfigurationService } from '@theia/ai-core';
|
|
25
|
+
import { Container } from '@theia/core/shared/inversify';
|
|
26
|
+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
27
|
+
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
28
|
+
import { FileOperationError } from '@theia/filesystem/lib/common/files';
|
|
29
|
+
import { URI } from '@theia/core/lib/common/uri';
|
|
30
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
31
|
+
import { ProblemManager } from '@theia/markers/lib/browser';
|
|
32
|
+
import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
|
|
33
|
+
import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
|
|
34
|
+
import { FileSearchService } from '@theia/file-search/lib/common/file-search-service';
|
|
35
|
+
import { Minimatch } from 'minimatch';
|
|
36
|
+
import { MockLogger } from '@theia/core/lib/common/test/mock-logger';
|
|
37
|
+
import { withWorkspaceServiceDefaults } from '@theia/workspace/lib/browser/test/with-workspace-service-defaults';
|
|
40
38
|
const makeFileSearchService = (impl) => {
|
|
41
39
|
const calls = [];
|
|
42
40
|
return {
|
|
@@ -57,11 +55,11 @@ const makeRipgrepLikeSearchService = (filesByRoot) => makeFileSearchService(asyn
|
|
|
57
55
|
if (!root) {
|
|
58
56
|
return [];
|
|
59
57
|
}
|
|
60
|
-
const rootUri = new
|
|
61
|
-
const includes = (options.includePatterns ?? []).map(pattern => new
|
|
62
|
-
const excludes = (options.excludePatterns ?? []).map(pattern => new
|
|
58
|
+
const rootUri = new URI(root);
|
|
59
|
+
const includes = (options.includePatterns ?? []).map(pattern => new Minimatch(pattern, { dot: true }));
|
|
60
|
+
const excludes = (options.excludePatterns ?? []).map(pattern => new Minimatch(pattern, { dot: true }));
|
|
63
61
|
return (filesByRoot[root] ?? []).filter(file => {
|
|
64
|
-
const relativePath = rootUri.relative(new
|
|
62
|
+
const relativePath = rootUri.relative(new URI(file))?.toString() ?? '';
|
|
65
63
|
const included = includes.length === 0 || includes.some(matcher => matcher.match(relativePath));
|
|
66
64
|
const excluded = excludes.some(matcher => matcher.match(relativePath));
|
|
67
65
|
return included && !excluded;
|
|
@@ -85,24 +83,24 @@ describe('Workspace Functions Cancellation Tests', () => {
|
|
|
85
83
|
let mockCtx;
|
|
86
84
|
let container;
|
|
87
85
|
before(() => {
|
|
88
|
-
disableJSDOM =
|
|
86
|
+
disableJSDOM = enableJSDOM();
|
|
89
87
|
});
|
|
90
88
|
after(() => {
|
|
91
89
|
// Disable JSDOM after all tests
|
|
92
90
|
disableJSDOM();
|
|
93
91
|
});
|
|
94
92
|
beforeEach(() => {
|
|
95
|
-
cancellationTokenSource = new
|
|
93
|
+
cancellationTokenSource = new CancellationTokenSource();
|
|
96
94
|
// Setup mock context
|
|
97
95
|
mockCtx = {
|
|
98
96
|
cancellationToken: cancellationTokenSource.token
|
|
99
97
|
};
|
|
100
98
|
// Create a new container for each test
|
|
101
|
-
container = new
|
|
99
|
+
container = new Container();
|
|
102
100
|
// Mock dependencies
|
|
103
101
|
const mockWorkspaceService = {
|
|
104
|
-
roots: Promise.resolve([{ resource: new
|
|
105
|
-
tryGetRoots: () => [{ resource: new
|
|
102
|
+
roots: Promise.resolve([{ resource: new URI('file:///workspace') }]),
|
|
103
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
106
104
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
107
105
|
};
|
|
108
106
|
const mockFileService = {
|
|
@@ -112,16 +110,16 @@ describe('Workspace Functions Cancellation Tests', () => {
|
|
|
112
110
|
children: [
|
|
113
111
|
{
|
|
114
112
|
isDirectory: true,
|
|
115
|
-
resource: new
|
|
113
|
+
resource: new URI('file:///workspace/dir'),
|
|
116
114
|
path: { base: 'dir' }
|
|
117
115
|
}
|
|
118
116
|
],
|
|
119
|
-
resource: new
|
|
117
|
+
resource: new URI('file:///workspace')
|
|
120
118
|
}),
|
|
121
119
|
read: async () => ({ value: { toString: () => 'test content' } })
|
|
122
120
|
};
|
|
123
121
|
const mockPreferenceService = {
|
|
124
|
-
get: (_path,
|
|
122
|
+
get: (_path, options) => options?.fallback
|
|
125
123
|
};
|
|
126
124
|
const mockMonacoWorkspace = {
|
|
127
125
|
getTextDocument: () => undefined
|
|
@@ -140,53 +138,53 @@ describe('Workspace Functions Cancellation Tests', () => {
|
|
|
140
138
|
})
|
|
141
139
|
};
|
|
142
140
|
// Register mocks in the container
|
|
143
|
-
container.bind(
|
|
144
|
-
container.bind(
|
|
145
|
-
container.bind(
|
|
146
|
-
container.bind(
|
|
147
|
-
container.bind(
|
|
148
|
-
container.bind(
|
|
149
|
-
container.bind(
|
|
150
|
-
container.bind(
|
|
151
|
-
container.bind(
|
|
152
|
-
container.bind(
|
|
153
|
-
container.bind(
|
|
154
|
-
container.bind(
|
|
155
|
-
container.bind(
|
|
156
|
-
container.bind(
|
|
157
|
-
container.bind(
|
|
158
|
-
container.bind(
|
|
141
|
+
container.bind(ILogger).to(MockLogger);
|
|
142
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
143
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
144
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
145
|
+
container.bind(MonacoWorkspace).toConstantValue(mockMonacoWorkspace);
|
|
146
|
+
container.bind(ProblemManager).toConstantValue(mockProblemManager);
|
|
147
|
+
container.bind(MonacoTextModelService).toConstantValue(mockMonacoTextModelService);
|
|
148
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
149
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
150
|
+
container.bind(FileSearchService).toConstantValue(makeFileSearchService());
|
|
151
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
152
|
+
container.bind(GetWorkspaceDirectoryStructure).toSelf();
|
|
153
|
+
container.bind(FileContentFunction).toSelf();
|
|
154
|
+
container.bind(GetWorkspaceFileList).toSelf();
|
|
155
|
+
container.bind(FileDiagnosticProvider).toSelf();
|
|
156
|
+
container.bind(FindFilesByPattern).toSelf();
|
|
159
157
|
});
|
|
160
158
|
afterEach(() => {
|
|
161
159
|
cancellationTokenSource.dispose();
|
|
162
160
|
});
|
|
163
161
|
it('GetWorkspaceDirectoryStructure should respect cancellation token', async () => {
|
|
164
|
-
const getDirectoryStructure = container.get(
|
|
162
|
+
const getDirectoryStructure = container.get(GetWorkspaceDirectoryStructure);
|
|
165
163
|
cancellationTokenSource.cancel();
|
|
166
164
|
const handler = getDirectoryStructure.getTool().handler;
|
|
167
165
|
const result = await handler(JSON.stringify({}), mockCtx);
|
|
168
166
|
const jsonResponse = typeof result === 'string' ? JSON.parse(result) : result;
|
|
169
|
-
|
|
167
|
+
expect(jsonResponse.error).to.equal('Operation cancelled by user');
|
|
170
168
|
});
|
|
171
169
|
it('FileContentFunction should respect cancellation token', async () => {
|
|
172
|
-
const fileContentFunction = container.get(
|
|
170
|
+
const fileContentFunction = container.get(FileContentFunction);
|
|
173
171
|
cancellationTokenSource.cancel();
|
|
174
172
|
const handler = fileContentFunction.getTool().handler;
|
|
175
173
|
const result = await handler(JSON.stringify({ file: 'test.txt' }), mockCtx);
|
|
176
174
|
const jsonResponse = JSON.parse(result);
|
|
177
|
-
|
|
175
|
+
expect(jsonResponse.error).to.equal('Operation cancelled by user');
|
|
178
176
|
});
|
|
179
177
|
it('GetWorkspaceFileList should respect cancellation token', async () => {
|
|
180
|
-
const getWorkspaceFileList = container.get(
|
|
178
|
+
const getWorkspaceFileList = container.get(GetWorkspaceFileList);
|
|
181
179
|
cancellationTokenSource.cancel();
|
|
182
180
|
const handler = getWorkspaceFileList.getTool().handler;
|
|
183
181
|
const result = await handler(JSON.stringify({ path: '' }), mockCtx);
|
|
184
|
-
|
|
182
|
+
expect(result).to.include('Operation cancelled by user');
|
|
185
183
|
});
|
|
186
184
|
it('GetWorkspaceFileList should check cancellation at multiple points', async () => {
|
|
187
|
-
const getWorkspaceFileList = container.get(
|
|
185
|
+
const getWorkspaceFileList = container.get(GetWorkspaceFileList);
|
|
188
186
|
// We'll let it pass the first check then cancel
|
|
189
|
-
const mockFileService = container.get(
|
|
187
|
+
const mockFileService = container.get(FileService);
|
|
190
188
|
const originalResolve = mockFileService.resolve;
|
|
191
189
|
// Mock resolve to cancel the token after it's called
|
|
192
190
|
mockFileService.resolve = async (...args) => {
|
|
@@ -197,15 +195,15 @@ describe('Workspace Functions Cancellation Tests', () => {
|
|
|
197
195
|
const handler = getWorkspaceFileList.getTool().handler;
|
|
198
196
|
// Use a path that triggers file resolution (root name + subdirectory)
|
|
199
197
|
const result = await handler(JSON.stringify({ path: 'workspace/dir' }), mockCtx);
|
|
200
|
-
|
|
198
|
+
expect(result).to.include('Operation cancelled by user');
|
|
201
199
|
});
|
|
202
200
|
it('FileDiagnosticProvider should respect cancellation token', async () => {
|
|
203
|
-
const fileDiagnosticProvider = container.get(
|
|
201
|
+
const fileDiagnosticProvider = container.get(FileDiagnosticProvider);
|
|
204
202
|
cancellationTokenSource.cancel();
|
|
205
203
|
const handler = fileDiagnosticProvider.getTool().handler;
|
|
206
204
|
const result = await handler(JSON.stringify({ file: 'test.txt' }), mockCtx);
|
|
207
205
|
const jsonResponse = JSON.parse(result);
|
|
208
|
-
|
|
206
|
+
expect(jsonResponse.error).to.equal('Operation cancelled by user');
|
|
209
207
|
});
|
|
210
208
|
});
|
|
211
209
|
describe('FileContentFunction.getArgumentsShortLabel', () => {
|
|
@@ -213,16 +211,16 @@ describe('FileContentFunction.getArgumentsShortLabel', () => {
|
|
|
213
211
|
let getArgumentsShortLabel;
|
|
214
212
|
let disableJSDOMInner;
|
|
215
213
|
before(() => {
|
|
216
|
-
disableJSDOMInner =
|
|
214
|
+
disableJSDOMInner = enableJSDOM();
|
|
217
215
|
});
|
|
218
216
|
after(() => {
|
|
219
217
|
disableJSDOMInner();
|
|
220
218
|
});
|
|
221
219
|
beforeEach(() => {
|
|
222
|
-
container = new
|
|
220
|
+
container = new Container();
|
|
223
221
|
const mockWorkspaceService = {
|
|
224
|
-
roots: Promise.resolve([{ resource: new
|
|
225
|
-
tryGetRoots: () => [{ resource: new
|
|
222
|
+
roots: Promise.resolve([{ resource: new URI('file:///workspace') }]),
|
|
223
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
226
224
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
227
225
|
};
|
|
228
226
|
const mockFileService = {
|
|
@@ -230,52 +228,52 @@ describe('FileContentFunction.getArgumentsShortLabel', () => {
|
|
|
230
228
|
resolve: async () => ({
|
|
231
229
|
isDirectory: true,
|
|
232
230
|
children: [],
|
|
233
|
-
resource: new
|
|
231
|
+
resource: new URI('file:///workspace')
|
|
234
232
|
}),
|
|
235
233
|
read: async () => ({ value: { toString: () => 'test content' } })
|
|
236
234
|
};
|
|
237
235
|
const mockPreferenceService = {
|
|
238
|
-
get: (_path,
|
|
236
|
+
get: (_path, options) => options?.fallback
|
|
239
237
|
};
|
|
240
238
|
const mockMonacoWorkspace = {
|
|
241
239
|
getTextDocument: () => undefined
|
|
242
240
|
};
|
|
243
|
-
container.bind(
|
|
244
|
-
container.bind(
|
|
245
|
-
container.bind(
|
|
246
|
-
container.bind(
|
|
247
|
-
container.bind(
|
|
248
|
-
container.bind(
|
|
249
|
-
container.bind(
|
|
250
|
-
container.bind(
|
|
251
|
-
container.bind(
|
|
252
|
-
const fileContentFunction = container.get(
|
|
241
|
+
container.bind(ILogger).to(MockLogger);
|
|
242
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
243
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
244
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
245
|
+
container.bind(MonacoWorkspace).toConstantValue(mockMonacoWorkspace);
|
|
246
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
247
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
248
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
249
|
+
container.bind(FileContentFunction).toSelf();
|
|
250
|
+
const fileContentFunction = container.get(FileContentFunction);
|
|
253
251
|
const tool = fileContentFunction.getTool();
|
|
254
252
|
getArgumentsShortLabel = tool.getArgumentsShortLabel;
|
|
255
253
|
});
|
|
256
254
|
it('returns label for valid file argument', () => {
|
|
257
255
|
const result = getArgumentsShortLabel(JSON.stringify({ file: 'src/index.ts' }));
|
|
258
|
-
|
|
256
|
+
expect(result).to.deep.equal({ label: 'src/index.ts', hasMore: false });
|
|
259
257
|
});
|
|
260
258
|
it('returns undefined for invalid JSON', () => {
|
|
261
259
|
const result = getArgumentsShortLabel('not valid json');
|
|
262
|
-
|
|
260
|
+
expect(result).to.be.undefined;
|
|
263
261
|
});
|
|
264
262
|
it('returns undefined when file key is missing', () => {
|
|
265
263
|
const result = getArgumentsShortLabel(JSON.stringify({ path: 'src/index.ts' }));
|
|
266
|
-
|
|
264
|
+
expect(result).to.be.undefined;
|
|
267
265
|
});
|
|
268
266
|
it('returns hasMore true when offset is provided', () => {
|
|
269
267
|
const result = getArgumentsShortLabel(JSON.stringify({ file: 'src/index.ts', offset: 10 }));
|
|
270
|
-
|
|
268
|
+
expect(result).to.deep.equal({ label: 'src/index.ts', hasMore: true });
|
|
271
269
|
});
|
|
272
270
|
it('returns hasMore true when limit is provided', () => {
|
|
273
271
|
const result = getArgumentsShortLabel(JSON.stringify({ file: 'src/index.ts', limit: 50 }));
|
|
274
|
-
|
|
272
|
+
expect(result).to.deep.equal({ label: 'src/index.ts', hasMore: true });
|
|
275
273
|
});
|
|
276
274
|
it('returns hasMore true when both offset and limit are provided', () => {
|
|
277
275
|
const result = getArgumentsShortLabel(JSON.stringify({ file: 'src/index.ts', offset: 10, limit: 50 }));
|
|
278
|
-
|
|
276
|
+
expect(result).to.deep.equal({ label: 'src/index.ts', hasMore: true });
|
|
279
277
|
});
|
|
280
278
|
});
|
|
281
279
|
describe('FileContentFunction handler', () => {
|
|
@@ -305,23 +303,23 @@ describe('FileContentFunction handler', () => {
|
|
|
305
303
|
};
|
|
306
304
|
let disableJSDOMInner;
|
|
307
305
|
before(() => {
|
|
308
|
-
disableJSDOMInner =
|
|
306
|
+
disableJSDOMInner = enableJSDOM();
|
|
309
307
|
});
|
|
310
308
|
after(() => {
|
|
311
309
|
disableJSDOMInner();
|
|
312
310
|
});
|
|
313
311
|
beforeEach(() => {
|
|
314
|
-
container = new
|
|
312
|
+
container = new Container();
|
|
315
313
|
const mockWorkspaceService = {
|
|
316
|
-
roots: Promise.resolve([{ resource: new
|
|
317
|
-
tryGetRoots: () => [{ resource: new
|
|
314
|
+
roots: Promise.resolve([{ resource: new URI('file:///workspace') }]),
|
|
315
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
318
316
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
319
317
|
};
|
|
320
318
|
mockResolve = async () => ({
|
|
321
319
|
isFile: true,
|
|
322
320
|
isDirectory: false,
|
|
323
321
|
size: 1024,
|
|
324
|
-
resource: new
|
|
322
|
+
resource: new URI('file:///workspace/test.txt')
|
|
325
323
|
});
|
|
326
324
|
mockRead = async () => ({ value: 'line1\nline2\nline3\nline4\nline5' });
|
|
327
325
|
mockReadStream = async () => ({ value: makeMockStream('line1\nline2\nline3\nline4\nline5') });
|
|
@@ -335,26 +333,35 @@ describe('FileContentFunction handler', () => {
|
|
|
335
333
|
readStream: () => mockReadStream(),
|
|
336
334
|
};
|
|
337
335
|
mockPreferenceService = {
|
|
338
|
-
get: (_path,
|
|
336
|
+
get: (_path, optionsOrFallback) => {
|
|
337
|
+
if (optionsOrFallback === undefined) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
// eslint-disable-next-line no-null/no-null
|
|
341
|
+
if (typeof optionsOrFallback === 'object' && optionsOrFallback !== null && !Array.isArray(optionsOrFallback) && 'fallback' in optionsOrFallback) {
|
|
342
|
+
return optionsOrFallback.fallback;
|
|
343
|
+
}
|
|
344
|
+
return optionsOrFallback;
|
|
345
|
+
}
|
|
339
346
|
};
|
|
340
347
|
mockMonacoWorkspace = {
|
|
341
348
|
getTextDocument: () => undefined
|
|
342
349
|
};
|
|
343
|
-
container.bind(
|
|
344
|
-
container.bind(
|
|
345
|
-
container.bind(
|
|
346
|
-
container.bind(
|
|
347
|
-
container.bind(
|
|
348
|
-
container.bind(
|
|
349
|
-
container.bind(
|
|
350
|
-
container.bind(
|
|
351
|
-
container.bind(
|
|
352
|
-
fileContentFunction = container.get(
|
|
350
|
+
container.bind(ILogger).to(MockLogger);
|
|
351
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
352
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
353
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
354
|
+
container.bind(MonacoWorkspace).toConstantValue(mockMonacoWorkspace);
|
|
355
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
356
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
357
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
358
|
+
container.bind(FileContentFunction).toSelf();
|
|
359
|
+
fileContentFunction = container.get(FileContentFunction);
|
|
353
360
|
});
|
|
354
361
|
it('returns file content when file is within size limit', async () => {
|
|
355
362
|
const handler = fileContentFunction.getTool().handler;
|
|
356
363
|
const result = await handler(JSON.stringify({ file: 'test.txt' }), undefined);
|
|
357
|
-
|
|
364
|
+
expect(result).to.equal('line1\nline2\nline3\nline4\nline5');
|
|
358
365
|
});
|
|
359
366
|
it('rejects without reading when on-disk size exceeds limit', async () => {
|
|
360
367
|
// Stat reports 512 KB; default limit is 256 KB
|
|
@@ -363,7 +370,7 @@ describe('FileContentFunction handler', () => {
|
|
|
363
370
|
isFile: true,
|
|
364
371
|
isDirectory: false,
|
|
365
372
|
size: 512 * 1024,
|
|
366
|
-
resource: new
|
|
373
|
+
resource: new URI('file:///workspace/big.txt')
|
|
367
374
|
});
|
|
368
375
|
mockRead = async () => {
|
|
369
376
|
readCalled = true;
|
|
@@ -372,23 +379,23 @@ describe('FileContentFunction handler', () => {
|
|
|
372
379
|
const handler = fileContentFunction.getTool().handler;
|
|
373
380
|
const result = await handler(JSON.stringify({ file: 'big.txt' }), undefined);
|
|
374
381
|
const parsed = JSON.parse(result);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
382
|
+
expect(parsed.error).to.include('size limit');
|
|
383
|
+
expect(parsed.sizeKB).to.equal(512);
|
|
384
|
+
expect(readCalled).to.be.false;
|
|
378
385
|
});
|
|
379
386
|
it('returns editor content when file is open in editor and within size limit', async () => {
|
|
380
387
|
let resolveCalled = false;
|
|
381
388
|
mockResolve = async () => {
|
|
382
389
|
resolveCalled = true;
|
|
383
|
-
return { isFile: true, isDirectory: false, size: 1024, resource: new
|
|
390
|
+
return { isFile: true, isDirectory: false, size: 1024, resource: new URI('file:///workspace/open.txt') };
|
|
384
391
|
};
|
|
385
392
|
mockMonacoWorkspace.getTextDocument = () => ({
|
|
386
393
|
getText: () => 'editor content'
|
|
387
394
|
});
|
|
388
395
|
const handler = fileContentFunction.getTool().handler;
|
|
389
396
|
const result = await handler(JSON.stringify({ file: 'open.txt' }), undefined);
|
|
390
|
-
|
|
391
|
-
|
|
397
|
+
expect(result).to.equal('editor content');
|
|
398
|
+
expect(resolveCalled).to.be.false;
|
|
392
399
|
});
|
|
393
400
|
it('rejects editor content when it exceeds the size limit', async () => {
|
|
394
401
|
const bigContent = 'x'.repeat(512 * 1024);
|
|
@@ -398,14 +405,14 @@ describe('FileContentFunction handler', () => {
|
|
|
398
405
|
const handler = fileContentFunction.getTool().handler;
|
|
399
406
|
const result = await handler(JSON.stringify({ file: 'open.txt' }), undefined);
|
|
400
407
|
const parsed = JSON.parse(result);
|
|
401
|
-
|
|
402
|
-
|
|
408
|
+
expect(parsed.error).to.include('size limit');
|
|
409
|
+
expect(parsed.sizeKB).to.equal(512);
|
|
403
410
|
});
|
|
404
411
|
it('returns sliced content with header when offset and limit are provided', async () => {
|
|
405
412
|
const handler = fileContentFunction.getTool().handler;
|
|
406
413
|
const result = await handler(JSON.stringify({ file: 'test.txt', offset: 1, limit: 2 }), undefined);
|
|
407
|
-
|
|
408
|
-
|
|
414
|
+
expect(result).to.include('[Lines 2\u20133 of 5 total.');
|
|
415
|
+
expect(result).to.include('line2\nline3');
|
|
409
416
|
});
|
|
410
417
|
it('returns sliced editor content with header when file is open and offset/limit are provided', async () => {
|
|
411
418
|
mockMonacoWorkspace.getTextDocument = () => ({
|
|
@@ -413,8 +420,8 @@ describe('FileContentFunction handler', () => {
|
|
|
413
420
|
});
|
|
414
421
|
const handler = fileContentFunction.getTool().handler;
|
|
415
422
|
const result = await handler(JSON.stringify({ file: 'open.txt', offset: 1, limit: 2 }), undefined);
|
|
416
|
-
|
|
417
|
-
|
|
423
|
+
expect(result).to.include('[Lines 2\u20133 of 5 total.');
|
|
424
|
+
expect(result).to.include('beta\ngamma');
|
|
418
425
|
});
|
|
419
426
|
it('does not call resolve() or read() for paginated disk reads, uses readStream instead', async () => {
|
|
420
427
|
// Stat would report huge file, but the streaming path bypasses both stat and read
|
|
@@ -426,7 +433,7 @@ describe('FileContentFunction handler', () => {
|
|
|
426
433
|
isFile: true,
|
|
427
434
|
isDirectory: false,
|
|
428
435
|
size: 512 * 1024,
|
|
429
|
-
resource: new
|
|
436
|
+
resource: new URI('file:///workspace/big.txt')
|
|
430
437
|
};
|
|
431
438
|
};
|
|
432
439
|
mockRead = async () => {
|
|
@@ -436,9 +443,9 @@ describe('FileContentFunction handler', () => {
|
|
|
436
443
|
const handler = fileContentFunction.getTool().handler;
|
|
437
444
|
const result = await handler(JSON.stringify({ file: 'big.txt', offset: 0, limit: 3 }), undefined);
|
|
438
445
|
// resolve and read are NOT called in the streaming path
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
446
|
+
expect(resolveCalled).to.be.false;
|
|
447
|
+
expect(readCalled).to.be.false;
|
|
448
|
+
expect(result).to.include('line1\nline2\nline3');
|
|
442
449
|
});
|
|
443
450
|
it('rejects when the requested slice itself exceeds the size limit', async () => {
|
|
444
451
|
const bigLine = 'x'.repeat(1024);
|
|
@@ -448,8 +455,8 @@ describe('FileContentFunction handler', () => {
|
|
|
448
455
|
// Reading all 300 lines × 1 KB each = ~300 KB, over the 256 KB default limit
|
|
449
456
|
const result = await handler(JSON.stringify({ file: 'big.txt', offset: 0, limit: 300 }), undefined);
|
|
450
457
|
const parsed = JSON.parse(result);
|
|
451
|
-
|
|
452
|
-
|
|
458
|
+
expect(parsed.error).to.include('size limit');
|
|
459
|
+
expect(parsed.resultSizeKB).to.be.greaterThan(256);
|
|
453
460
|
});
|
|
454
461
|
it('returns File not found error when file does not exist', async () => {
|
|
455
462
|
mockResolve = async () => { throw new Error('File not found'); };
|
|
@@ -458,66 +465,66 @@ describe('FileContentFunction handler', () => {
|
|
|
458
465
|
const handler = fileContentFunction.getTool().handler;
|
|
459
466
|
const result = await handler(JSON.stringify({ file: 'nonexistent.txt' }), undefined);
|
|
460
467
|
const parsed = JSON.parse(result);
|
|
461
|
-
|
|
468
|
+
expect(parsed.error).to.equal('File not found');
|
|
462
469
|
});
|
|
463
470
|
it('rejects negative offset', async () => {
|
|
464
471
|
const handler = fileContentFunction.getTool().handler;
|
|
465
472
|
const result = await handler(JSON.stringify({ file: 'test.txt', offset: -1 }), undefined);
|
|
466
473
|
const parsed = JSON.parse(result);
|
|
467
|
-
|
|
474
|
+
expect(parsed.error).to.include('non-negative integer');
|
|
468
475
|
});
|
|
469
476
|
it('rejects fractional offset', async () => {
|
|
470
477
|
const handler = fileContentFunction.getTool().handler;
|
|
471
478
|
const result = await handler(JSON.stringify({ file: 'test.txt', offset: 1.5 }), undefined);
|
|
472
479
|
const parsed = JSON.parse(result);
|
|
473
|
-
|
|
480
|
+
expect(parsed.error).to.include('non-negative integer');
|
|
474
481
|
});
|
|
475
482
|
it('rejects negative limit', async () => {
|
|
476
483
|
const handler = fileContentFunction.getTool().handler;
|
|
477
484
|
const result = await handler(JSON.stringify({ file: 'test.txt', limit: -1 }), undefined);
|
|
478
485
|
const parsed = JSON.parse(result);
|
|
479
|
-
|
|
486
|
+
expect(parsed.error).to.include('positive integer');
|
|
480
487
|
});
|
|
481
488
|
it('rejects zero limit', async () => {
|
|
482
489
|
const handler = fileContentFunction.getTool().handler;
|
|
483
490
|
const result = await handler(JSON.stringify({ file: 'test.txt', limit: 0 }), undefined);
|
|
484
491
|
const parsed = JSON.parse(result);
|
|
485
|
-
|
|
492
|
+
expect(parsed.error).to.include('positive integer');
|
|
486
493
|
});
|
|
487
494
|
it('returns content from offset to end when only offset is provided', async () => {
|
|
488
495
|
const handler = fileContentFunction.getTool().handler;
|
|
489
496
|
const result = await handler(JSON.stringify({ file: 'test.txt', offset: 2 }), undefined);
|
|
490
|
-
|
|
491
|
-
|
|
497
|
+
expect(result).to.include('[Lines 3\u20135 of 5 total.');
|
|
498
|
+
expect(result).to.include('line3\nline4\nline5');
|
|
492
499
|
});
|
|
493
500
|
it('returns last line when offset is at boundary', async () => {
|
|
494
501
|
const handler = fileContentFunction.getTool().handler;
|
|
495
502
|
const result = await handler(JSON.stringify({ file: 'test.txt', offset: 4, limit: 1 }), undefined);
|
|
496
|
-
|
|
497
|
-
|
|
503
|
+
expect(result).to.include('[Lines 5\u20135 of 5 total.');
|
|
504
|
+
expect(result).to.include('line5');
|
|
498
505
|
});
|
|
499
506
|
it('returns empty content when offset is beyond end of file', async () => {
|
|
500
507
|
const handler = fileContentFunction.getTool().handler;
|
|
501
508
|
const result = await handler(JSON.stringify({ file: 'test.txt', offset: 100, limit: 5 }), undefined);
|
|
502
509
|
// slice beyond end returns empty array → empty joined string
|
|
503
|
-
|
|
510
|
+
expect(result).to.include('[Lines 101\u2013100 of 5 total.');
|
|
504
511
|
});
|
|
505
512
|
it('uses custom preference value for size limit', async () => {
|
|
506
513
|
// Set a very small limit of 1 KB
|
|
507
|
-
mockPreferenceService.get = (_path,
|
|
514
|
+
mockPreferenceService.get = (_path, _optionsOrFallback) => 1;
|
|
508
515
|
const content = 'x'.repeat(2 * 1024); // 2 KB
|
|
509
516
|
mockRead = async () => ({ value: content });
|
|
510
517
|
mockResolve = async () => ({
|
|
511
518
|
isFile: true,
|
|
512
519
|
isDirectory: false,
|
|
513
520
|
size: 2 * 1024,
|
|
514
|
-
resource: new
|
|
521
|
+
resource: new URI('file:///workspace/test.txt')
|
|
515
522
|
});
|
|
516
523
|
const handler = fileContentFunction.getTool().handler;
|
|
517
524
|
const result = await handler(JSON.stringify({ file: 'test.txt' }), undefined);
|
|
518
525
|
const parsed = JSON.parse(result);
|
|
519
|
-
|
|
520
|
-
|
|
526
|
+
expect(parsed.error).to.include('size limit');
|
|
527
|
+
expect(parsed.maxSizeKB).to.equal(1);
|
|
521
528
|
});
|
|
522
529
|
it('falls back to streaming when stat.size is undefined and file is within limit', async () => {
|
|
523
530
|
// stat does not include a size — the code must not treat this as "0 KB"
|
|
@@ -527,7 +534,7 @@ describe('FileContentFunction handler', () => {
|
|
|
527
534
|
isFile: true,
|
|
528
535
|
isDirectory: false,
|
|
529
536
|
size: undefined,
|
|
530
|
-
resource: new
|
|
537
|
+
resource: new URI('file:///workspace/test.txt')
|
|
531
538
|
});
|
|
532
539
|
mockRead = async () => {
|
|
533
540
|
readCalled = true;
|
|
@@ -536,10 +543,10 @@ describe('FileContentFunction handler', () => {
|
|
|
536
543
|
// mockReadStream already returns the small 5-line fixture from beforeEach
|
|
537
544
|
const handler = fileContentFunction.getTool().handler;
|
|
538
545
|
const result = await handler(JSON.stringify({ file: 'test.txt' }), undefined);
|
|
539
|
-
|
|
540
|
-
|
|
546
|
+
expect(readCalled).to.be.false;
|
|
547
|
+
expect(result).to.include('line1');
|
|
541
548
|
// Full-file streaming fallback must NOT include the [Lines...] header
|
|
542
|
-
|
|
549
|
+
expect(result).to.not.include('[Lines');
|
|
543
550
|
});
|
|
544
551
|
it('returns size-limit error (not "File not found") when stat.size is undefined and streamed content exceeds limit', async () => {
|
|
545
552
|
// stat does not include a size; the streamed content is larger than the limit.
|
|
@@ -547,7 +554,7 @@ describe('FileContentFunction handler', () => {
|
|
|
547
554
|
isFile: true,
|
|
548
555
|
isDirectory: false,
|
|
549
556
|
size: undefined,
|
|
550
|
-
resource: new
|
|
557
|
+
resource: new URI('file:///workspace/big.txt')
|
|
551
558
|
});
|
|
552
559
|
const bigLine = 'x'.repeat(1024);
|
|
553
560
|
const bigContent = Array.from({ length: 300 }, () => bigLine).join('\n'); // ~300 KB
|
|
@@ -555,9 +562,9 @@ describe('FileContentFunction handler', () => {
|
|
|
555
562
|
const handler = fileContentFunction.getTool().handler;
|
|
556
563
|
const result = await handler(JSON.stringify({ file: 'big.txt' }), undefined);
|
|
557
564
|
const parsed = JSON.parse(result);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
565
|
+
expect(parsed.error).to.include('size limit');
|
|
566
|
+
expect(parsed.error).to.include('offset');
|
|
567
|
+
expect(parsed.error).not.to.equal('File not found');
|
|
561
568
|
});
|
|
562
569
|
it('returns size-limit error (not "File not found") when fileService.read throws FILE_TOO_LARGE', async () => {
|
|
563
570
|
// Simulate a file system provider that enforces its own hard size limit below maxSizeKB.
|
|
@@ -566,34 +573,34 @@ describe('FileContentFunction handler', () => {
|
|
|
566
573
|
isFile: true,
|
|
567
574
|
isDirectory: false,
|
|
568
575
|
size: 100 * 1024, // 100 KB — under the 256 KB default limit
|
|
569
|
-
resource: new
|
|
576
|
+
resource: new URI('file:///workspace/test.txt')
|
|
570
577
|
});
|
|
571
578
|
mockRead = async () => {
|
|
572
|
-
throw new
|
|
579
|
+
throw new FileOperationError('File too large', 7 /* FileOperationResult.FILE_TOO_LARGE */);
|
|
573
580
|
};
|
|
574
581
|
const handler = fileContentFunction.getTool().handler;
|
|
575
582
|
const result = await handler(JSON.stringify({ file: 'test.txt' }), undefined);
|
|
576
583
|
const parsed = JSON.parse(result);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
584
|
+
expect(parsed.error).to.include('size limit');
|
|
585
|
+
expect(parsed.error).to.include('offset');
|
|
586
|
+
expect(parsed.maxSizeKB).to.equal(256);
|
|
580
587
|
});
|
|
581
588
|
it('returns size-limit error (not "File not found") when fileService.read throws FILE_EXCEEDS_MEMORY_LIMIT', async () => {
|
|
582
589
|
mockResolve = async () => ({
|
|
583
590
|
isFile: true,
|
|
584
591
|
isDirectory: false,
|
|
585
592
|
size: 100 * 1024,
|
|
586
|
-
resource: new
|
|
593
|
+
resource: new URI('file:///workspace/test.txt')
|
|
587
594
|
});
|
|
588
595
|
mockRead = async () => {
|
|
589
|
-
throw new
|
|
596
|
+
throw new FileOperationError('Exceeds memory limit', 9 /* FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT */);
|
|
590
597
|
};
|
|
591
598
|
const handler = fileContentFunction.getTool().handler;
|
|
592
599
|
const result = await handler(JSON.stringify({ file: 'test.txt' }), undefined);
|
|
593
600
|
const parsed = JSON.parse(result);
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
601
|
+
expect(parsed.error).to.include('size limit');
|
|
602
|
+
expect(parsed.error).to.include('offset');
|
|
603
|
+
expect(parsed.maxSizeKB).to.equal(256);
|
|
597
604
|
});
|
|
598
605
|
it('returns size-limit error (not "File not found") when readStream throws FILE_TOO_LARGE for paginated read', async () => {
|
|
599
606
|
// This is the key scenario: files.maxFileSizeMB is lower than the file size,
|
|
@@ -601,15 +608,15 @@ describe('FileContentFunction handler', () => {
|
|
|
601
608
|
// Before the fix, readStream would throw FILE_TOO_LARGE and the catch block
|
|
602
609
|
// would return "File not found".
|
|
603
610
|
mockReadStream = async () => {
|
|
604
|
-
throw new
|
|
611
|
+
throw new FileOperationError('File too large', 7 /* FileOperationResult.FILE_TOO_LARGE */);
|
|
605
612
|
};
|
|
606
613
|
const handler = fileContentFunction.getTool().handler;
|
|
607
614
|
const result = await handler(JSON.stringify({ file: 'big.txt', offset: 0, limit: 50 }), undefined);
|
|
608
615
|
const parsed = JSON.parse(result);
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
616
|
+
expect(parsed.error).to.include('size limit');
|
|
617
|
+
expect(parsed.error).to.include('offset');
|
|
618
|
+
expect(parsed.error).not.to.equal('File not found');
|
|
619
|
+
expect(parsed.maxSizeKB).to.equal(256);
|
|
613
620
|
});
|
|
614
621
|
});
|
|
615
622
|
describe('FindFilesByPattern.getArgumentsShortLabel', () => {
|
|
@@ -617,16 +624,16 @@ describe('FindFilesByPattern.getArgumentsShortLabel', () => {
|
|
|
617
624
|
let getArgumentsShortLabel;
|
|
618
625
|
let disableJSDOMInner;
|
|
619
626
|
before(() => {
|
|
620
|
-
disableJSDOMInner =
|
|
627
|
+
disableJSDOMInner = enableJSDOM();
|
|
621
628
|
});
|
|
622
629
|
after(() => {
|
|
623
630
|
disableJSDOMInner();
|
|
624
631
|
});
|
|
625
632
|
beforeEach(() => {
|
|
626
|
-
container = new
|
|
633
|
+
container = new Container();
|
|
627
634
|
const mockWorkspaceService = {
|
|
628
|
-
roots: Promise.resolve([{ resource: new
|
|
629
|
-
tryGetRoots: () => [{ resource: new
|
|
635
|
+
roots: Promise.resolve([{ resource: new URI('file:///workspace') }]),
|
|
636
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
630
637
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
631
638
|
};
|
|
632
639
|
const mockFileService = {
|
|
@@ -634,41 +641,41 @@ describe('FindFilesByPattern.getArgumentsShortLabel', () => {
|
|
|
634
641
|
resolve: async () => ({
|
|
635
642
|
isDirectory: true,
|
|
636
643
|
children: [],
|
|
637
|
-
resource: new
|
|
644
|
+
resource: new URI('file:///workspace')
|
|
638
645
|
}),
|
|
639
646
|
read: async () => ({ value: { toString: () => 'test content' } })
|
|
640
647
|
};
|
|
641
648
|
const mockPreferenceService = {
|
|
642
|
-
get: (_path,
|
|
643
|
-
};
|
|
644
|
-
container.bind(
|
|
645
|
-
container.bind(
|
|
646
|
-
container.bind(
|
|
647
|
-
container.bind(
|
|
648
|
-
container.bind(
|
|
649
|
-
container.bind(
|
|
650
|
-
container.bind(
|
|
651
|
-
container.bind(
|
|
652
|
-
container.bind(
|
|
653
|
-
const findFilesByPattern = container.get(
|
|
649
|
+
get: (_path, options) => options?.fallback
|
|
650
|
+
};
|
|
651
|
+
container.bind(ILogger).to(MockLogger);
|
|
652
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
653
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
654
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
655
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
656
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
657
|
+
container.bind(FileSearchService).toConstantValue(makeFileSearchService());
|
|
658
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
659
|
+
container.bind(FindFilesByPattern).toSelf();
|
|
660
|
+
const findFilesByPattern = container.get(FindFilesByPattern);
|
|
654
661
|
const tool = findFilesByPattern.getTool();
|
|
655
662
|
getArgumentsShortLabel = tool.getArgumentsShortLabel;
|
|
656
663
|
});
|
|
657
664
|
it('returns label for valid pattern argument', () => {
|
|
658
665
|
const result = getArgumentsShortLabel(JSON.stringify({ pattern: '**/*.ts' }));
|
|
659
|
-
|
|
666
|
+
expect(result).to.deep.equal({ label: '**/*.ts', hasMore: false });
|
|
660
667
|
});
|
|
661
668
|
it('returns hasMore true when additional arguments exist', () => {
|
|
662
669
|
const result = getArgumentsShortLabel(JSON.stringify({ pattern: '**/*.ts', exclude: ['node_modules'] }));
|
|
663
|
-
|
|
670
|
+
expect(result).to.deep.equal({ label: '**/*.ts', hasMore: true });
|
|
664
671
|
});
|
|
665
672
|
it('returns undefined for invalid JSON', () => {
|
|
666
673
|
const result = getArgumentsShortLabel('not valid json');
|
|
667
|
-
|
|
674
|
+
expect(result).to.be.undefined;
|
|
668
675
|
});
|
|
669
676
|
it('returns undefined when pattern key is missing', () => {
|
|
670
677
|
const result = getArgumentsShortLabel(JSON.stringify({ glob: '**/*.ts' }));
|
|
671
|
-
|
|
678
|
+
expect(result).to.be.undefined;
|
|
672
679
|
});
|
|
673
680
|
});
|
|
674
681
|
describe('FindFilesByPattern.findFiles', () => {
|
|
@@ -680,20 +687,20 @@ describe('FindFilesByPattern.findFiles', () => {
|
|
|
680
687
|
let prefs;
|
|
681
688
|
let allowedExternal;
|
|
682
689
|
let disableJSDOMInner;
|
|
683
|
-
before(() => { disableJSDOMInner =
|
|
690
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
684
691
|
after(() => { disableJSDOMInner(); });
|
|
685
692
|
// External hits are rendered with `Path.fsPath()`, which branches on the backend OS, so the POSIX
|
|
686
693
|
// fixtures below pin it instead of inheriting the OS the tests happen to run on.
|
|
687
694
|
let originalIsWindows;
|
|
688
695
|
beforeEach(() => {
|
|
689
|
-
originalIsWindows =
|
|
690
|
-
|
|
696
|
+
originalIsWindows = OS.backend.isWindows;
|
|
697
|
+
OS.backend.isWindows = false;
|
|
691
698
|
});
|
|
692
|
-
afterEach(() => {
|
|
699
|
+
afterEach(() => { OS.backend.isWindows = originalIsWindows; });
|
|
693
700
|
beforeEach(() => {
|
|
694
|
-
container = new
|
|
701
|
+
container = new Container();
|
|
695
702
|
searchResults = [];
|
|
696
|
-
roots = [{ resource: new
|
|
703
|
+
roots = [{ resource: new URI('file:///workspace') }];
|
|
697
704
|
prefs = {
|
|
698
705
|
'ai-features.workspaceFunctions.considerGitIgnore': true,
|
|
699
706
|
'ai-features.workspaceFunctions.userExcludes': ['node_modules', 'lib']
|
|
@@ -718,99 +725,99 @@ describe('FindFilesByPattern.findFiles', () => {
|
|
|
718
725
|
onDidChangeTrust: () => ({ dispose: () => { } })
|
|
719
726
|
};
|
|
720
727
|
fileSearchService = makeFileSearchService(async () => searchResults);
|
|
721
|
-
container.bind(
|
|
722
|
-
container.bind(
|
|
723
|
-
container.bind(
|
|
724
|
-
container.bind(
|
|
725
|
-
container.bind(
|
|
726
|
-
container.bind(
|
|
727
|
-
container.bind(
|
|
728
|
-
container.bind(
|
|
729
|
-
container.bind(
|
|
730
|
-
findFilesByPattern = container.get(
|
|
728
|
+
container.bind(ILogger).to(MockLogger);
|
|
729
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
730
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
731
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
732
|
+
container.bind(AiConfigurationService).toConstantValue(trustAwareReader);
|
|
733
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
734
|
+
container.bind(FileSearchService).toConstantValue(fileSearchService);
|
|
735
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
736
|
+
container.bind(FindFilesByPattern).toSelf();
|
|
737
|
+
findFilesByPattern = container.get(FindFilesByPattern);
|
|
731
738
|
});
|
|
732
739
|
const call = (args, ctx) => findFilesByPattern.getTool().handler(JSON.stringify(args), ctx);
|
|
733
740
|
it('returns root-prefixed workspace-relative paths', async () => {
|
|
734
741
|
searchResults = ['file:///workspace/src/a.ts', 'file:///workspace/src/b.ts'];
|
|
735
742
|
const result = JSON.parse(await call({ pattern: '**/*.ts' }));
|
|
736
|
-
|
|
743
|
+
expect(result.files).to.deep.equal(['workspace/src/a.ts', 'workspace/src/b.ts']);
|
|
737
744
|
});
|
|
738
745
|
it('passes the glob, user excludes, gitignore flag and root to the search service', async () => {
|
|
739
746
|
await call({ pattern: '**/*.ts', exclude: ['**/*.spec.ts'] });
|
|
740
|
-
|
|
747
|
+
expect(fileSearchService.calls).to.have.length(1);
|
|
741
748
|
const options = fileSearchService.calls[0].options;
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
749
|
+
expect(options.includePatterns).to.deep.equal(['**/*.ts']);
|
|
750
|
+
expect(options.excludePatterns).to.deep.equal(['node_modules', 'lib', '**/*.spec.ts']);
|
|
751
|
+
expect(options.useGitIgnore).to.equal(true);
|
|
752
|
+
expect(options.rootUris).to.deep.equal(['file:///workspace']);
|
|
753
|
+
expect(options.fuzzyMatch).to.equal(false);
|
|
747
754
|
});
|
|
748
755
|
it('does not use gitignore when the preference is disabled', async () => {
|
|
749
756
|
prefs['ai-features.workspaceFunctions.considerGitIgnore'] = false;
|
|
750
757
|
await call({ pattern: '**/*.ts' });
|
|
751
|
-
|
|
758
|
+
expect(fileSearchService.calls[0].options.useGitIgnore).to.equal(false);
|
|
752
759
|
});
|
|
753
760
|
it('reports an error when no workspace is open', async () => {
|
|
754
761
|
roots.length = 0;
|
|
755
762
|
const result = JSON.parse(await call({ pattern: '**/*.ts' }));
|
|
756
|
-
|
|
763
|
+
expect(result.error).to.equal('No workspace has been opened yet');
|
|
757
764
|
});
|
|
758
765
|
it('respects the cancellation token', async () => {
|
|
759
|
-
const cts = new
|
|
766
|
+
const cts = new CancellationTokenSource();
|
|
760
767
|
cts.cancel();
|
|
761
768
|
const result = JSON.parse(await call({ pattern: '**/*.ts' }, { cancellationToken: cts.token }));
|
|
762
|
-
|
|
769
|
+
expect(result.error).to.equal('Operation cancelled by user');
|
|
763
770
|
});
|
|
764
771
|
it('truncates to 200 results and flags truncation', async () => {
|
|
765
772
|
searchResults = Array.from({ length: 250 }, (_, i) => `file:///workspace/f${i}.ts`);
|
|
766
773
|
const result = JSON.parse(await call({ pattern: '**/*.ts' }));
|
|
767
|
-
|
|
768
|
-
|
|
774
|
+
expect(result.files).to.have.length(200);
|
|
775
|
+
expect(result.truncated).to.equal(true);
|
|
769
776
|
});
|
|
770
777
|
it('returns absolute paths for an allow-listed external searchRoot', async () => {
|
|
771
778
|
allowedExternal = ['/external/data'];
|
|
772
779
|
searchResults = ['file:///external/data/x.ts'];
|
|
773
780
|
const result = JSON.parse(await call({ pattern: '**/*.ts', searchRoot: '/external/data' }));
|
|
774
|
-
|
|
775
|
-
|
|
781
|
+
expect(result.files).to.deep.equal(['/external/data/x.ts']);
|
|
782
|
+
expect(fileSearchService.calls[0].options.rootUris).to.deep.equal(['file:///external/data']);
|
|
776
783
|
});
|
|
777
784
|
it('does not apply gitignore to external roots (it is scoped to workspace roots)', async () => {
|
|
778
785
|
prefs['ai-features.workspaceFunctions.considerGitIgnore'] = true;
|
|
779
786
|
allowedExternal = ['/external/data'];
|
|
780
787
|
await call({ pattern: '**/*.ts', searchRoot: '/external/data' });
|
|
781
788
|
const options = fileSearchService.calls[0].options;
|
|
782
|
-
|
|
783
|
-
|
|
789
|
+
expect(options.useGitIgnore).to.equal(false);
|
|
790
|
+
expect(options.excludePatterns).to.include('.git');
|
|
784
791
|
});
|
|
785
792
|
it('still applies gitignore to workspace roots', async () => {
|
|
786
793
|
prefs['ai-features.workspaceFunctions.considerGitIgnore'] = true;
|
|
787
794
|
await call({ pattern: '**/*.ts' });
|
|
788
|
-
|
|
795
|
+
expect(fileSearchService.calls[0].options.useGitIgnore).to.equal(true);
|
|
789
796
|
});
|
|
790
797
|
it('matches an allow-list entry with a trailing slash against a searchRoot without one', async () => {
|
|
791
798
|
allowedExternal = ['/external/data/']; // trailing slash, as a user/file-picker may enter it
|
|
792
799
|
searchResults = ['file:///external/data/x.ts'];
|
|
793
800
|
const result = JSON.parse(await call({ pattern: '**/*.ts', searchRoot: '/external/data' }));
|
|
794
|
-
|
|
795
|
-
|
|
801
|
+
expect(result.error).to.be.undefined;
|
|
802
|
+
expect(result.files).to.deep.equal(['/external/data/x.ts']);
|
|
796
803
|
});
|
|
797
804
|
it('matches an allow-list entry without a trailing slash against a searchRoot that has one', async () => {
|
|
798
805
|
allowedExternal = ['/external/data'];
|
|
799
806
|
searchResults = ['file:///external/data/x.ts'];
|
|
800
807
|
const result = JSON.parse(await call({ pattern: '**/*.ts', searchRoot: '/external/data/' }));
|
|
801
|
-
|
|
802
|
-
|
|
808
|
+
expect(result.error).to.be.undefined;
|
|
809
|
+
expect(result.files).to.deep.equal(['/external/data/x.ts']);
|
|
803
810
|
});
|
|
804
811
|
describe('on Windows', () => {
|
|
805
812
|
// The tool promises the returned paths can be passed back to the file tools, so an external
|
|
806
813
|
// hit must be rendered as a native path and not as the URI-style `/c:/...`.
|
|
807
814
|
it('returns native paths for an allow-listed external searchRoot', async () => {
|
|
808
|
-
|
|
815
|
+
OS.backend.isWindows = true;
|
|
809
816
|
allowedExternal = ['C:\\external\\data'];
|
|
810
817
|
searchResults = ['file:///c%3A/external/data/x.ts'];
|
|
811
818
|
const result = JSON.parse(await call({ pattern: '**/*.ts', searchRoot: 'C:\\external\\data' }));
|
|
812
|
-
|
|
813
|
-
|
|
819
|
+
expect(result.error).to.be.undefined;
|
|
820
|
+
expect(result.files).to.deep.equal(['C:\\external\\data\\x.ts']);
|
|
814
821
|
});
|
|
815
822
|
});
|
|
816
823
|
});
|
|
@@ -820,15 +827,15 @@ describe('WorkspaceFunctionScope gitignore caching', () => {
|
|
|
820
827
|
let resolveCount;
|
|
821
828
|
let gitignoreReadCount;
|
|
822
829
|
let disableJSDOMInner;
|
|
823
|
-
before(() => { disableJSDOMInner =
|
|
830
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
824
831
|
after(() => { disableJSDOMInner(); });
|
|
825
832
|
beforeEach(() => {
|
|
826
|
-
container = new
|
|
833
|
+
container = new Container();
|
|
827
834
|
resolveCount = 0;
|
|
828
835
|
gitignoreReadCount = 0;
|
|
829
836
|
const mockWorkspaceService = {
|
|
830
|
-
roots: Promise.resolve([{ resource: new
|
|
831
|
-
tryGetRoots: () => [{ resource: new
|
|
837
|
+
roots: Promise.resolve([{ resource: new URI('file:///workspace') }]),
|
|
838
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
832
839
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
833
840
|
};
|
|
834
841
|
const mockFileService = {
|
|
@@ -846,30 +853,30 @@ describe('WorkspaceFunctionScope gitignore caching', () => {
|
|
|
846
853
|
const mockPreferenceService = {
|
|
847
854
|
get: (path, defaultValue) => (path === 'ai-features.workspaceFunctions.considerGitIgnore' ? true : defaultValue)
|
|
848
855
|
};
|
|
849
|
-
container.bind(
|
|
850
|
-
container.bind(
|
|
851
|
-
container.bind(
|
|
852
|
-
container.bind(
|
|
853
|
-
container.bind(
|
|
854
|
-
container.bind(
|
|
855
|
-
container.bind(
|
|
856
|
-
scope = container.get(
|
|
856
|
+
container.bind(ILogger).to(MockLogger);
|
|
857
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
858
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
859
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
860
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
861
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
862
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
863
|
+
scope = container.get(WorkspaceFunctionScope);
|
|
857
864
|
});
|
|
858
865
|
const stat = (path, isDirectory = false) => ({
|
|
859
|
-
resource: new
|
|
866
|
+
resource: new URI(path),
|
|
860
867
|
isDirectory,
|
|
861
868
|
path: { base: path.split('/').pop() }
|
|
862
869
|
});
|
|
863
870
|
it('still excludes gitignored paths and keeps others', async () => {
|
|
864
|
-
|
|
865
|
-
|
|
871
|
+
expect(await scope.shouldExclude(stat('file:///workspace/dist/a.js'))).to.be.true;
|
|
872
|
+
expect(await scope.shouldExclude(stat('file:///workspace/src/b.ts'))).to.be.false;
|
|
866
873
|
});
|
|
867
874
|
it('reads .gitignore at most once and issues no per-check resolve RPC', async () => {
|
|
868
875
|
for (let i = 0; i < 25; i++) {
|
|
869
876
|
await scope.shouldExclude(stat(`file:///workspace/src/file${i}.ts`));
|
|
870
877
|
}
|
|
871
|
-
|
|
872
|
-
|
|
878
|
+
expect(gitignoreReadCount).to.equal(1);
|
|
879
|
+
expect(resolveCount).to.equal(0);
|
|
873
880
|
});
|
|
874
881
|
});
|
|
875
882
|
describe('GetWorkspaceFileList resolves the target directory once', () => {
|
|
@@ -877,14 +884,14 @@ describe('GetWorkspaceFileList resolves the target directory once', () => {
|
|
|
877
884
|
let tool;
|
|
878
885
|
let resolveCount;
|
|
879
886
|
let disableJSDOMInner;
|
|
880
|
-
before(() => { disableJSDOMInner =
|
|
887
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
881
888
|
after(() => { disableJSDOMInner(); });
|
|
882
889
|
beforeEach(() => {
|
|
883
|
-
container = new
|
|
890
|
+
container = new Container();
|
|
884
891
|
resolveCount = 0;
|
|
885
892
|
const mockWorkspaceService = {
|
|
886
|
-
roots: Promise.resolve([{ resource: new
|
|
887
|
-
tryGetRoots: () => [{ resource: new
|
|
893
|
+
roots: Promise.resolve([{ resource: new URI('file:///workspace') }]),
|
|
894
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
888
895
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
889
896
|
};
|
|
890
897
|
const mockFileService = {
|
|
@@ -895,8 +902,8 @@ describe('GetWorkspaceFileList resolves the target directory once', () => {
|
|
|
895
902
|
isDirectory: true,
|
|
896
903
|
resource: uri,
|
|
897
904
|
children: [
|
|
898
|
-
{ isDirectory: false, resource: new
|
|
899
|
-
{ isDirectory: true, resource: new
|
|
905
|
+
{ isDirectory: false, resource: new URI('file:///workspace/sub/a.ts'), path: { base: 'a.ts' } },
|
|
906
|
+
{ isDirectory: true, resource: new URI('file:///workspace/sub/dir'), path: { base: 'dir' } }
|
|
900
907
|
]
|
|
901
908
|
};
|
|
902
909
|
},
|
|
@@ -905,20 +912,20 @@ describe('GetWorkspaceFileList resolves the target directory once', () => {
|
|
|
905
912
|
onDidFilesChange: () => ({ dispose: () => { } })
|
|
906
913
|
};
|
|
907
914
|
const mockPreferenceService = { get: (_path, def) => def };
|
|
908
|
-
container.bind(
|
|
909
|
-
container.bind(
|
|
910
|
-
container.bind(
|
|
911
|
-
container.bind(
|
|
912
|
-
container.bind(
|
|
913
|
-
container.bind(
|
|
914
|
-
container.bind(
|
|
915
|
-
container.bind(
|
|
916
|
-
tool = container.get(
|
|
915
|
+
container.bind(ILogger).to(MockLogger);
|
|
916
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
917
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
918
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
919
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
920
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
921
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
922
|
+
container.bind(GetWorkspaceFileList).toSelf();
|
|
923
|
+
tool = container.get(GetWorkspaceFileList);
|
|
917
924
|
});
|
|
918
925
|
it('lists one level and resolves the directory only once', async () => {
|
|
919
926
|
const result = JSON.parse(await tool.getTool().handler(JSON.stringify({ path: 'workspace/sub' }), undefined));
|
|
920
|
-
|
|
921
|
-
|
|
927
|
+
expect(result).to.deep.equal({ 'a.ts': 'file', 'dir': 'directory' });
|
|
928
|
+
expect(resolveCount).to.equal(1);
|
|
922
929
|
});
|
|
923
930
|
});
|
|
924
931
|
describe('GetWorkspaceDirectoryStructure preserves empty folders', () => {
|
|
@@ -932,13 +939,13 @@ describe('GetWorkspaceDirectoryStructure preserves empty folders', () => {
|
|
|
932
939
|
'file:///workspace/node_modules': ['file:///workspace/node_modules/pkg']
|
|
933
940
|
};
|
|
934
941
|
let disableJSDOMInner;
|
|
935
|
-
before(() => { disableJSDOMInner =
|
|
942
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
936
943
|
after(() => { disableJSDOMInner(); });
|
|
937
944
|
beforeEach(() => {
|
|
938
|
-
container = new
|
|
945
|
+
container = new Container();
|
|
939
946
|
const mockWorkspaceService = {
|
|
940
|
-
roots: Promise.resolve([{ resource: new
|
|
941
|
-
tryGetRoots: () => [{ resource: new
|
|
947
|
+
roots: Promise.resolve([{ resource: new URI('file:///workspace') }]),
|
|
948
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
942
949
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
943
950
|
};
|
|
944
951
|
const mockFileService = {
|
|
@@ -947,7 +954,7 @@ describe('GetWorkspaceDirectoryStructure preserves empty folders', () => {
|
|
|
947
954
|
resource: uri,
|
|
948
955
|
children: (TREE[uri.toString()] ?? []).map(child => ({
|
|
949
956
|
isDirectory: true,
|
|
950
|
-
resource: new
|
|
957
|
+
resource: new URI(child),
|
|
951
958
|
path: { base: child.split('/').pop() }
|
|
952
959
|
}))
|
|
953
960
|
}),
|
|
@@ -958,19 +965,19 @@ describe('GetWorkspaceDirectoryStructure preserves empty folders', () => {
|
|
|
958
965
|
const mockPreferenceService = {
|
|
959
966
|
get: (path, def) => (path === 'ai-features.workspaceFunctions.userExcludes' ? ['node_modules'] : def)
|
|
960
967
|
};
|
|
961
|
-
container.bind(
|
|
962
|
-
container.bind(
|
|
963
|
-
container.bind(
|
|
964
|
-
container.bind(
|
|
965
|
-
container.bind(
|
|
966
|
-
container.bind(
|
|
967
|
-
container.bind(
|
|
968
|
-
container.bind(
|
|
969
|
-
tool = container.get(
|
|
968
|
+
container.bind(ILogger).to(MockLogger);
|
|
969
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
970
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
971
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
972
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
973
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
974
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
975
|
+
container.bind(GetWorkspaceDirectoryStructure).toSelf();
|
|
976
|
+
tool = container.get(GetWorkspaceDirectoryStructure);
|
|
970
977
|
});
|
|
971
978
|
it('includes empty directories and excludes user-excluded ones', async () => {
|
|
972
979
|
const result = await tool.getTool().handler(JSON.stringify({}), undefined);
|
|
973
|
-
|
|
980
|
+
expect(result).to.deep.equal({
|
|
974
981
|
workspace: {
|
|
975
982
|
src: { nested: {} },
|
|
976
983
|
empty: {}
|
|
@@ -985,15 +992,15 @@ describe('FileContentFunction external paths', () => {
|
|
|
985
992
|
let allowedPaths;
|
|
986
993
|
let trustedScopeOnly;
|
|
987
994
|
let disableJSDOMInner;
|
|
988
|
-
before(() => { disableJSDOMInner =
|
|
995
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
989
996
|
after(() => { disableJSDOMInner(); });
|
|
990
997
|
beforeEach(() => {
|
|
991
|
-
container = new
|
|
998
|
+
container = new Container();
|
|
992
999
|
allowedPaths = [];
|
|
993
1000
|
trustedScopeOnly = false;
|
|
994
1001
|
const mockWorkspaceService = {
|
|
995
|
-
roots: [{ resource: new
|
|
996
|
-
tryGetRoots: () => [{ resource: new
|
|
1002
|
+
roots: [{ resource: new URI('file:///workspace') }],
|
|
1003
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
997
1004
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
998
1005
|
};
|
|
999
1006
|
const mockFileService = {
|
|
@@ -1018,72 +1025,72 @@ describe('FileContentFunction external paths', () => {
|
|
|
1018
1025
|
ready: Promise.resolve(),
|
|
1019
1026
|
onDidChangeTrust: () => ({ dispose: () => { } })
|
|
1020
1027
|
};
|
|
1021
|
-
container.bind(
|
|
1022
|
-
container.bind(
|
|
1023
|
-
container.bind(
|
|
1024
|
-
container.bind(
|
|
1025
|
-
container.bind(
|
|
1026
|
-
container.bind(
|
|
1027
|
-
container.bind(
|
|
1028
|
-
container.bind(
|
|
1029
|
-
container.bind(
|
|
1030
|
-
fileContentFunction = container.get(
|
|
1028
|
+
container.bind(ILogger).to(MockLogger);
|
|
1029
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1030
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
1031
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
1032
|
+
container.bind(MonacoWorkspace).toConstantValue(mockMonacoWorkspace);
|
|
1033
|
+
container.bind(AiConfigurationService).toConstantValue(trustAwareReader);
|
|
1034
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer('file:///home/test'));
|
|
1035
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1036
|
+
container.bind(FileContentFunction).toSelf();
|
|
1037
|
+
fileContentFunction = container.get(FileContentFunction);
|
|
1031
1038
|
});
|
|
1032
1039
|
const callTool = (file) => fileContentFunction.getTool().handler(JSON.stringify({ file }), undefined);
|
|
1033
1040
|
it('rejects absolute path outside the workspace when allow-list is empty', async () => {
|
|
1034
1041
|
const result = await callTool('/etc/hosts');
|
|
1035
1042
|
const parsed = JSON.parse(result);
|
|
1036
|
-
|
|
1037
|
-
|
|
1043
|
+
expect(parsed.error).to.include('not allowed');
|
|
1044
|
+
expect(parsed.error).to.include('allowedExternalPaths');
|
|
1038
1045
|
});
|
|
1039
1046
|
it('allows absolute path inside an allow-listed directory', async () => {
|
|
1040
1047
|
allowedPaths = ['/external/configs'];
|
|
1041
1048
|
const result = await callTool('/external/configs/myapp.json');
|
|
1042
|
-
|
|
1049
|
+
expect(result).to.equal('content-of-/external/configs/myapp.json');
|
|
1043
1050
|
});
|
|
1044
1051
|
it('allows file:// URI inside an allow-listed directory', async () => {
|
|
1045
1052
|
allowedPaths = ['file:///external/configs'];
|
|
1046
1053
|
const result = await callTool('file:///external/configs/myapp.json');
|
|
1047
|
-
|
|
1054
|
+
expect(result).to.equal('content-of-/external/configs/myapp.json');
|
|
1048
1055
|
});
|
|
1049
1056
|
it('rejects sibling that shares the prefix but is not a child', async () => {
|
|
1050
1057
|
// /external/configs-other must NOT be matched by /external/configs
|
|
1051
1058
|
allowedPaths = ['/external/configs'];
|
|
1052
1059
|
const result = await callTool('/external/configs-other/myapp.json');
|
|
1053
1060
|
const parsed = JSON.parse(result);
|
|
1054
|
-
|
|
1061
|
+
expect(parsed.error).to.include('not allowed');
|
|
1055
1062
|
});
|
|
1056
1063
|
it('rejects path with .. traversal', async () => {
|
|
1057
1064
|
allowedPaths = ['/external/configs'];
|
|
1058
1065
|
const result = await callTool('/external/configs/../secrets.txt');
|
|
1059
1066
|
const parsed = JSON.parse(result);
|
|
1060
|
-
|
|
1067
|
+
expect(parsed.error).to.include('Invalid path');
|
|
1061
1068
|
});
|
|
1062
1069
|
it('expands ~ in allow-list entries', async () => {
|
|
1063
1070
|
allowedPaths = ['~/configs'];
|
|
1064
1071
|
const result = await callTool('/home/test/configs/myapp.json');
|
|
1065
|
-
|
|
1072
|
+
expect(result).to.equal('content-of-/home/test/configs/myapp.json');
|
|
1066
1073
|
});
|
|
1067
1074
|
it('expands ~ in tool input paths', async () => {
|
|
1068
1075
|
allowedPaths = ['/home/test/configs'];
|
|
1069
1076
|
const result = await callTool('~/configs/myapp.json');
|
|
1070
|
-
|
|
1077
|
+
expect(result).to.equal('content-of-/home/test/configs/myapp.json');
|
|
1071
1078
|
});
|
|
1072
1079
|
it('expands ~ in both allow-list entries and tool input paths', async () => {
|
|
1073
1080
|
allowedPaths = ['~/configs'];
|
|
1074
1081
|
const result = await callTool('~/configs/myapp.json');
|
|
1075
|
-
|
|
1082
|
+
expect(result).to.equal('content-of-/home/test/configs/myapp.json');
|
|
1076
1083
|
});
|
|
1077
1084
|
it('rejects ~-prefixed tool input that escapes via .. traversal', async () => {
|
|
1078
1085
|
allowedPaths = ['/home/test/configs'];
|
|
1079
1086
|
const result = await callTool('~/configs/../secrets.txt');
|
|
1080
1087
|
const parsed = JSON.parse(result);
|
|
1081
|
-
|
|
1088
|
+
expect(parsed.error).to.include('Invalid path');
|
|
1082
1089
|
});
|
|
1083
1090
|
it('still allows workspace-relative paths', async () => {
|
|
1084
1091
|
allowedPaths = [];
|
|
1085
1092
|
const result = await callTool('src/index.ts');
|
|
1086
|
-
|
|
1093
|
+
expect(result).to.equal('content-of-/workspace/src/index.ts');
|
|
1087
1094
|
});
|
|
1088
1095
|
it('accepts Windows-style absolute paths in the allow-list', async () => {
|
|
1089
1096
|
// Mixed: backslash separators in the entry, forward-slash in the file argument
|
|
@@ -1091,21 +1098,21 @@ describe('FileContentFunction external paths', () => {
|
|
|
1091
1098
|
const result = await callTool('C:/external/configs/myapp.json');
|
|
1092
1099
|
// The mock returns content keyed off the resolved URI's path. Both forms
|
|
1093
1100
|
// normalize to the same `file:///c:/external/configs/myapp.json`.
|
|
1094
|
-
|
|
1095
|
-
|
|
1101
|
+
expect(result).to.match(/^content-of-/);
|
|
1102
|
+
expect(result).to.include('external/configs/myapp.json');
|
|
1096
1103
|
});
|
|
1097
1104
|
it('accepts Windows drive paths as the tool argument', async () => {
|
|
1098
1105
|
allowedPaths = ['C:/external'];
|
|
1099
1106
|
const result = await callTool('C:\\external\\file.txt');
|
|
1100
|
-
|
|
1101
|
-
|
|
1107
|
+
expect(result).to.match(/^content-of-/);
|
|
1108
|
+
expect(result).to.include('external/file.txt');
|
|
1102
1109
|
});
|
|
1103
1110
|
it('drops workspace-scoped allow-list values when workspace is untrusted', async () => {
|
|
1104
1111
|
// simulate trust-aware reader behavior: when untrusted, only user/default scope is returned
|
|
1105
1112
|
trustedScopeOnly = true;
|
|
1106
1113
|
const result = await callTool('/external/configs/myapp.json');
|
|
1107
1114
|
const parsed = JSON.parse(result);
|
|
1108
|
-
|
|
1115
|
+
expect(parsed.error).to.include('not allowed');
|
|
1109
1116
|
});
|
|
1110
1117
|
});
|
|
1111
1118
|
describe('WorkspaceFunctionScope accessible root contributions', () => {
|
|
@@ -1115,17 +1122,17 @@ describe('WorkspaceFunctionScope accessible root contributions', () => {
|
|
|
1115
1122
|
let contributionFails;
|
|
1116
1123
|
let rootQueries;
|
|
1117
1124
|
let disableJSDOMInner;
|
|
1118
|
-
before(() => { disableJSDOMInner =
|
|
1125
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
1119
1126
|
after(() => { disableJSDOMInner(); });
|
|
1120
1127
|
beforeEach(() => {
|
|
1121
|
-
container = new
|
|
1122
|
-
container.bind(
|
|
1128
|
+
container = new Container();
|
|
1129
|
+
container.bind(ILogger).to(MockLogger);
|
|
1123
1130
|
contributedRoots = [];
|
|
1124
1131
|
contributionFails = false;
|
|
1125
1132
|
rootQueries = 0;
|
|
1126
1133
|
const mockWorkspaceService = {
|
|
1127
|
-
roots: [{ resource: new
|
|
1128
|
-
tryGetRoots: () => [{ resource: new
|
|
1134
|
+
roots: [{ resource: new URI('file:///workspace') }],
|
|
1135
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
1129
1136
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1130
1137
|
};
|
|
1131
1138
|
const mockFileService = {
|
|
@@ -1142,52 +1149,52 @@ describe('WorkspaceFunctionScope accessible root contributions', () => {
|
|
|
1142
1149
|
return contributedRoots;
|
|
1143
1150
|
}
|
|
1144
1151
|
};
|
|
1145
|
-
container.bind(
|
|
1146
|
-
container.bind(
|
|
1147
|
-
container.bind(
|
|
1148
|
-
container.bind(
|
|
1149
|
-
container.bind(
|
|
1150
|
-
container.bind(
|
|
1151
|
-
|
|
1152
|
-
container.bind(
|
|
1153
|
-
container.bind(
|
|
1154
|
-
container.bind(
|
|
1155
|
-
fileContentFunction = container.get(
|
|
1152
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1153
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
1154
|
+
container.bind(PreferenceService).toConstantValue({ get: (_path, defaultValue) => defaultValue });
|
|
1155
|
+
container.bind(MonacoWorkspace).toConstantValue({ getTextDocument: () => undefined });
|
|
1156
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1157
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1158
|
+
bindRootContributionProvider(container, AccessibleRootContribution);
|
|
1159
|
+
container.bind(AccessibleRootContribution).toConstantValue(contribution);
|
|
1160
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1161
|
+
container.bind(FileContentFunction).toSelf();
|
|
1162
|
+
fileContentFunction = container.get(FileContentFunction);
|
|
1156
1163
|
});
|
|
1157
1164
|
const callTool = (file) => fileContentFunction.getTool().handler(JSON.stringify({ file }), undefined);
|
|
1158
1165
|
it('allows a file under a contributed root although the allow-list is empty', async () => {
|
|
1159
|
-
contributedRoots = [new
|
|
1166
|
+
contributedRoots = [new URI('file:///config/workspace-metadata/uuid/memory')];
|
|
1160
1167
|
const result = await callTool('/config/workspace-metadata/uuid/memory/wiki/index.md');
|
|
1161
|
-
|
|
1168
|
+
expect(result).to.equal('content-of-/config/workspace-metadata/uuid/memory/wiki/index.md');
|
|
1162
1169
|
});
|
|
1163
1170
|
it('rejects a sibling that only shares the contributed root\'s prefix', async () => {
|
|
1164
|
-
contributedRoots = [new
|
|
1171
|
+
contributedRoots = [new URI('file:///config/memory')];
|
|
1165
1172
|
const parsed = JSON.parse(await callTool('/config/memory-other/wiki/index.md'));
|
|
1166
|
-
|
|
1173
|
+
expect(parsed.error).to.include('not allowed');
|
|
1167
1174
|
});
|
|
1168
1175
|
it('matches a contributed root that carries a trailing separator', async () => {
|
|
1169
|
-
contributedRoots = [new
|
|
1176
|
+
contributedRoots = [new URI('file:///config/memory/')];
|
|
1170
1177
|
const result = await callTool('/config/memory/wiki/index.md');
|
|
1171
|
-
|
|
1178
|
+
expect(result).to.equal('content-of-/config/memory/wiki/index.md');
|
|
1172
1179
|
});
|
|
1173
1180
|
it('re-queries the contributions, so a root that moves needs no invalidation', async () => {
|
|
1174
|
-
contributedRoots = [new
|
|
1175
|
-
|
|
1176
|
-
contributedRoots = [new
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1181
|
+
contributedRoots = [new URI('file:///config/first/memory')];
|
|
1182
|
+
expect(await callTool('/config/first/memory/notes.md')).to.match(/^content-of-/);
|
|
1183
|
+
contributedRoots = [new URI('file:///config/second/memory')];
|
|
1184
|
+
expect(JSON.parse(await callTool('/config/first/memory/notes.md')).error).to.include('not allowed');
|
|
1185
|
+
expect(await callTool('/config/second/memory/notes.md')).to.match(/^content-of-/);
|
|
1186
|
+
expect(rootQueries).to.equal(3);
|
|
1180
1187
|
});
|
|
1181
1188
|
it('keeps checking the other sources when a contribution fails', async () => {
|
|
1182
1189
|
contributionFails = true;
|
|
1183
1190
|
const parsed = JSON.parse(await callTool('/config/memory/wiki/index.md'));
|
|
1184
|
-
|
|
1191
|
+
expect(parsed.error).to.include('not allowed');
|
|
1185
1192
|
// The workspace itself is unaffected by the broken contribution.
|
|
1186
|
-
|
|
1193
|
+
expect(await callTool('src/index.ts')).to.equal('content-of-/workspace/src/index.ts');
|
|
1187
1194
|
});
|
|
1188
1195
|
it('is not consulted for paths inside the workspace', async () => {
|
|
1189
|
-
|
|
1190
|
-
|
|
1196
|
+
expect(await callTool('src/index.ts')).to.equal('content-of-/workspace/src/index.ts');
|
|
1197
|
+
expect(rootQueries).to.equal(0);
|
|
1191
1198
|
});
|
|
1192
1199
|
});
|
|
1193
1200
|
describe('GetWorkspaceFileList / GetWorkspaceDirectoryStructure with external paths', () => {
|
|
@@ -1205,14 +1212,14 @@ describe('GetWorkspaceFileList / GetWorkspaceDirectoryStructure with external pa
|
|
|
1205
1212
|
'file:///external/configs/sub': { isDirectory: true, children: [] }
|
|
1206
1213
|
};
|
|
1207
1214
|
let disableJSDOMInner;
|
|
1208
|
-
before(() => { disableJSDOMInner =
|
|
1215
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
1209
1216
|
after(() => { disableJSDOMInner(); });
|
|
1210
1217
|
beforeEach(() => {
|
|
1211
|
-
container = new
|
|
1218
|
+
container = new Container();
|
|
1212
1219
|
allowedPaths = [];
|
|
1213
1220
|
const mockWorkspaceService = {
|
|
1214
|
-
roots: [{ resource: new
|
|
1215
|
-
tryGetRoots: () => [{ resource: new
|
|
1221
|
+
roots: [{ resource: new URI('file:///workspace') }],
|
|
1222
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
1216
1223
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1217
1224
|
};
|
|
1218
1225
|
const mockFileService = {
|
|
@@ -1229,7 +1236,7 @@ describe('GetWorkspaceFileList / GetWorkspaceDirectoryStructure with external pa
|
|
|
1229
1236
|
children: node.children?.map(child => ({
|
|
1230
1237
|
isDirectory: !!FS_TREE[child]?.isDirectory,
|
|
1231
1238
|
isFile: !FS_TREE[child]?.isDirectory,
|
|
1232
|
-
resource: new
|
|
1239
|
+
resource: new URI(child),
|
|
1233
1240
|
path: { base: child.split('/').pop() }
|
|
1234
1241
|
})) ?? []
|
|
1235
1242
|
};
|
|
@@ -1244,48 +1251,48 @@ describe('GetWorkspaceFileList / GetWorkspaceDirectoryStructure with external pa
|
|
|
1244
1251
|
ready: Promise.resolve(),
|
|
1245
1252
|
onDidChangeTrust: () => ({ dispose: () => { } })
|
|
1246
1253
|
};
|
|
1247
|
-
container.bind(
|
|
1248
|
-
container.bind(
|
|
1249
|
-
container.bind(
|
|
1250
|
-
container.bind(
|
|
1251
|
-
container.bind(
|
|
1252
|
-
container.bind(
|
|
1253
|
-
container.bind(
|
|
1254
|
-
container.bind(
|
|
1255
|
-
container.bind(
|
|
1254
|
+
container.bind(ILogger).to(MockLogger);
|
|
1255
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1256
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
1257
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
1258
|
+
container.bind(AiConfigurationService).toConstantValue(trustAwareReader);
|
|
1259
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1260
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1261
|
+
container.bind(GetWorkspaceFileList).toSelf();
|
|
1262
|
+
container.bind(GetWorkspaceDirectoryStructure).toSelf();
|
|
1256
1263
|
});
|
|
1257
1264
|
it('GetWorkspaceFileList rejects absolute path outside the allow-list', async () => {
|
|
1258
|
-
const tool = container.get(
|
|
1265
|
+
const tool = container.get(GetWorkspaceFileList).getTool();
|
|
1259
1266
|
const result = await tool.handler(JSON.stringify({ path: '/external/configs' }), undefined);
|
|
1260
1267
|
const parsed = JSON.parse(result);
|
|
1261
|
-
|
|
1268
|
+
expect(parsed.error).to.include('not allowed');
|
|
1262
1269
|
});
|
|
1263
1270
|
it('GetWorkspaceFileList lists an allow-listed external directory', async () => {
|
|
1264
1271
|
allowedPaths = ['/external/configs'];
|
|
1265
|
-
const tool = container.get(
|
|
1272
|
+
const tool = container.get(GetWorkspaceFileList).getTool();
|
|
1266
1273
|
const result = await tool.handler(JSON.stringify({ path: '/external/configs' }), undefined);
|
|
1267
|
-
|
|
1274
|
+
expect(JSON.parse(result)).to.deep.equal({ 'myapp.json': 'file', 'sub': 'directory' });
|
|
1268
1275
|
});
|
|
1269
1276
|
it('GetWorkspaceFileList still lists workspace-relative paths', async () => {
|
|
1270
|
-
const tool = container.get(
|
|
1277
|
+
const tool = container.get(GetWorkspaceFileList).getTool();
|
|
1271
1278
|
const result = await tool.handler(JSON.stringify({ path: '' }), undefined);
|
|
1272
|
-
|
|
1279
|
+
expect(JSON.parse(result)).to.deep.equal({ workspace: 'directory' });
|
|
1273
1280
|
});
|
|
1274
1281
|
it('GetWorkspaceDirectoryStructure rejects external root not in the allow-list', async () => {
|
|
1275
|
-
const tool = container.get(
|
|
1282
|
+
const tool = container.get(GetWorkspaceDirectoryStructure).getTool();
|
|
1276
1283
|
const result = await tool.handler(JSON.stringify({ root: '/external/configs' }), undefined);
|
|
1277
|
-
|
|
1284
|
+
expect(result.error).to.include('not allowed');
|
|
1278
1285
|
});
|
|
1279
1286
|
it('GetWorkspaceDirectoryStructure walks an allow-listed external root', async () => {
|
|
1280
1287
|
allowedPaths = ['/external/configs'];
|
|
1281
|
-
const tool = container.get(
|
|
1288
|
+
const tool = container.get(GetWorkspaceDirectoryStructure).getTool();
|
|
1282
1289
|
const result = await tool.handler(JSON.stringify({ root: '/external/configs' }), undefined);
|
|
1283
|
-
|
|
1290
|
+
expect(result).to.deep.equal({ sub: {} });
|
|
1284
1291
|
});
|
|
1285
1292
|
it('GetWorkspaceDirectoryStructure preserves prior workspace-only behavior when root omitted', async () => {
|
|
1286
|
-
const tool = container.get(
|
|
1293
|
+
const tool = container.get(GetWorkspaceDirectoryStructure).getTool();
|
|
1287
1294
|
const result = await tool.handler('', undefined);
|
|
1288
|
-
|
|
1295
|
+
expect(result).to.deep.equal({ workspace: { sub: {} } });
|
|
1289
1296
|
});
|
|
1290
1297
|
});
|
|
1291
1298
|
describe('FindFilesByPattern with searchRoot', () => {
|
|
@@ -1297,22 +1304,22 @@ describe('FindFilesByPattern with searchRoot', () => {
|
|
|
1297
1304
|
'file:///external/configs': ['file:///external/configs/myapp.json', 'file:///external/configs/other.txt']
|
|
1298
1305
|
};
|
|
1299
1306
|
let disableJSDOMInner;
|
|
1300
|
-
before(() => { disableJSDOMInner =
|
|
1307
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
1301
1308
|
after(() => { disableJSDOMInner(); });
|
|
1302
1309
|
// External hits are rendered with `Path.fsPath()`, which branches on the backend OS, so the POSIX
|
|
1303
1310
|
// fixtures below pin it instead of inheriting the OS the tests happen to run on.
|
|
1304
1311
|
let originalIsWindows;
|
|
1305
1312
|
beforeEach(() => {
|
|
1306
|
-
originalIsWindows =
|
|
1307
|
-
|
|
1313
|
+
originalIsWindows = OS.backend.isWindows;
|
|
1314
|
+
OS.backend.isWindows = false;
|
|
1308
1315
|
});
|
|
1309
|
-
afterEach(() => {
|
|
1316
|
+
afterEach(() => { OS.backend.isWindows = originalIsWindows; });
|
|
1310
1317
|
beforeEach(() => {
|
|
1311
|
-
container = new
|
|
1318
|
+
container = new Container();
|
|
1312
1319
|
allowedPaths = [];
|
|
1313
1320
|
const mockWorkspaceService = {
|
|
1314
|
-
roots: [{ resource: new
|
|
1315
|
-
tryGetRoots: () => [{ resource: new
|
|
1321
|
+
roots: [{ resource: new URI('file:///workspace') }],
|
|
1322
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace') }],
|
|
1316
1323
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1317
1324
|
};
|
|
1318
1325
|
const mockFileService = {
|
|
@@ -1328,33 +1335,33 @@ describe('FindFilesByPattern with searchRoot', () => {
|
|
|
1328
1335
|
ready: Promise.resolve(),
|
|
1329
1336
|
onDidChangeTrust: () => ({ dispose: () => { } })
|
|
1330
1337
|
};
|
|
1331
|
-
container.bind(
|
|
1332
|
-
container.bind(
|
|
1333
|
-
container.bind(
|
|
1334
|
-
container.bind(
|
|
1335
|
-
container.bind(
|
|
1336
|
-
container.bind(
|
|
1337
|
-
container.bind(
|
|
1338
|
-
container.bind(
|
|
1339
|
-
container.bind(
|
|
1340
|
-
findFilesByPattern = container.get(
|
|
1338
|
+
container.bind(ILogger).to(MockLogger);
|
|
1339
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1340
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
1341
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
1342
|
+
container.bind(AiConfigurationService).toConstantValue(trustAwareReader);
|
|
1343
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1344
|
+
container.bind(FileSearchService).toConstantValue(makeRipgrepLikeSearchService(FILES_BY_ROOT));
|
|
1345
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1346
|
+
container.bind(FindFilesByPattern).toSelf();
|
|
1347
|
+
findFilesByPattern = container.get(FindFilesByPattern);
|
|
1341
1348
|
});
|
|
1342
1349
|
const callTool = (args) => findFilesByPattern.getTool().handler(JSON.stringify(args), undefined);
|
|
1343
1350
|
it('searches the workspace by default and returns relative paths', async () => {
|
|
1344
1351
|
const result = await callTool({ pattern: '**/*.ts' });
|
|
1345
1352
|
const parsed = JSON.parse(result);
|
|
1346
|
-
|
|
1353
|
+
expect(parsed.files).to.deep.equal(['workspace/a.ts']);
|
|
1347
1354
|
});
|
|
1348
1355
|
it('rejects searchRoot that is not in the allow-list', async () => {
|
|
1349
1356
|
const result = await callTool({ pattern: '**/*.json', searchRoot: '/external/configs' });
|
|
1350
1357
|
const parsed = JSON.parse(result);
|
|
1351
|
-
|
|
1358
|
+
expect(parsed.error).to.include('not allowed');
|
|
1352
1359
|
});
|
|
1353
1360
|
it('searches an allow-listed external root and returns absolute paths', async () => {
|
|
1354
1361
|
allowedPaths = ['/external/configs'];
|
|
1355
1362
|
const result = await callTool({ pattern: '**/*.json', searchRoot: '/external/configs' });
|
|
1356
1363
|
const parsed = JSON.parse(result);
|
|
1357
|
-
|
|
1364
|
+
expect(parsed.files).to.deep.equal(['/external/configs/myapp.json']);
|
|
1358
1365
|
});
|
|
1359
1366
|
it('honors exclude patterns when using an external searchRoot', async () => {
|
|
1360
1367
|
allowedPaths = ['/external/configs'];
|
|
@@ -1364,7 +1371,7 @@ describe('FindFilesByPattern with searchRoot', () => {
|
|
|
1364
1371
|
searchRoot: '/external/configs'
|
|
1365
1372
|
});
|
|
1366
1373
|
const parsed = JSON.parse(result);
|
|
1367
|
-
|
|
1374
|
+
expect(parsed.files).to.deep.equal(['/external/configs/myapp.json']);
|
|
1368
1375
|
});
|
|
1369
1376
|
});
|
|
1370
1377
|
describe('WorkspaceFunctionScope path-traversal hardening', () => {
|
|
@@ -1374,16 +1381,16 @@ describe('WorkspaceFunctionScope path-traversal hardening', () => {
|
|
|
1374
1381
|
let trustReady;
|
|
1375
1382
|
let trustAwareGet;
|
|
1376
1383
|
let disableJSDOMInner;
|
|
1377
|
-
before(() => { disableJSDOMInner =
|
|
1384
|
+
before(() => { disableJSDOMInner = enableJSDOM(); });
|
|
1378
1385
|
after(() => { disableJSDOMInner(); });
|
|
1379
1386
|
beforeEach(() => {
|
|
1380
|
-
container = new
|
|
1387
|
+
container = new Container();
|
|
1381
1388
|
allowedPaths = [];
|
|
1382
1389
|
trustReady = Promise.resolve();
|
|
1383
1390
|
trustAwareGet = (_name, _fallback) => allowedPaths;
|
|
1384
1391
|
const mockWorkspaceService = {
|
|
1385
|
-
roots: [{ resource: new
|
|
1386
|
-
tryGetRoots: () => [{ resource: new
|
|
1392
|
+
roots: [{ resource: new URI('file:///workspace/project') }],
|
|
1393
|
+
tryGetRoots: () => [{ resource: new URI('file:///workspace/project') }],
|
|
1387
1394
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1388
1395
|
};
|
|
1389
1396
|
const mockFileService = {
|
|
@@ -1408,29 +1415,29 @@ describe('WorkspaceFunctionScope path-traversal hardening', () => {
|
|
|
1408
1415
|
get ready() { return trustReady; },
|
|
1409
1416
|
onDidChangeTrust: () => ({ dispose: () => { } })
|
|
1410
1417
|
};
|
|
1411
|
-
container.bind(
|
|
1412
|
-
container.bind(
|
|
1413
|
-
container.bind(
|
|
1414
|
-
container.bind(
|
|
1415
|
-
container.bind(
|
|
1416
|
-
container.bind(
|
|
1417
|
-
container.bind(
|
|
1418
|
-
container.bind(
|
|
1419
|
-
container.bind(
|
|
1420
|
-
container.bind(
|
|
1418
|
+
container.bind(ILogger).to(MockLogger);
|
|
1419
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1420
|
+
container.bind(FileService).toConstantValue(mockFileService);
|
|
1421
|
+
container.bind(PreferenceService).toConstantValue(mockPreferenceService);
|
|
1422
|
+
container.bind(MonacoWorkspace).toConstantValue(mockMonacoWorkspace);
|
|
1423
|
+
container.bind(AiConfigurationService).toConstantValue(trustAwareReader);
|
|
1424
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer('file:///home/test'));
|
|
1425
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1426
|
+
container.bind(FileContentFunction).toSelf();
|
|
1427
|
+
container.bind(FileDiagnosticProvider).toSelf();
|
|
1421
1428
|
const mockProblemManager = {
|
|
1422
1429
|
findMarkers: () => [],
|
|
1423
1430
|
onDidChangeMarkers: () => ({ dispose: () => { } })
|
|
1424
1431
|
};
|
|
1425
|
-
container.bind(
|
|
1432
|
+
container.bind(ProblemManager).toConstantValue(mockProblemManager);
|
|
1426
1433
|
const mockMonacoTextModelService = {
|
|
1427
1434
|
createModelReference: async () => ({
|
|
1428
1435
|
object: { lineCount: 10, getText: () => '' },
|
|
1429
1436
|
dispose: () => { }
|
|
1430
1437
|
})
|
|
1431
1438
|
};
|
|
1432
|
-
container.bind(
|
|
1433
|
-
fileContentFunction = container.get(
|
|
1439
|
+
container.bind(MonacoTextModelService).toConstantValue(mockMonacoTextModelService);
|
|
1440
|
+
fileContentFunction = container.get(FileContentFunction);
|
|
1434
1441
|
});
|
|
1435
1442
|
const callContent = (file) => fileContentFunction.getTool().handler(JSON.stringify({ file }), undefined);
|
|
1436
1443
|
// Item 1 — URL-encoded and literal `..` in file:// URIs must be normalized
|
|
@@ -1440,33 +1447,33 @@ describe('WorkspaceFunctionScope path-traversal hardening', () => {
|
|
|
1440
1447
|
allowedPaths = ['/external/configs'];
|
|
1441
1448
|
const result = await callContent('file:///external/configs/../secrets.txt');
|
|
1442
1449
|
const parsed = JSON.parse(result);
|
|
1443
|
-
|
|
1450
|
+
expect(parsed.error).to.include('not allowed');
|
|
1444
1451
|
});
|
|
1445
1452
|
it('rejects file:// URI with percent-encoded %2e%2e traversal', async () => {
|
|
1446
1453
|
allowedPaths = ['/external/configs'];
|
|
1447
1454
|
const result = await callContent('file:///external/configs/%2e%2e/secrets.txt');
|
|
1448
1455
|
const parsed = JSON.parse(result);
|
|
1449
|
-
|
|
1456
|
+
expect(parsed.error).to.include('not allowed');
|
|
1450
1457
|
});
|
|
1451
1458
|
it('rejects file:// URI with upper-case percent-encoded traversal', async () => {
|
|
1452
1459
|
allowedPaths = ['/external/configs'];
|
|
1453
1460
|
const result = await callContent('file:///external/configs/%2E%2E/secrets.txt');
|
|
1454
1461
|
const parsed = JSON.parse(result);
|
|
1455
|
-
|
|
1462
|
+
expect(parsed.error).to.include('not allowed');
|
|
1456
1463
|
});
|
|
1457
1464
|
// Item 4 — A filename that contains `..` as a substring but is not a
|
|
1458
1465
|
// traversal segment must be accepted.
|
|
1459
1466
|
it('accepts a valid filename whose name contains ".." as a substring', async () => {
|
|
1460
1467
|
allowedPaths = ['/external/configs'];
|
|
1461
1468
|
const result = await callContent('/external/configs/my..file.json');
|
|
1462
|
-
|
|
1469
|
+
expect(result).to.equal('content-of-/external/configs/my..file.json');
|
|
1463
1470
|
});
|
|
1464
1471
|
// Item 3 — Non-file allow-list entries are documented as unsupported.
|
|
1465
1472
|
it('rejects http:// allow-list entry even if target path appears to match', async () => {
|
|
1466
1473
|
allowedPaths = ['http://example.com/external/configs'];
|
|
1467
1474
|
const result = await callContent('/external/configs/myapp.json');
|
|
1468
1475
|
const parsed = JSON.parse(result);
|
|
1469
|
-
|
|
1476
|
+
expect(parsed.error).to.include('not allowed');
|
|
1470
1477
|
});
|
|
1471
1478
|
// Item 7 — getAllowedExternalUris must await AiConfigurationService.ready
|
|
1472
1479
|
// so that preference reads see the resolved trust state.
|
|
@@ -1482,7 +1489,7 @@ describe('WorkspaceFunctionScope path-traversal hardening', () => {
|
|
|
1482
1489
|
preferenceVisible = true;
|
|
1483
1490
|
resolveReady();
|
|
1484
1491
|
const result = await promise;
|
|
1485
|
-
|
|
1492
|
+
expect(result).to.equal('content-of-/external/configs/myapp.json');
|
|
1486
1493
|
});
|
|
1487
1494
|
// Item 2 — On case-insensitive filesystems (Windows, macOS by default)
|
|
1488
1495
|
// the allow-list match must be case-insensitive. Stub OS.backend flags
|
|
@@ -1491,51 +1498,51 @@ describe('WorkspaceFunctionScope path-traversal hardening', () => {
|
|
|
1491
1498
|
let originalIsWindows;
|
|
1492
1499
|
let originalIsOSX;
|
|
1493
1500
|
beforeEach(() => {
|
|
1494
|
-
originalIsWindows =
|
|
1495
|
-
originalIsOSX =
|
|
1501
|
+
originalIsWindows = OS.backend.isWindows;
|
|
1502
|
+
originalIsOSX = OS.backend.isOSX;
|
|
1496
1503
|
});
|
|
1497
1504
|
afterEach(() => {
|
|
1498
|
-
|
|
1499
|
-
|
|
1505
|
+
OS.backend.isWindows = originalIsWindows;
|
|
1506
|
+
OS.backend.isOSX = originalIsOSX;
|
|
1500
1507
|
});
|
|
1501
1508
|
it('admits a case-mismatched Windows target when allow-list entry is mixed-case', async () => {
|
|
1502
|
-
|
|
1503
|
-
|
|
1509
|
+
OS.backend.isWindows = true;
|
|
1510
|
+
OS.backend.isOSX = false;
|
|
1504
1511
|
allowedPaths = ['C:\\External\\Configs'];
|
|
1505
1512
|
const result = await callContent('c:/external/configs/myapp.json');
|
|
1506
1513
|
// The mock returns the URI's path; both forms canonicalize to the
|
|
1507
1514
|
// same file:///c:/... URI (drive letter is lowercased by URI.fromFilePath),
|
|
1508
1515
|
// but the path segments differ in case.
|
|
1509
|
-
|
|
1510
|
-
|
|
1516
|
+
expect(result).to.match(/^content-of-/);
|
|
1517
|
+
expect(result.toLowerCase()).to.include('external/configs/myapp.json');
|
|
1511
1518
|
});
|
|
1512
1519
|
it('admits a case-mismatched macOS target when allow-list entry is mixed-case', async () => {
|
|
1513
|
-
|
|
1514
|
-
|
|
1520
|
+
OS.backend.isWindows = false;
|
|
1521
|
+
OS.backend.isOSX = true;
|
|
1515
1522
|
allowedPaths = ['/Users/safi/External/Configs'];
|
|
1516
1523
|
const result = await callContent('/users/safi/external/configs/myapp.json');
|
|
1517
|
-
|
|
1518
|
-
|
|
1524
|
+
expect(result).to.match(/^content-of-/);
|
|
1525
|
+
expect(result.toLowerCase()).to.include('/users/safi/external/configs/myapp.json');
|
|
1519
1526
|
});
|
|
1520
1527
|
it('keeps Linux case-sensitive: rejects a case-mismatched target', async () => {
|
|
1521
|
-
|
|
1522
|
-
|
|
1528
|
+
OS.backend.isWindows = false;
|
|
1529
|
+
OS.backend.isOSX = false;
|
|
1523
1530
|
allowedPaths = ['/home/safi/External/Configs'];
|
|
1524
1531
|
const result = await callContent('/home/safi/external/configs/myapp.json');
|
|
1525
1532
|
const parsed = JSON.parse(result);
|
|
1526
|
-
|
|
1533
|
+
expect(parsed.error).to.include('not allowed');
|
|
1527
1534
|
});
|
|
1528
1535
|
});
|
|
1529
1536
|
// getFileDiagnostics goes through the same `resolveAccessiblePath` gate as every other tool, so a
|
|
1530
1537
|
// sibling that merely shares the workspace root's string prefix must not pass.
|
|
1531
1538
|
it('FileDiagnosticProvider rejects sibling-prefix path outside the workspace', async () => {
|
|
1532
|
-
const diag = container.get(
|
|
1539
|
+
const diag = container.get(FileDiagnosticProvider);
|
|
1533
1540
|
const handler = diag.getTool().handler;
|
|
1534
1541
|
// Workspace root is file:///workspace/project, so file:///workspace/project-other/file.txt is
|
|
1535
1542
|
// outside it — and a `..` segment is refused before it can even be resolved.
|
|
1536
1543
|
const result = await handler(JSON.stringify({ file: '../project-other/file.txt' }), undefined);
|
|
1537
1544
|
const parsed = JSON.parse(result);
|
|
1538
|
-
|
|
1545
|
+
expect(parsed.error).to.include('Invalid path');
|
|
1539
1546
|
});
|
|
1540
1547
|
});
|
|
1541
1548
|
// ── Branch: Multi-root workspace tests ──────────────────────────────────────
|
|
@@ -1544,177 +1551,177 @@ describe('WorkspaceFunctionScope Multi-Root Tests', () => {
|
|
|
1544
1551
|
let workspaceScope;
|
|
1545
1552
|
let disableJSDOMInner;
|
|
1546
1553
|
before(() => {
|
|
1547
|
-
disableJSDOMInner =
|
|
1554
|
+
disableJSDOMInner = enableJSDOM();
|
|
1548
1555
|
});
|
|
1549
1556
|
after(() => {
|
|
1550
1557
|
disableJSDOMInner();
|
|
1551
1558
|
});
|
|
1552
1559
|
beforeEach(() => {
|
|
1553
|
-
container = new
|
|
1554
|
-
container.bind(
|
|
1560
|
+
container = new Container();
|
|
1561
|
+
container.bind(ILogger).to(MockLogger);
|
|
1555
1562
|
});
|
|
1556
1563
|
describe('getRootMapping', () => {
|
|
1557
1564
|
it('returns single root with its basename', () => {
|
|
1558
1565
|
const mockWorkspaceService = {
|
|
1559
|
-
tryGetRoots: () => [{ resource: new
|
|
1566
|
+
tryGetRoots: () => [{ resource: new URI('file:///home/user/my-project') }],
|
|
1560
1567
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1561
1568
|
};
|
|
1562
|
-
container.bind(
|
|
1563
|
-
container.bind(
|
|
1564
|
-
container.bind(
|
|
1565
|
-
container.bind(
|
|
1566
|
-
container.bind(
|
|
1567
|
-
container.bind(
|
|
1568
|
-
workspaceScope = container.get(
|
|
1569
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1570
|
+
container.bind(FileService).toConstantValue({});
|
|
1571
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1572
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1573
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1574
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1575
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1569
1576
|
const mapping = workspaceScope.getRootMapping();
|
|
1570
|
-
|
|
1571
|
-
|
|
1577
|
+
expect(mapping.size).to.equal(1);
|
|
1578
|
+
expect(mapping.get('my-project')?.toString()).to.equal('file:///home/user/my-project');
|
|
1572
1579
|
});
|
|
1573
1580
|
it('returns multiple roots with their basenames', () => {
|
|
1574
1581
|
const mockWorkspaceService = {
|
|
1575
1582
|
tryGetRoots: () => [
|
|
1576
|
-
{ resource: new
|
|
1577
|
-
{ resource: new
|
|
1578
|
-
{ resource: new
|
|
1583
|
+
{ resource: new URI('file:///home/user/frontend') },
|
|
1584
|
+
{ resource: new URI('file:///home/user/backend') },
|
|
1585
|
+
{ resource: new URI('file:///home/user/shared') }
|
|
1579
1586
|
],
|
|
1580
1587
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1581
1588
|
};
|
|
1582
|
-
container.bind(
|
|
1583
|
-
container.bind(
|
|
1584
|
-
container.bind(
|
|
1585
|
-
container.bind(
|
|
1586
|
-
container.bind(
|
|
1587
|
-
container.bind(
|
|
1588
|
-
workspaceScope = container.get(
|
|
1589
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1590
|
+
container.bind(FileService).toConstantValue({});
|
|
1591
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1592
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1593
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1594
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1595
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1589
1596
|
const mapping = workspaceScope.getRootMapping();
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1597
|
+
expect(mapping.size).to.equal(3);
|
|
1598
|
+
expect(mapping.get('frontend')?.toString()).to.equal('file:///home/user/frontend');
|
|
1599
|
+
expect(mapping.get('backend')?.toString()).to.equal('file:///home/user/backend');
|
|
1600
|
+
expect(mapping.get('shared')?.toString()).to.equal('file:///home/user/shared');
|
|
1594
1601
|
});
|
|
1595
1602
|
it('names roots sharing a basename as peers', () => {
|
|
1596
1603
|
const mockWorkspaceService = {
|
|
1597
1604
|
tryGetRoots: () => [
|
|
1598
|
-
{ resource: new
|
|
1599
|
-
{ resource: new
|
|
1605
|
+
{ resource: new URI('file:///alice/app') },
|
|
1606
|
+
{ resource: new URI('file:///bob/app') }
|
|
1600
1607
|
],
|
|
1601
1608
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1602
1609
|
};
|
|
1603
|
-
container.bind(
|
|
1604
|
-
container.bind(
|
|
1605
|
-
container.bind(
|
|
1606
|
-
container.bind(
|
|
1607
|
-
container.bind(
|
|
1608
|
-
container.bind(
|
|
1609
|
-
workspaceScope = container.get(
|
|
1610
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1611
|
+
container.bind(FileService).toConstantValue({});
|
|
1612
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1613
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1614
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1615
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1616
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1610
1617
|
const mapping = workspaceScope.getRootMapping();
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1618
|
+
expect(mapping.size).to.equal(2);
|
|
1619
|
+
expect(mapping.get('alice/app')?.toString()).to.equal('file:///alice/app');
|
|
1620
|
+
expect(mapping.get('bob/app')?.toString()).to.equal('file:///bob/app');
|
|
1614
1621
|
});
|
|
1615
1622
|
});
|
|
1616
1623
|
describe('toWorkspaceRelativePath', () => {
|
|
1617
1624
|
it('returns root-prefixed path for file in single root', () => {
|
|
1618
1625
|
const mockWorkspaceService = {
|
|
1619
|
-
tryGetRoots: () => [{ resource: new
|
|
1626
|
+
tryGetRoots: () => [{ resource: new URI('file:///home/user/project') }],
|
|
1620
1627
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1621
1628
|
};
|
|
1622
|
-
container.bind(
|
|
1623
|
-
container.bind(
|
|
1624
|
-
container.bind(
|
|
1625
|
-
container.bind(
|
|
1626
|
-
container.bind(
|
|
1627
|
-
container.bind(
|
|
1628
|
-
workspaceScope = container.get(
|
|
1629
|
-
const uri = new
|
|
1629
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1630
|
+
container.bind(FileService).toConstantValue({});
|
|
1631
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1632
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1633
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1634
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1635
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1636
|
+
const uri = new URI('file:///home/user/project/src/index.ts');
|
|
1630
1637
|
const result = workspaceScope.toWorkspaceRelativePath(uri);
|
|
1631
|
-
|
|
1638
|
+
expect(result).to.equal('project/src/index.ts');
|
|
1632
1639
|
});
|
|
1633
1640
|
it('returns root-prefixed path for file in multi-root workspace', () => {
|
|
1634
1641
|
const mockWorkspaceService = {
|
|
1635
1642
|
tryGetRoots: () => [
|
|
1636
|
-
{ resource: new
|
|
1637
|
-
{ resource: new
|
|
1643
|
+
{ resource: new URI('file:///home/user/frontend') },
|
|
1644
|
+
{ resource: new URI('file:///home/user/backend') }
|
|
1638
1645
|
],
|
|
1639
1646
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1640
1647
|
};
|
|
1641
|
-
container.bind(
|
|
1642
|
-
container.bind(
|
|
1643
|
-
container.bind(
|
|
1644
|
-
container.bind(
|
|
1645
|
-
container.bind(
|
|
1646
|
-
container.bind(
|
|
1647
|
-
workspaceScope = container.get(
|
|
1648
|
-
const frontendFile = new
|
|
1649
|
-
|
|
1650
|
-
const backendFile = new
|
|
1651
|
-
|
|
1648
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1649
|
+
container.bind(FileService).toConstantValue({});
|
|
1650
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1651
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1652
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1653
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1654
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1655
|
+
const frontendFile = new URI('file:///home/user/frontend/src/App.tsx');
|
|
1656
|
+
expect(workspaceScope.toWorkspaceRelativePath(frontendFile)).to.equal('frontend/src/App.tsx');
|
|
1657
|
+
const backendFile = new URI('file:///home/user/backend/src/server.ts');
|
|
1658
|
+
expect(workspaceScope.toWorkspaceRelativePath(backendFile)).to.equal('backend/src/server.ts');
|
|
1652
1659
|
});
|
|
1653
1660
|
it('returns undefined for file outside workspace', () => {
|
|
1654
1661
|
const mockWorkspaceService = {
|
|
1655
|
-
tryGetRoots: () => [{ resource: new
|
|
1662
|
+
tryGetRoots: () => [{ resource: new URI('file:///home/user/project') }],
|
|
1656
1663
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1657
1664
|
};
|
|
1658
|
-
container.bind(
|
|
1659
|
-
container.bind(
|
|
1660
|
-
container.bind(
|
|
1661
|
-
container.bind(
|
|
1662
|
-
container.bind(
|
|
1663
|
-
container.bind(
|
|
1664
|
-
workspaceScope = container.get(
|
|
1665
|
-
const uri = new
|
|
1665
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1666
|
+
container.bind(FileService).toConstantValue({});
|
|
1667
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1668
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1669
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1670
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1671
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1672
|
+
const uri = new URI('file:///etc/passwd');
|
|
1666
1673
|
const result = workspaceScope.toWorkspaceRelativePath(uri);
|
|
1667
|
-
|
|
1674
|
+
expect(result).to.be.undefined;
|
|
1668
1675
|
});
|
|
1669
1676
|
it('returns just root name for the root directory itself', () => {
|
|
1670
1677
|
const mockWorkspaceService = {
|
|
1671
|
-
tryGetRoots: () => [{ resource: new
|
|
1678
|
+
tryGetRoots: () => [{ resource: new URI('file:///home/user/project') }],
|
|
1672
1679
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1673
1680
|
};
|
|
1674
|
-
container.bind(
|
|
1675
|
-
container.bind(
|
|
1676
|
-
container.bind(
|
|
1677
|
-
container.bind(
|
|
1678
|
-
container.bind(
|
|
1679
|
-
container.bind(
|
|
1680
|
-
workspaceScope = container.get(
|
|
1681
|
-
const uri = new
|
|
1681
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1682
|
+
container.bind(FileService).toConstantValue({});
|
|
1683
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1684
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1685
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1686
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1687
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1688
|
+
const uri = new URI('file:///home/user/project');
|
|
1682
1689
|
const result = workspaceScope.toWorkspaceRelativePath(uri);
|
|
1683
1690
|
// When the URI is the root itself, it returns just the root name
|
|
1684
|
-
|
|
1691
|
+
expect(result).to.equal('project');
|
|
1685
1692
|
});
|
|
1686
1693
|
});
|
|
1687
1694
|
describe('resolveRelativePath', () => {
|
|
1688
1695
|
function createScope(roots) {
|
|
1689
|
-
const rootObjects = roots.map(r => ({ resource: new
|
|
1696
|
+
const rootObjects = roots.map(r => ({ resource: new URI(r) }));
|
|
1690
1697
|
const mockWorkspaceService = {
|
|
1691
1698
|
tryGetRoots: () => rootObjects,
|
|
1692
1699
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1693
1700
|
};
|
|
1694
|
-
container.bind(
|
|
1695
|
-
container.bind(
|
|
1696
|
-
container.bind(
|
|
1697
|
-
container.bind(
|
|
1698
|
-
container.bind(
|
|
1699
|
-
container.bind(
|
|
1700
|
-
return container.get(
|
|
1701
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1702
|
+
container.bind(FileService).toConstantValue({});
|
|
1703
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1704
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1705
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1706
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1707
|
+
return container.get(WorkspaceFunctionScope);
|
|
1701
1708
|
}
|
|
1702
1709
|
describe('Phase 1 — root+relative', () => {
|
|
1703
1710
|
it('resolves path with matching root prefix', () => {
|
|
1704
1711
|
workspaceScope = createScope(['file:///home/user/frontend', 'file:///home/user/backend']);
|
|
1705
1712
|
const result = workspaceScope.resolveRelativePath('backend/src/server.ts');
|
|
1706
|
-
|
|
1713
|
+
expect(result.toString()).to.equal('file:///home/user/backend/src/server.ts');
|
|
1707
1714
|
});
|
|
1708
1715
|
it('returns the root URI when path is just a root name', () => {
|
|
1709
1716
|
workspaceScope = createScope(['file:///home/user/frontend', 'file:///home/user/backend']);
|
|
1710
1717
|
const result = workspaceScope.resolveRelativePath('frontend');
|
|
1711
|
-
|
|
1718
|
+
expect(result.toString()).to.equal('file:///home/user/frontend');
|
|
1712
1719
|
});
|
|
1713
1720
|
it('root name takes priority over subdirectory with same name', () => {
|
|
1714
1721
|
// Root named 'src' exists, AND another root has a 'src/' subdirectory
|
|
1715
1722
|
workspaceScope = createScope(['file:///home/user/src', 'file:///home/user/app']);
|
|
1716
1723
|
const result = workspaceScope.resolveRelativePath('src/index.ts');
|
|
1717
|
-
|
|
1724
|
+
expect(result.toString()).to.equal('file:///home/user/src/index.ts');
|
|
1718
1725
|
});
|
|
1719
1726
|
});
|
|
1720
1727
|
describe('Phase 2 — supra-relative', () => {
|
|
@@ -1722,114 +1729,114 @@ describe('WorkspaceFunctionScope Multi-Root Tests', () => {
|
|
|
1722
1729
|
workspaceScope = createScope(['file:///home/user/backend']);
|
|
1723
1730
|
// 'user/backend/src/file.ts' — 'backend' matches root basename, 'user' matches preceding component
|
|
1724
1731
|
const result = workspaceScope.resolveRelativePath('user/backend/src/file.ts');
|
|
1725
|
-
|
|
1732
|
+
expect(result.toString()).to.equal('file:///home/user/backend/src/file.ts');
|
|
1726
1733
|
});
|
|
1727
1734
|
it('resolves with full preceding path match', () => {
|
|
1728
1735
|
workspaceScope = createScope(['file:///home/user/backend']);
|
|
1729
1736
|
const result = workspaceScope.resolveRelativePath('home/user/backend/src/file.ts');
|
|
1730
|
-
|
|
1737
|
+
expect(result.toString()).to.equal('file:///home/user/backend/src/file.ts');
|
|
1731
1738
|
});
|
|
1732
1739
|
it('does not match when preceding components differ', () => {
|
|
1733
1740
|
// Must use multi-root so Phase 3 (single-root fallback) doesn't short-circuit
|
|
1734
1741
|
workspaceScope = createScope(['file:///home/user/frontend', 'file:///home/user/backend']);
|
|
1735
1742
|
// 'other/backend/src/file.ts' — 'other' does NOT match any suffix of 'home/user'
|
|
1736
|
-
|
|
1743
|
+
expect(() => workspaceScope.resolveRelativePath('other/backend/src/file.ts')).to.throw(/Could not resolve path/);
|
|
1737
1744
|
});
|
|
1738
1745
|
it('resolves with no preceding components (just root basename at start)', () => {
|
|
1739
1746
|
// This should be caught by Phase 1 (root+relative) since the first segment IS the root name
|
|
1740
1747
|
workspaceScope = createScope(['file:///home/user/backend']);
|
|
1741
1748
|
const result = workspaceScope.resolveRelativePath('backend/src/file.ts');
|
|
1742
|
-
|
|
1749
|
+
expect(result.toString()).to.equal('file:///home/user/backend/src/file.ts');
|
|
1743
1750
|
});
|
|
1744
1751
|
it('resolves supra-relative path in multi-root workspace', () => {
|
|
1745
1752
|
workspaceScope = createScope(['file:///home/user/frontend', 'file:///home/user/backend']);
|
|
1746
1753
|
const result = workspaceScope.resolveRelativePath('user/backend/src/file.ts');
|
|
1747
|
-
|
|
1754
|
+
expect(result.toString()).to.equal('file:///home/user/backend/src/file.ts');
|
|
1748
1755
|
});
|
|
1749
1756
|
});
|
|
1750
1757
|
describe('Phase 3 — single-root fallback', () => {
|
|
1751
1758
|
it('resolves unprefixed path relative to the single root', () => {
|
|
1752
1759
|
workspaceScope = createScope(['file:///home/user/project']);
|
|
1753
1760
|
const result = workspaceScope.resolveRelativePath('src/index.ts');
|
|
1754
|
-
|
|
1761
|
+
expect(result.toString()).to.equal('file:///home/user/project/src/index.ts');
|
|
1755
1762
|
});
|
|
1756
1763
|
it('resolves new file path relative to single root', () => {
|
|
1757
1764
|
workspaceScope = createScope(['file:///home/user/project']);
|
|
1758
1765
|
const result = workspaceScope.resolveRelativePath('new-file.ts');
|
|
1759
|
-
|
|
1766
|
+
expect(result.toString()).to.equal('file:///home/user/project/new-file.ts');
|
|
1760
1767
|
});
|
|
1761
1768
|
});
|
|
1762
1769
|
describe('Phase 4 — error for unresolvable multi-root paths', () => {
|
|
1763
1770
|
it('throws when unprefixed path cannot be resolved in multi-root', () => {
|
|
1764
1771
|
workspaceScope = createScope(['file:///home/user/frontend', 'file:///home/user/backend']);
|
|
1765
|
-
|
|
1772
|
+
expect(() => workspaceScope.resolveRelativePath('src/index.ts')).to.throw(/Could not resolve path/);
|
|
1766
1773
|
});
|
|
1767
1774
|
it('error message lists available roots', () => {
|
|
1768
1775
|
workspaceScope = createScope(['file:///home/user/frontend', 'file:///home/user/backend']);
|
|
1769
1776
|
try {
|
|
1770
1777
|
workspaceScope.resolveRelativePath('src/index.ts');
|
|
1771
|
-
|
|
1778
|
+
expect.fail('Expected an error');
|
|
1772
1779
|
}
|
|
1773
1780
|
catch (e) {
|
|
1774
1781
|
const error = e;
|
|
1775
|
-
|
|
1776
|
-
|
|
1782
|
+
expect(error.message).to.include('frontend');
|
|
1783
|
+
expect(error.message).to.include('backend');
|
|
1777
1784
|
}
|
|
1778
1785
|
});
|
|
1779
1786
|
it('does not throw when root prefix is provided in multi-root', () => {
|
|
1780
1787
|
workspaceScope = createScope(['file:///home/user/frontend', 'file:///home/user/backend']);
|
|
1781
1788
|
const result = workspaceScope.resolveRelativePath('frontend/src/index.ts');
|
|
1782
|
-
|
|
1789
|
+
expect(result.toString()).to.equal('file:///home/user/frontend/src/index.ts');
|
|
1783
1790
|
});
|
|
1784
1791
|
});
|
|
1785
1792
|
describe('normalization', () => {
|
|
1786
1793
|
it('normalizes backslash paths', () => {
|
|
1787
1794
|
workspaceScope = createScope(['file:///home/user/project']);
|
|
1788
1795
|
const result = workspaceScope.resolveRelativePath('project\\src\\index.ts');
|
|
1789
|
-
|
|
1796
|
+
expect(result.toString()).to.equal('file:///home/user/project/src/index.ts');
|
|
1790
1797
|
});
|
|
1791
1798
|
it('resolves .. segments', () => {
|
|
1792
1799
|
workspaceScope = createScope(['file:///home/user/project']);
|
|
1793
1800
|
const result = workspaceScope.resolveRelativePath('project/src/../src/index.ts');
|
|
1794
|
-
|
|
1801
|
+
expect(result.toString()).to.equal('file:///home/user/project/src/index.ts');
|
|
1795
1802
|
});
|
|
1796
1803
|
it('normalizes redundant separators', () => {
|
|
1797
1804
|
workspaceScope = createScope(['file:///home/user/project']);
|
|
1798
1805
|
const result = workspaceScope.resolveRelativePath('project/src//index.ts');
|
|
1799
|
-
|
|
1806
|
+
expect(result.path.toString()).to.include('project/src/index.ts');
|
|
1800
1807
|
});
|
|
1801
1808
|
it('handles trailing slashes', () => {
|
|
1802
1809
|
workspaceScope = createScope(['file:///home/user/project']);
|
|
1803
1810
|
const result = workspaceScope.resolveRelativePath('project/src/');
|
|
1804
|
-
|
|
1811
|
+
expect(result.path.toString()).to.include('project/src');
|
|
1805
1812
|
});
|
|
1806
1813
|
});
|
|
1807
1814
|
describe('duplicate root basenames', () => {
|
|
1808
1815
|
it('names all roots sharing a basename as peers and round-trips each of them', () => {
|
|
1809
1816
|
workspaceScope = createScope(['file:///workspace/a/app', 'file:///workspace/b/app']);
|
|
1810
1817
|
const mapping = workspaceScope.getRootMapping();
|
|
1811
|
-
|
|
1818
|
+
expect(Array.from(mapping.keys())).to.deep.equal(['a/app', 'b/app']);
|
|
1812
1819
|
for (const [name, rootUri] of mapping) {
|
|
1813
1820
|
const uri = rootUri.resolve('src/index.ts');
|
|
1814
1821
|
const relPath = workspaceScope.toWorkspaceRelativePath(uri);
|
|
1815
|
-
|
|
1816
|
-
|
|
1822
|
+
expect(relPath).to.equal(`${name}/src/index.ts`);
|
|
1823
|
+
expect(workspaceScope.resolveRelativePath(relPath).toString()).to.equal(uri.toString());
|
|
1817
1824
|
}
|
|
1818
1825
|
});
|
|
1819
1826
|
it('rejects the shared basename as ambiguous', () => {
|
|
1820
1827
|
workspaceScope = createScope(['file:///workspace/a/app', 'file:///workspace/b/app']);
|
|
1821
|
-
|
|
1828
|
+
expect(() => workspaceScope.resolveRelativePath('app/src/index.ts')).to.throw(/ambiguous.*a\/app.*b\/app/);
|
|
1822
1829
|
});
|
|
1823
1830
|
it('resolves a longer parent-qualified path (Phase 2)', () => {
|
|
1824
1831
|
workspaceScope = createScope(['file:///workspace/a/app', 'file:///workspace/b/app']);
|
|
1825
1832
|
const result = workspaceScope.resolveRelativePath('workspace/b/app/src/index.ts');
|
|
1826
|
-
|
|
1833
|
+
expect(result.toString()).to.equal('file:///workspace/b/app/src/index.ts');
|
|
1827
1834
|
});
|
|
1828
1835
|
// The external file change notice names files by a uri when roots share a basename.
|
|
1829
1836
|
it('resolves a uri for a root sharing its basename', async () => {
|
|
1830
1837
|
workspaceScope = createScope(['file:///workspace/a/app', 'file:///workspace/b/app']);
|
|
1831
1838
|
const uri = 'file:///workspace/b/app/src/index.ts';
|
|
1832
|
-
|
|
1839
|
+
expect((await workspaceScope.resolveAccessiblePath(uri)).toString()).to.equal(uri);
|
|
1833
1840
|
});
|
|
1834
1841
|
});
|
|
1835
1842
|
});
|
|
@@ -1838,131 +1845,131 @@ describe('WorkspaceFunctionScope Multi-Root Tests', () => {
|
|
|
1838
1845
|
// First ordering
|
|
1839
1846
|
const mockWorkspaceServiceA = {
|
|
1840
1847
|
tryGetRoots: () => [
|
|
1841
|
-
{ resource: new
|
|
1842
|
-
{ resource: new
|
|
1848
|
+
{ resource: new URI('file:///workspace/b/app') },
|
|
1849
|
+
{ resource: new URI('file:///workspace/a/app') }
|
|
1843
1850
|
],
|
|
1844
1851
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1845
1852
|
};
|
|
1846
|
-
container.bind(
|
|
1847
|
-
container.bind(
|
|
1848
|
-
container.bind(
|
|
1849
|
-
container.bind(
|
|
1850
|
-
container.bind(
|
|
1851
|
-
container.bind(
|
|
1852
|
-
const scopeA = container.get(
|
|
1853
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceServiceA));
|
|
1854
|
+
container.bind(FileService).toConstantValue({});
|
|
1855
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1856
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1857
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1858
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1859
|
+
const scopeA = container.get(WorkspaceFunctionScope);
|
|
1853
1860
|
const mappingA = scopeA.getRootMapping();
|
|
1854
1861
|
// Reverse ordering in a fresh container
|
|
1855
|
-
const container2 = new
|
|
1862
|
+
const container2 = new Container();
|
|
1856
1863
|
const mockWorkspaceServiceB = {
|
|
1857
1864
|
tryGetRoots: () => [
|
|
1858
|
-
{ resource: new
|
|
1859
|
-
{ resource: new
|
|
1865
|
+
{ resource: new URI('file:///workspace/a/app') },
|
|
1866
|
+
{ resource: new URI('file:///workspace/b/app') }
|
|
1860
1867
|
],
|
|
1861
1868
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1862
1869
|
};
|
|
1863
|
-
container2.bind(
|
|
1864
|
-
container2.bind(
|
|
1865
|
-
container2.bind(
|
|
1866
|
-
container2.bind(
|
|
1867
|
-
container2.bind(
|
|
1868
|
-
container2.bind(
|
|
1869
|
-
container2.bind(
|
|
1870
|
-
const scopeB = container2.get(
|
|
1870
|
+
container2.bind(ILogger).to(MockLogger);
|
|
1871
|
+
container2.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceServiceB));
|
|
1872
|
+
container2.bind(FileService).toConstantValue({});
|
|
1873
|
+
container2.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1874
|
+
container2.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1875
|
+
container2.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1876
|
+
container2.bind(WorkspaceFunctionScope).toSelf();
|
|
1877
|
+
const scopeB = container2.get(WorkspaceFunctionScope);
|
|
1871
1878
|
const mappingB = scopeB.getRootMapping();
|
|
1872
1879
|
for (const name of ['a/app', 'b/app']) {
|
|
1873
|
-
|
|
1880
|
+
expect(mappingA.get(name)?.toString()).to.equal(mappingB.get(name)?.toString());
|
|
1874
1881
|
}
|
|
1875
|
-
|
|
1882
|
+
expect(mappingA.get('a/app')?.toString()).to.equal('file:///workspace/a/app');
|
|
1876
1883
|
});
|
|
1877
1884
|
});
|
|
1878
1885
|
describe('getContainingRoot', () => {
|
|
1879
1886
|
it('returns the most specific root for nested roots', () => {
|
|
1880
1887
|
const mockWorkspaceService = {
|
|
1881
1888
|
tryGetRoots: () => [
|
|
1882
|
-
{ resource: new
|
|
1883
|
-
{ resource: new
|
|
1889
|
+
{ resource: new URI('file:///projects/monorepo') },
|
|
1890
|
+
{ resource: new URI('file:///projects/monorepo/packages/shared') }
|
|
1884
1891
|
],
|
|
1885
1892
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1886
1893
|
};
|
|
1887
|
-
container.bind(
|
|
1888
|
-
container.bind(
|
|
1889
|
-
container.bind(
|
|
1890
|
-
container.bind(
|
|
1891
|
-
container.bind(
|
|
1892
|
-
container.bind(
|
|
1893
|
-
workspaceScope = container.get(
|
|
1894
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1895
|
+
container.bind(FileService).toConstantValue({});
|
|
1896
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1897
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1898
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1899
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1900
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1894
1901
|
// File in nested root should resolve to the more specific root
|
|
1895
|
-
const fileInNested = new
|
|
1902
|
+
const fileInNested = new URI('file:///projects/monorepo/packages/shared/src/utils.ts');
|
|
1896
1903
|
const result = workspaceScope.getContainingRoot(fileInNested);
|
|
1897
|
-
|
|
1904
|
+
expect(result?.toString()).to.equal('file:///projects/monorepo/packages/shared');
|
|
1898
1905
|
// File in parent root (outside nested) should resolve to parent
|
|
1899
|
-
const fileInParent = new
|
|
1906
|
+
const fileInParent = new URI('file:///projects/monorepo/src/main.ts');
|
|
1900
1907
|
const resultParent = workspaceScope.getContainingRoot(fileInParent);
|
|
1901
|
-
|
|
1908
|
+
expect(resultParent?.toString()).to.equal('file:///projects/monorepo');
|
|
1902
1909
|
});
|
|
1903
1910
|
it('returns undefined for file outside all workspace roots', () => {
|
|
1904
1911
|
const mockWorkspaceService = {
|
|
1905
1912
|
tryGetRoots: () => [
|
|
1906
|
-
{ resource: new
|
|
1913
|
+
{ resource: new URI('file:///home/user/project') }
|
|
1907
1914
|
],
|
|
1908
1915
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1909
1916
|
};
|
|
1910
|
-
container.bind(
|
|
1911
|
-
container.bind(
|
|
1912
|
-
container.bind(
|
|
1913
|
-
container.bind(
|
|
1914
|
-
container.bind(
|
|
1915
|
-
container.bind(
|
|
1916
|
-
workspaceScope = container.get(
|
|
1917
|
-
const externalFile = new
|
|
1917
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1918
|
+
container.bind(FileService).toConstantValue({});
|
|
1919
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1920
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1921
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1922
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1923
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1924
|
+
const externalFile = new URI('file:///tmp/outside.ts');
|
|
1918
1925
|
const result = workspaceScope.getContainingRoot(externalFile);
|
|
1919
|
-
|
|
1926
|
+
expect(result).to.be.undefined;
|
|
1920
1927
|
});
|
|
1921
1928
|
});
|
|
1922
1929
|
describe('round-trip consistency', () => {
|
|
1923
1930
|
it('toWorkspaceRelativePath and resolveRelativePath are inverses', () => {
|
|
1924
1931
|
const mockWorkspaceService = {
|
|
1925
1932
|
tryGetRoots: () => [
|
|
1926
|
-
{ resource: new
|
|
1927
|
-
{ resource: new
|
|
1933
|
+
{ resource: new URI('file:///home/user/frontend') },
|
|
1934
|
+
{ resource: new URI('file:///home/user/backend') }
|
|
1928
1935
|
],
|
|
1929
1936
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1930
1937
|
};
|
|
1931
|
-
container.bind(
|
|
1932
|
-
container.bind(
|
|
1933
|
-
container.bind(
|
|
1934
|
-
container.bind(
|
|
1935
|
-
container.bind(
|
|
1936
|
-
container.bind(
|
|
1937
|
-
workspaceScope = container.get(
|
|
1938
|
-
const originalUri = new
|
|
1938
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1939
|
+
container.bind(FileService).toConstantValue({});
|
|
1940
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1941
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1942
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1943
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1944
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1945
|
+
const originalUri = new URI('file:///home/user/backend/src/index.ts');
|
|
1939
1946
|
const relativePath = workspaceScope.toWorkspaceRelativePath(originalUri);
|
|
1940
1947
|
const resolvedUri = workspaceScope.resolveRelativePath(relativePath);
|
|
1941
|
-
|
|
1948
|
+
expect(resolvedUri.toString()).to.equal(originalUri.toString());
|
|
1942
1949
|
});
|
|
1943
1950
|
it('paths from toWorkspaceRelativePath always have a root prefix that resolves directly', () => {
|
|
1944
1951
|
const mockWorkspaceService = {
|
|
1945
1952
|
tryGetRoots: () => [
|
|
1946
|
-
{ resource: new
|
|
1947
|
-
{ resource: new
|
|
1953
|
+
{ resource: new URI('file:///home/user/frontend') },
|
|
1954
|
+
{ resource: new URI('file:///home/user/backend') }
|
|
1948
1955
|
],
|
|
1949
1956
|
onWorkspaceChanged: () => ({ dispose: () => { } })
|
|
1950
1957
|
};
|
|
1951
|
-
container.bind(
|
|
1952
|
-
container.bind(
|
|
1953
|
-
container.bind(
|
|
1954
|
-
container.bind(
|
|
1955
|
-
container.bind(
|
|
1956
|
-
container.bind(
|
|
1957
|
-
workspaceScope = container.get(
|
|
1958
|
+
container.bind(WorkspaceService).toConstantValue(withWorkspaceServiceDefaults(mockWorkspaceService));
|
|
1959
|
+
container.bind(FileService).toConstantValue({});
|
|
1960
|
+
container.bind(PreferenceService).toConstantValue({ get: () => false });
|
|
1961
|
+
container.bind(AiConfigurationService).toConstantValue(makeTrustAwareReader());
|
|
1962
|
+
container.bind(EnvVariablesServer).toConstantValue(makeEnvVariablesServer());
|
|
1963
|
+
container.bind(WorkspaceFunctionScope).toSelf();
|
|
1964
|
+
workspaceScope = container.get(WorkspaceFunctionScope);
|
|
1958
1965
|
// Test for each root
|
|
1959
1966
|
const roots = workspaceScope.getRootMapping();
|
|
1960
1967
|
for (const [rootName, rootUri] of roots) {
|
|
1961
1968
|
const fileUri = rootUri.resolve('src/app.ts');
|
|
1962
1969
|
const relPath = workspaceScope.toWorkspaceRelativePath(fileUri);
|
|
1963
|
-
|
|
1970
|
+
expect(relPath.startsWith(rootName + '/')).to.be.true;
|
|
1964
1971
|
const resolved = workspaceScope.resolveRelativePath(relPath);
|
|
1965
|
-
|
|
1972
|
+
expect(resolved.toString()).to.equal(fileUri.toString());
|
|
1966
1973
|
}
|
|
1967
1974
|
});
|
|
1968
1975
|
});
|