@theia/ai-terminal 1.77.0-next.57 → 1.77.0-next.63
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/shell-execution-tool.d.ts +11 -3
- package/lib/browser/shell-execution-tool.d.ts.map +1 -1
- package/lib/browser/shell-execution-tool.js +42 -20
- package/lib/browser/shell-execution-tool.js.map +1 -1
- package/lib/browser/shell-execution-tool.spec.d.ts +2 -0
- package/lib/browser/shell-execution-tool.spec.d.ts.map +1 -0
- package/lib/browser/shell-execution-tool.spec.js +122 -0
- package/lib/browser/shell-execution-tool.spec.js.map +1 -0
- package/package.json +8 -8
- package/src/browser/shell-execution-tool.spec.ts +132 -0
- package/src/browser/shell-execution-tool.ts +46 -21
|
@@ -2,15 +2,23 @@ import { ToolRequest } from '@theia/ai-core';
|
|
|
2
2
|
import { ShellCommandPermissionService } from './shell-command-permission-service';
|
|
3
3
|
import { AbstractShellExecutionTool } from './abstract-shell-execution-tool';
|
|
4
4
|
import { ShellExecutionToolResult, ShellExecutionCanceledResult } from '../common/shell-execution-server';
|
|
5
|
+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
5
6
|
export declare class ShellExecutionTool extends AbstractShellExecutionTool {
|
|
6
7
|
protected readonly shellCommandPermissionService: ShellCommandPermissionService;
|
|
8
|
+
protected readonly envVariablesServer: EnvVariablesServer;
|
|
7
9
|
getTool(): ToolRequest;
|
|
8
10
|
protected executeCommand(argString: string, ctx?: unknown): Promise<ShellExecutionToolResult | ShellExecutionCanceledResult>;
|
|
9
11
|
/**
|
|
10
12
|
* Resolves a cwd value to an absolute filesystem path.
|
|
11
|
-
* Handles: undefined (falls back to workspace root), absolute paths
|
|
12
|
-
* root-
|
|
13
|
+
* Handles: undefined (falls back to the workspace root in a single-root workspace), absolute paths
|
|
14
|
+
* and `file://` URIs, `~` for the home directory, root-prefixed paths (`<rootName>/...`) and bare
|
|
15
|
+
* root names, with the root names of {@link WorkspaceService.getRootNames}.
|
|
13
16
|
*/
|
|
14
|
-
|
|
17
|
+
protected resolveCwd(cwd: string | undefined): Promise<string>;
|
|
18
|
+
protected exampleRootName(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Lists the workspace root names with their locations, for error messages.
|
|
21
|
+
*/
|
|
22
|
+
protected describeRoots(): string;
|
|
15
23
|
}
|
|
16
24
|
//# sourceMappingURL=shell-execution-tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-execution-tool.d.ts","sourceRoot":"","sources":["../../src/browser/shell-execution-tool.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,WAAW,EAAoB,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAEH,wBAAwB,EACxB,4BAA4B,EAC/B,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"shell-execution-tool.d.ts","sourceRoot":"","sources":["../../src/browser/shell-execution-tool.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,WAAW,EAAoB,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AACnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAEH,wBAAwB,EACxB,4BAA4B,EAC/B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,qBACa,kBAAmB,SAAQ,0BAA0B;IAG9D,SAAS,CAAC,QAAQ,CAAC,6BAA6B,EAAE,6BAA6B,CAAC;IAGhF,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAE1D,OAAO,IAAI,WAAW;cAgHN,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,wBAAwB,GAAG,4BAA4B,CAAC;IAelI;;;;;OAKG;cACa,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IA6CpE,SAAS,CAAC,eAAe,IAAI,MAAM;IAInC;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,MAAM;CAIpC"}
|
|
@@ -22,6 +22,7 @@ const shell_command_permission_service_1 = require("./shell-command-permission-s
|
|
|
22
22
|
const abstract_shell_execution_tool_1 = require("./abstract-shell-execution-tool");
|
|
23
23
|
const shell_execution_server_1 = require("../common/shell-execution-server");
|
|
24
24
|
const core_1 = require("@theia/core");
|
|
25
|
+
const env_variables_1 = require("@theia/core/lib/common/env-variables");
|
|
25
26
|
let ShellExecutionTool = class ShellExecutionTool extends abstract_shell_execution_tool_1.AbstractShellExecutionTool {
|
|
26
27
|
getTool() {
|
|
27
28
|
return {
|
|
@@ -62,6 +63,10 @@ To avoid losing important information, limit output size in your commands:
|
|
|
62
63
|
- Use wc -l to count lines before fetching full output
|
|
63
64
|
- Redirect verbose output to /dev/null if not needed
|
|
64
65
|
|
|
66
|
+
WORKING DIRECTORY: The command runs inside the directory given as cwd, so relative paths in the command
|
|
67
|
+
are relative to it. In a multi-root workspace, cwd is required: a workspace root name such as "backend" is the
|
|
68
|
+
root folder itself, not a folder to change into.
|
|
69
|
+
|
|
65
70
|
TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer commands.`,
|
|
66
71
|
parameters: {
|
|
67
72
|
type: 'object',
|
|
@@ -95,10 +100,11 @@ TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer co
|
|
|
95
100
|
},
|
|
96
101
|
cwd: {
|
|
97
102
|
type: 'string',
|
|
98
|
-
description: 'Working directory
|
|
99
|
-
'Use a workspace
|
|
100
|
-
'or an absolute filesystem path. ' +
|
|
101
|
-
'
|
|
103
|
+
description: 'Working directory in which the command runs. ' +
|
|
104
|
+
'Use a workspace root name (e.g., "backend"), a path starting with a root name (e.g., "backend/src"), ' +
|
|
105
|
+
'as in the paths returned by the workspace tools, or an absolute filesystem path. ' +
|
|
106
|
+
'The command starts inside this directory: with cwd "backend", do not `cd backend` in the command. ' +
|
|
107
|
+
'Required in a multi-root workspace. If omitted in a single-root workspace, defaults to the workspace root.'
|
|
102
108
|
},
|
|
103
109
|
timeout: {
|
|
104
110
|
type: 'number',
|
|
@@ -133,45 +139,57 @@ TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer co
|
|
|
133
139
|
const args = JSON.parse(argString);
|
|
134
140
|
return this.runShellCommand({
|
|
135
141
|
command: args.command,
|
|
136
|
-
cwd: this.resolveCwd(args.cwd),
|
|
142
|
+
cwd: await this.resolveCwd(args.cwd),
|
|
137
143
|
timeout: args.timeout,
|
|
138
144
|
ctx
|
|
139
145
|
});
|
|
140
146
|
}
|
|
141
147
|
/**
|
|
142
148
|
* Resolves a cwd value to an absolute filesystem path.
|
|
143
|
-
* Handles: undefined (falls back to workspace root), absolute paths
|
|
144
|
-
* root-
|
|
149
|
+
* Handles: undefined (falls back to the workspace root in a single-root workspace), absolute paths
|
|
150
|
+
* and `file://` URIs, `~` for the home directory, root-prefixed paths (`<rootName>/...`) and bare
|
|
151
|
+
* root names, with the root names of {@link WorkspaceService.getRootNames}.
|
|
145
152
|
*/
|
|
146
|
-
resolveCwd(cwd) {
|
|
153
|
+
async resolveCwd(cwd) {
|
|
147
154
|
const roots = this.workspaceService.tryGetRoots();
|
|
148
|
-
const rootNames = roots.map(r => r.resource.path.base);
|
|
149
155
|
if (!cwd) {
|
|
150
156
|
if (roots.length === 1) {
|
|
151
157
|
return roots[0].resource.path.fsPath();
|
|
152
158
|
}
|
|
153
159
|
throw new Error('A working directory (cwd) is required in a multi-root workspace. ' +
|
|
154
|
-
`
|
|
155
|
-
|
|
160
|
+
`Provide a workspace root name as cwd (e.g., "${this.exampleRootName()}") or a path in it ` +
|
|
161
|
+
`(e.g., "${this.exampleRootName()}/src"). Available workspace roots: ${this.describeRoots()}.`);
|
|
162
|
+
}
|
|
163
|
+
if (cwd.startsWith('file://')) {
|
|
164
|
+
return new core_1.URI(cwd).path.fsPath();
|
|
156
165
|
}
|
|
157
166
|
const normalized = core_1.Path.normalizePathSeparator(cwd);
|
|
158
|
-
if (new core_1.Path(normalized).isAbsolute) {
|
|
167
|
+
if (new core_1.Path(normalized).isAbsolute || core_1.Path.isDrive(normalized.split('/')[0])) {
|
|
159
168
|
return normalized;
|
|
160
169
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
if (normalized === '~' || normalized.startsWith('~/')) {
|
|
171
|
+
const home = new core_1.URI(await this.envVariablesServer.getHomeDirUri());
|
|
172
|
+
return (normalized === '~' ? home : home.resolve(normalized.substring(2))).path.fsPath();
|
|
173
|
+
}
|
|
174
|
+
const rootPrefixed = this.workspaceService.resolveRootPrefixedPath(normalized);
|
|
175
|
+
if (rootPrefixed) {
|
|
176
|
+
return rootPrefixed.path.fsPath();
|
|
168
177
|
}
|
|
169
178
|
if (roots.length === 1) {
|
|
170
179
|
return roots[0].resource.resolve(normalized).path.fsPath();
|
|
171
180
|
}
|
|
172
181
|
throw new Error(`Could not resolve working directory '${cwd}' to a workspace root. ` +
|
|
173
182
|
'In a multi-root workspace, prefix paths with the workspace root name ' +
|
|
174
|
-
`(e.g., '${
|
|
183
|
+
`(e.g., '${this.exampleRootName()}/path'). Available workspace roots: ${this.describeRoots()}.`);
|
|
184
|
+
}
|
|
185
|
+
exampleRootName() {
|
|
186
|
+
return this.workspaceService.getRootNames().keys().next().value ?? 'rootName';
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Lists the workspace root names with their locations, for error messages.
|
|
190
|
+
*/
|
|
191
|
+
describeRoots() {
|
|
192
|
+
return Array.from(this.workspaceService.getRootNames(), ([name, uri]) => `${name} (${uri.path.fsPath()})`).join(', ');
|
|
175
193
|
}
|
|
176
194
|
};
|
|
177
195
|
exports.ShellExecutionTool = ShellExecutionTool;
|
|
@@ -179,6 +197,10 @@ tslib_1.__decorate([
|
|
|
179
197
|
(0, inversify_1.inject)(shell_command_permission_service_1.ShellCommandPermissionService),
|
|
180
198
|
tslib_1.__metadata("design:type", shell_command_permission_service_1.ShellCommandPermissionService)
|
|
181
199
|
], ShellExecutionTool.prototype, "shellCommandPermissionService", void 0);
|
|
200
|
+
tslib_1.__decorate([
|
|
201
|
+
(0, inversify_1.inject)(env_variables_1.EnvVariablesServer),
|
|
202
|
+
tslib_1.__metadata("design:type", Object)
|
|
203
|
+
], ShellExecutionTool.prototype, "envVariablesServer", void 0);
|
|
182
204
|
exports.ShellExecutionTool = ShellExecutionTool = tslib_1.__decorate([
|
|
183
205
|
(0, inversify_1.injectable)()
|
|
184
206
|
], ShellExecutionTool);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-execution-tool.js","sourceRoot":"","sources":["../../src/browser/shell-execution-tool.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,yCAAyC;AACzC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;;;AAEhF,4DAAkE;AAElE,yFAAmF;AACnF,mFAA6E;AAC7E,6EAI0C;AAC1C,
|
|
1
|
+
{"version":3,"file":"shell-execution-tool.js","sourceRoot":"","sources":["../../src/browser/shell-execution-tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-execution-tool.spec.d.ts","sourceRoot":"","sources":["../../src/browser/shell-execution-tool.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// *****************************************************************************
|
|
3
|
+
// Copyright (C) 2026 STMicroelectronics and others.
|
|
4
|
+
//
|
|
5
|
+
// This program and the accompanying materials are made available under the
|
|
6
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
7
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
8
|
+
//
|
|
9
|
+
// This Source Code may also be made available under the following Secondary
|
|
10
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
11
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
12
|
+
// with the GNU Classpath Exception which is available at
|
|
13
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
14
|
+
//
|
|
15
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
16
|
+
// *****************************************************************************
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const tslib_1 = require("tslib");
|
|
19
|
+
const jsdom_1 = require("@theia/core/lib/browser/test/jsdom");
|
|
20
|
+
let disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
21
|
+
const frontend_application_config_provider_1 = require("@theia/core/lib/browser/frontend-application-config-provider");
|
|
22
|
+
frontend_application_config_provider_1.FrontendApplicationConfigProvider.set({});
|
|
23
|
+
const chai_1 = require("chai");
|
|
24
|
+
const sinon = tslib_1.__importStar(require("sinon"));
|
|
25
|
+
const core_1 = require("@theia/core");
|
|
26
|
+
const uri_1 = tslib_1.__importDefault(require("@theia/core/lib/common/uri"));
|
|
27
|
+
const inversify_1 = require("@theia/core/shared/inversify");
|
|
28
|
+
const env_variables_1 = require("@theia/core/lib/common/env-variables");
|
|
29
|
+
const workspace_service_1 = require("@theia/workspace/lib/browser/workspace-service");
|
|
30
|
+
const with_workspace_service_defaults_1 = require("@theia/workspace/lib/browser/test/with-workspace-service-defaults");
|
|
31
|
+
const shell_execution_server_1 = require("../common/shell-execution-server");
|
|
32
|
+
const shell_command_permission_service_1 = require("./shell-command-permission-service");
|
|
33
|
+
const shell_execution_tool_1 = require("./shell-execution-tool");
|
|
34
|
+
disableJSDOM();
|
|
35
|
+
describe('ShellExecutionTool working directory', () => {
|
|
36
|
+
let shellServer;
|
|
37
|
+
let tool;
|
|
38
|
+
let originalIsWindows;
|
|
39
|
+
before(() => {
|
|
40
|
+
disableJSDOM = (0, jsdom_1.enableJSDOM)();
|
|
41
|
+
});
|
|
42
|
+
after(() => {
|
|
43
|
+
disableJSDOM();
|
|
44
|
+
});
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
originalIsWindows = core_1.OS.backend.isWindows;
|
|
47
|
+
core_1.OS.backend.isWindows = false;
|
|
48
|
+
});
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
core_1.OS.backend.isWindows = originalIsWindows;
|
|
51
|
+
});
|
|
52
|
+
const setUp = (roots) => {
|
|
53
|
+
const container = new inversify_1.Container();
|
|
54
|
+
shellServer = {
|
|
55
|
+
execute: sinon.stub().callsFake(async (request) => ({
|
|
56
|
+
success: true, exitCode: 0, stdout: '', stderr: '', duration: 0, resolvedCwd: request.cwd
|
|
57
|
+
})),
|
|
58
|
+
cancel: sinon.stub().resolves(true)
|
|
59
|
+
};
|
|
60
|
+
const workspaceService = (0, with_workspace_service_defaults_1.withWorkspaceServiceDefaults)({
|
|
61
|
+
tryGetRoots: () => roots.map(root => ({ resource: new uri_1.default(root), isDirectory: true }))
|
|
62
|
+
});
|
|
63
|
+
container.bind(shell_execution_server_1.ShellExecutionServer).toConstantValue(shellServer);
|
|
64
|
+
container.bind(workspace_service_1.WorkspaceService).toConstantValue(workspaceService);
|
|
65
|
+
container.bind(shell_command_permission_service_1.ShellCommandPermissionService).toConstantValue({});
|
|
66
|
+
container.bind(env_variables_1.EnvVariablesServer).toConstantValue({ getHomeDirUri: async () => 'file:///home/test' });
|
|
67
|
+
container.bind(shell_execution_tool_1.ShellExecutionTool).toSelf();
|
|
68
|
+
tool = container.get(shell_execution_tool_1.ShellExecutionTool);
|
|
69
|
+
};
|
|
70
|
+
const run = (args) => Promise.resolve(tool.getTool().handler(JSON.stringify({ command: 'pwd', description: 'Print the working directory', ...args }), undefined));
|
|
71
|
+
const executedCwd = () => shellServer.execute.lastCall.args[0].cwd;
|
|
72
|
+
it('defaults to the workspace root in a single-root workspace', async () => {
|
|
73
|
+
setUp(['file:///ws/theia']);
|
|
74
|
+
await run({});
|
|
75
|
+
(0, chai_1.expect)(executedCwd()).to.equal('/ws/theia');
|
|
76
|
+
});
|
|
77
|
+
it('runs in the root directory named by cwd, so the command must not change into it again', async () => {
|
|
78
|
+
setUp(['file:///ws/theia', 'file:///ws/other']);
|
|
79
|
+
await run({ cwd: 'theia' });
|
|
80
|
+
(0, chai_1.expect)(executedCwd()).to.equal('/ws/theia');
|
|
81
|
+
});
|
|
82
|
+
it('resolves a root name shared with another root by its parent-qualified name', async () => {
|
|
83
|
+
setUp(['file:///x/app', 'file:///y/app']);
|
|
84
|
+
await run({ cwd: 'y/app/src' });
|
|
85
|
+
(0, chai_1.expect)(executedCwd()).to.equal('/y/app/src');
|
|
86
|
+
});
|
|
87
|
+
it('resolves absolute paths, file URIs and the home directory', async () => {
|
|
88
|
+
setUp(['file:///ws/theia', 'file:///ws/other']);
|
|
89
|
+
await run({ cwd: '/tmp/build' });
|
|
90
|
+
(0, chai_1.expect)(executedCwd()).to.equal('/tmp/build');
|
|
91
|
+
await run({ cwd: 'file:///ws/other/src' });
|
|
92
|
+
(0, chai_1.expect)(executedCwd()).to.equal('/ws/other/src');
|
|
93
|
+
await run({ cwd: '~/projects' });
|
|
94
|
+
(0, chai_1.expect)(executedCwd()).to.equal('/home/test/projects');
|
|
95
|
+
});
|
|
96
|
+
it('rejects a basename shared by several roots, listing the root names', async () => {
|
|
97
|
+
setUp(['file:///x/app', 'file:///y/app']);
|
|
98
|
+
let error;
|
|
99
|
+
try {
|
|
100
|
+
await run({ cwd: 'app' });
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
error = e;
|
|
104
|
+
}
|
|
105
|
+
(0, chai_1.expect)(error?.message).to.contain('x/app (/x/app), y/app (/y/app)');
|
|
106
|
+
(0, chai_1.expect)(shellServer.execute.called).to.be.false;
|
|
107
|
+
});
|
|
108
|
+
// Agents often omit cwd on their first call and get this error (D1). It is required by design.
|
|
109
|
+
it('requires cwd in a multi-root workspace (D1)', async () => {
|
|
110
|
+
setUp(['file:///ws/theia', 'file:///ws/other']);
|
|
111
|
+
let error;
|
|
112
|
+
try {
|
|
113
|
+
await run({});
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
error = e;
|
|
117
|
+
}
|
|
118
|
+
(0, chai_1.expect)(error?.message).to.contain('A working directory (cwd) is required in a multi-root workspace');
|
|
119
|
+
(0, chai_1.expect)(shellServer.execute.called).to.be.false;
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=shell-execution-tool.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-execution-tool.spec.js","sourceRoot":"","sources":["../../src/browser/shell-execution-tool.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/ai-terminal",
|
|
3
|
-
"version": "1.77.0-next.
|
|
3
|
+
"version": "1.77.0-next.63+046af0812",
|
|
4
4
|
"description": "Theia - AI Terminal Extension",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@theia/ai-chat": "1.77.0-next.
|
|
7
|
-
"@theia/ai-chat-ui": "1.77.0-next.
|
|
8
|
-
"@theia/ai-core": "1.77.0-next.
|
|
9
|
-
"@theia/core": "1.77.0-next.
|
|
10
|
-
"@theia/terminal": "1.77.0-next.
|
|
11
|
-
"@theia/workspace": "1.77.0-next.
|
|
6
|
+
"@theia/ai-chat": "1.77.0-next.63+046af0812",
|
|
7
|
+
"@theia/ai-chat-ui": "1.77.0-next.63+046af0812",
|
|
8
|
+
"@theia/ai-core": "1.77.0-next.63+046af0812",
|
|
9
|
+
"@theia/core": "1.77.0-next.63+046af0812",
|
|
10
|
+
"@theia/terminal": "1.77.0-next.63+046af0812",
|
|
11
|
+
"@theia/workspace": "1.77.0-next.63+046af0812",
|
|
12
12
|
"tslib": "^2.8.1",
|
|
13
13
|
"zod": "^4.4.3"
|
|
14
14
|
},
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"nyc": {
|
|
52
52
|
"extends": "../../configs/nyc.json"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "046af0812516f46f2bf7ce3357cca0b672159b20"
|
|
55
55
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 STMicroelectronics and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
|
|
18
|
+
let disableJSDOM = enableJSDOM();
|
|
19
|
+
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
|
|
20
|
+
FrontendApplicationConfigProvider.set({});
|
|
21
|
+
|
|
22
|
+
import { expect } from 'chai';
|
|
23
|
+
import * as sinon from 'sinon';
|
|
24
|
+
import { OS } from '@theia/core';
|
|
25
|
+
import URI from '@theia/core/lib/common/uri';
|
|
26
|
+
import { Container } from '@theia/core/shared/inversify';
|
|
27
|
+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
28
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
|
29
|
+
import { withWorkspaceServiceDefaults } from '@theia/workspace/lib/browser/test/with-workspace-service-defaults';
|
|
30
|
+
import { ShellExecutionRequest, ShellExecutionServer } from '../common/shell-execution-server';
|
|
31
|
+
import { ShellCommandPermissionService } from './shell-command-permission-service';
|
|
32
|
+
import { ShellExecutionTool } from './shell-execution-tool';
|
|
33
|
+
|
|
34
|
+
disableJSDOM();
|
|
35
|
+
|
|
36
|
+
describe('ShellExecutionTool working directory', () => {
|
|
37
|
+
|
|
38
|
+
let shellServer: { execute: sinon.SinonStub; cancel: sinon.SinonStub };
|
|
39
|
+
let tool: ShellExecutionTool;
|
|
40
|
+
|
|
41
|
+
let originalIsWindows: boolean;
|
|
42
|
+
before(() => {
|
|
43
|
+
disableJSDOM = enableJSDOM();
|
|
44
|
+
});
|
|
45
|
+
after(() => {
|
|
46
|
+
disableJSDOM();
|
|
47
|
+
});
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
originalIsWindows = OS.backend.isWindows;
|
|
50
|
+
OS.backend.isWindows = false;
|
|
51
|
+
});
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
OS.backend.isWindows = originalIsWindows;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const setUp = (roots: string[]): void => {
|
|
57
|
+
const container = new Container();
|
|
58
|
+
shellServer = {
|
|
59
|
+
execute: sinon.stub().callsFake(async (request: ShellExecutionRequest) => ({
|
|
60
|
+
success: true, exitCode: 0, stdout: '', stderr: '', duration: 0, resolvedCwd: request.cwd
|
|
61
|
+
})),
|
|
62
|
+
cancel: sinon.stub().resolves(true)
|
|
63
|
+
};
|
|
64
|
+
const workspaceService = withWorkspaceServiceDefaults({
|
|
65
|
+
tryGetRoots: () => roots.map(root => ({ resource: new URI(root), isDirectory: true }))
|
|
66
|
+
});
|
|
67
|
+
container.bind(ShellExecutionServer).toConstantValue(shellServer as unknown as ShellExecutionServer);
|
|
68
|
+
container.bind(WorkspaceService).toConstantValue(workspaceService);
|
|
69
|
+
container.bind(ShellCommandPermissionService).toConstantValue({} as ShellCommandPermissionService);
|
|
70
|
+
container.bind(EnvVariablesServer).toConstantValue({ getHomeDirUri: async () => 'file:///home/test' } as unknown as EnvVariablesServer);
|
|
71
|
+
container.bind(ShellExecutionTool).toSelf();
|
|
72
|
+
tool = container.get(ShellExecutionTool);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const run = (args: { cwd?: string }): Promise<unknown> =>
|
|
76
|
+
Promise.resolve(tool.getTool().handler(JSON.stringify({ command: 'pwd', description: 'Print the working directory', ...args }), undefined));
|
|
77
|
+
|
|
78
|
+
const executedCwd = (): string | undefined => (shellServer.execute.lastCall.args[0] as ShellExecutionRequest).cwd;
|
|
79
|
+
|
|
80
|
+
it('defaults to the workspace root in a single-root workspace', async () => {
|
|
81
|
+
setUp(['file:///ws/theia']);
|
|
82
|
+
await run({});
|
|
83
|
+
expect(executedCwd()).to.equal('/ws/theia');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('runs in the root directory named by cwd, so the command must not change into it again', async () => {
|
|
87
|
+
setUp(['file:///ws/theia', 'file:///ws/other']);
|
|
88
|
+
await run({ cwd: 'theia' });
|
|
89
|
+
expect(executedCwd()).to.equal('/ws/theia');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('resolves a root name shared with another root by its parent-qualified name', async () => {
|
|
93
|
+
setUp(['file:///x/app', 'file:///y/app']);
|
|
94
|
+
await run({ cwd: 'y/app/src' });
|
|
95
|
+
expect(executedCwd()).to.equal('/y/app/src');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('resolves absolute paths, file URIs and the home directory', async () => {
|
|
99
|
+
setUp(['file:///ws/theia', 'file:///ws/other']);
|
|
100
|
+
await run({ cwd: '/tmp/build' });
|
|
101
|
+
expect(executedCwd()).to.equal('/tmp/build');
|
|
102
|
+
await run({ cwd: 'file:///ws/other/src' });
|
|
103
|
+
expect(executedCwd()).to.equal('/ws/other/src');
|
|
104
|
+
await run({ cwd: '~/projects' });
|
|
105
|
+
expect(executedCwd()).to.equal('/home/test/projects');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('rejects a basename shared by several roots, listing the root names', async () => {
|
|
109
|
+
setUp(['file:///x/app', 'file:///y/app']);
|
|
110
|
+
let error: Error | undefined;
|
|
111
|
+
try {
|
|
112
|
+
await run({ cwd: 'app' });
|
|
113
|
+
} catch (e) {
|
|
114
|
+
error = e;
|
|
115
|
+
}
|
|
116
|
+
expect(error?.message).to.contain('x/app (/x/app), y/app (/y/app)');
|
|
117
|
+
expect(shellServer.execute.called).to.be.false;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Agents often omit cwd on their first call and get this error (D1). It is required by design.
|
|
121
|
+
it('requires cwd in a multi-root workspace (D1)', async () => {
|
|
122
|
+
setUp(['file:///ws/theia', 'file:///ws/other']);
|
|
123
|
+
let error: Error | undefined;
|
|
124
|
+
try {
|
|
125
|
+
await run({});
|
|
126
|
+
} catch (e) {
|
|
127
|
+
error = e;
|
|
128
|
+
}
|
|
129
|
+
expect(error?.message).to.contain('A working directory (cwd) is required in a multi-root workspace');
|
|
130
|
+
expect(shellServer.execute.called).to.be.false;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
ShellExecutionToolResult,
|
|
24
24
|
ShellExecutionCanceledResult
|
|
25
25
|
} from '../common/shell-execution-server';
|
|
26
|
-
import { Path } from '@theia/core';
|
|
26
|
+
import { Path, URI } from '@theia/core';
|
|
27
|
+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
27
28
|
|
|
28
29
|
@injectable()
|
|
29
30
|
export class ShellExecutionTool extends AbstractShellExecutionTool {
|
|
@@ -31,6 +32,9 @@ export class ShellExecutionTool extends AbstractShellExecutionTool {
|
|
|
31
32
|
@inject(ShellCommandPermissionService)
|
|
32
33
|
protected readonly shellCommandPermissionService: ShellCommandPermissionService;
|
|
33
34
|
|
|
35
|
+
@inject(EnvVariablesServer)
|
|
36
|
+
protected readonly envVariablesServer: EnvVariablesServer;
|
|
37
|
+
|
|
34
38
|
getTool(): ToolRequest {
|
|
35
39
|
return {
|
|
36
40
|
id: SHELL_EXECUTION_FUNCTION_ID,
|
|
@@ -70,6 +74,10 @@ To avoid losing important information, limit output size in your commands:
|
|
|
70
74
|
- Use wc -l to count lines before fetching full output
|
|
71
75
|
- Redirect verbose output to /dev/null if not needed
|
|
72
76
|
|
|
77
|
+
WORKING DIRECTORY: The command runs inside the directory given as cwd, so relative paths in the command
|
|
78
|
+
are relative to it. In a multi-root workspace, cwd is required: a workspace root name such as "backend" is the
|
|
79
|
+
root folder itself, not a folder to change into.
|
|
80
|
+
|
|
73
81
|
TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer commands.`,
|
|
74
82
|
parameters: {
|
|
75
83
|
type: 'object',
|
|
@@ -103,10 +111,11 @@ TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer co
|
|
|
103
111
|
},
|
|
104
112
|
cwd: {
|
|
105
113
|
type: 'string',
|
|
106
|
-
description: 'Working directory
|
|
107
|
-
'Use a workspace
|
|
108
|
-
'or an absolute filesystem path. ' +
|
|
109
|
-
'
|
|
114
|
+
description: 'Working directory in which the command runs. ' +
|
|
115
|
+
'Use a workspace root name (e.g., "backend"), a path starting with a root name (e.g., "backend/src"), ' +
|
|
116
|
+
'as in the paths returned by the workspace tools, or an absolute filesystem path. ' +
|
|
117
|
+
'The command starts inside this directory: with cwd "backend", do not `cd backend` in the command. ' +
|
|
118
|
+
'Required in a multi-root workspace. If omitted in a single-root workspace, defaults to the workspace root.'
|
|
110
119
|
},
|
|
111
120
|
timeout: {
|
|
112
121
|
type: 'number',
|
|
@@ -147,7 +156,7 @@ TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer co
|
|
|
147
156
|
|
|
148
157
|
return this.runShellCommand({
|
|
149
158
|
command: args.command,
|
|
150
|
-
cwd: this.resolveCwd(args.cwd),
|
|
159
|
+
cwd: await this.resolveCwd(args.cwd),
|
|
151
160
|
timeout: args.timeout,
|
|
152
161
|
ctx
|
|
153
162
|
});
|
|
@@ -155,37 +164,42 @@ TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer co
|
|
|
155
164
|
|
|
156
165
|
/**
|
|
157
166
|
* Resolves a cwd value to an absolute filesystem path.
|
|
158
|
-
* Handles: undefined (falls back to workspace root), absolute paths
|
|
159
|
-
* root-
|
|
167
|
+
* Handles: undefined (falls back to the workspace root in a single-root workspace), absolute paths
|
|
168
|
+
* and `file://` URIs, `~` for the home directory, root-prefixed paths (`<rootName>/...`) and bare
|
|
169
|
+
* root names, with the root names of {@link WorkspaceService.getRootNames}.
|
|
160
170
|
*/
|
|
161
|
-
|
|
171
|
+
protected async resolveCwd(cwd: string | undefined): Promise<string> {
|
|
162
172
|
const roots = this.workspaceService.tryGetRoots();
|
|
163
173
|
|
|
164
|
-
const rootNames = roots.map(r => r.resource.path.base);
|
|
165
174
|
if (!cwd) {
|
|
166
175
|
if (roots.length === 1) {
|
|
167
176
|
return roots[0].resource.path.fsPath();
|
|
168
177
|
}
|
|
169
178
|
throw new Error(
|
|
170
179
|
'A working directory (cwd) is required in a multi-root workspace. ' +
|
|
171
|
-
`
|
|
172
|
-
|
|
180
|
+
`Provide a workspace root name as cwd (e.g., "${this.exampleRootName()}") or a path in it ` +
|
|
181
|
+
`(e.g., "${this.exampleRootName()}/src"). Available workspace roots: ${this.describeRoots()}.`
|
|
173
182
|
);
|
|
174
183
|
}
|
|
175
184
|
|
|
185
|
+
if (cwd.startsWith('file://')) {
|
|
186
|
+
return new URI(cwd).path.fsPath();
|
|
187
|
+
}
|
|
188
|
+
|
|
176
189
|
const normalized = Path.normalizePathSeparator(cwd);
|
|
177
190
|
|
|
178
|
-
if (new Path(normalized).isAbsolute) {
|
|
191
|
+
if (new Path(normalized).isAbsolute || Path.isDrive(normalized.split('/')[0])) {
|
|
179
192
|
return normalized;
|
|
180
193
|
}
|
|
181
194
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
195
|
+
if (normalized === '~' || normalized.startsWith('~/')) {
|
|
196
|
+
const home = new URI(await this.envVariablesServer.getHomeDirUri());
|
|
197
|
+
return (normalized === '~' ? home : home.resolve(normalized.substring(2))).path.fsPath();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const rootPrefixed = this.workspaceService.resolveRootPrefixedPath(normalized);
|
|
201
|
+
if (rootPrefixed) {
|
|
202
|
+
return rootPrefixed.path.fsPath();
|
|
189
203
|
}
|
|
190
204
|
|
|
191
205
|
if (roots.length === 1) {
|
|
@@ -195,8 +209,19 @@ TIMEOUT: Default 2 minutes, max 10 minutes. Specify higher timeout for longer co
|
|
|
195
209
|
throw new Error(
|
|
196
210
|
`Could not resolve working directory '${cwd}' to a workspace root. ` +
|
|
197
211
|
'In a multi-root workspace, prefix paths with the workspace root name ' +
|
|
198
|
-
`(e.g., '${
|
|
212
|
+
`(e.g., '${this.exampleRootName()}/path'). Available workspace roots: ${this.describeRoots()}.`
|
|
199
213
|
);
|
|
200
214
|
}
|
|
201
215
|
|
|
216
|
+
protected exampleRootName(): string {
|
|
217
|
+
return this.workspaceService.getRootNames().keys().next().value ?? 'rootName';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Lists the workspace root names with their locations, for error messages.
|
|
222
|
+
*/
|
|
223
|
+
protected describeRoots(): string {
|
|
224
|
+
return Array.from(this.workspaceService.getRootNames(), ([name, uri]) => `${name} (${uri.path.fsPath()})`).join(', ');
|
|
225
|
+
}
|
|
226
|
+
|
|
202
227
|
}
|