dominds 1.25.5 → 1.25.6
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/dist/server/dominds-self-update.js +88 -4
- package/dist/tools/os.d.ts +2 -0
- package/dist/tools/os.js +54 -82
- package/dist/tools/prompts/codex_inspect_and_patch_tools/en/tools.md +2 -1
- package/dist/tools/prompts/codex_inspect_and_patch_tools/zh/tools.md +2 -1
- package/dist/tools/prompts/os/en/scenarios.md +1 -1
- package/dist/tools/prompts/os/en/tools.md +3 -2
- package/dist/tools/prompts/os/zh/scenarios.md +1 -1
- package/dist/tools/prompts/os/zh/tools.md +3 -2
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ const BACKGROUND_CHECK_INTERVAL_MS = 30 * 60 * 1000;
|
|
|
22
22
|
const LATEST_VERSION_CHECK_TIMEOUT_MS = 15000;
|
|
23
23
|
const RESTART_PORT_RELEASE_TIMEOUT_MS = 15000;
|
|
24
24
|
const RESTART_PORT_PROBE_INTERVAL_MS = 150;
|
|
25
|
+
const COMMAND_OUTPUT_LOG_LIMIT = 2000;
|
|
25
26
|
const IDLE_RESTART_STATE = { kind: 'idle' };
|
|
26
27
|
let runtimeConfig = null;
|
|
27
28
|
let latestObservation = { kind: 'unknown' };
|
|
@@ -84,6 +85,90 @@ function getRestartPortProbeHost(host) {
|
|
|
84
85
|
return '::1';
|
|
85
86
|
return host;
|
|
86
87
|
}
|
|
88
|
+
function truncateCommandOutput(value) {
|
|
89
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
90
|
+
if (raw.length <= COMMAND_OUTPUT_LOG_LIMIT)
|
|
91
|
+
return raw;
|
|
92
|
+
return `${raw.slice(0, COMMAND_OUTPUT_LOG_LIMIT)}...[truncated ${raw.length - COMMAND_OUTPUT_LOG_LIMIT} chars]`;
|
|
93
|
+
}
|
|
94
|
+
function formatPathEnvExcerpt(pathEnv) {
|
|
95
|
+
if (pathEnv === null || pathEnv.trim() === '')
|
|
96
|
+
return null;
|
|
97
|
+
const parts = pathEnv.split(path_1.default.delimiter).filter((part) => part.trim() !== '');
|
|
98
|
+
if (parts.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
const visibleParts = parts.slice(0, 8);
|
|
101
|
+
const preview = visibleParts.join(path_1.default.delimiter);
|
|
102
|
+
if (parts.length <= visibleParts.length)
|
|
103
|
+
return preview;
|
|
104
|
+
return `${preview}${path_1.default.delimiter}...[+${parts.length - visibleParts.length} more]`;
|
|
105
|
+
}
|
|
106
|
+
function getErrorProp(error, key) {
|
|
107
|
+
if (typeof error !== 'object' || error === null)
|
|
108
|
+
return undefined;
|
|
109
|
+
return error[key];
|
|
110
|
+
}
|
|
111
|
+
function extractCommandFailureDiagnostics(error) {
|
|
112
|
+
const code = getErrorProp(error, 'code');
|
|
113
|
+
const signal = getErrorProp(error, 'signal');
|
|
114
|
+
const killed = getErrorProp(error, 'killed');
|
|
115
|
+
const cmd = getErrorProp(error, 'cmd');
|
|
116
|
+
return {
|
|
117
|
+
message: error instanceof Error ? error.message : String(error),
|
|
118
|
+
cmd: typeof cmd === 'string' && cmd.trim() !== '' ? cmd : null,
|
|
119
|
+
code: typeof code === 'string' || typeof code === 'number' ? code : null,
|
|
120
|
+
signal: typeof signal === 'string' && signal.trim() !== '' ? signal : null,
|
|
121
|
+
killed: typeof killed === 'boolean' ? killed : null,
|
|
122
|
+
stdout: truncateCommandOutput(getErrorProp(error, 'stdout')),
|
|
123
|
+
stderr: truncateCommandOutput(getErrorProp(error, 'stderr')),
|
|
124
|
+
cwd: process.cwd(),
|
|
125
|
+
pathEnv: typeof process.env.PATH === 'string' ? process.env.PATH : null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function formatCommandFailureForUi(diagnostics) {
|
|
129
|
+
const lines = [diagnostics.message];
|
|
130
|
+
if (diagnostics.cmd !== null) {
|
|
131
|
+
lines.push(`cmd: ${diagnostics.cmd}`);
|
|
132
|
+
}
|
|
133
|
+
if (diagnostics.code !== null) {
|
|
134
|
+
lines.push(`exit: ${String(diagnostics.code)}`);
|
|
135
|
+
}
|
|
136
|
+
if (diagnostics.signal !== null) {
|
|
137
|
+
lines.push(`signal: ${diagnostics.signal}`);
|
|
138
|
+
}
|
|
139
|
+
lines.push(`cwd: ${diagnostics.cwd}`);
|
|
140
|
+
if (diagnostics.stderr !== '') {
|
|
141
|
+
lines.push(`stderr: ${diagnostics.stderr}`);
|
|
142
|
+
return lines.join('\n');
|
|
143
|
+
}
|
|
144
|
+
if (diagnostics.stdout !== '') {
|
|
145
|
+
lines.push(`stdout: ${diagnostics.stdout}`);
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
const pathEnvExcerpt = formatPathEnvExcerpt(diagnostics.pathEnv);
|
|
149
|
+
if (pathEnvExcerpt !== null) {
|
|
150
|
+
lines.push(`PATH: ${pathEnvExcerpt}`);
|
|
151
|
+
}
|
|
152
|
+
const lowerMessage = diagnostics.message.toLowerCase();
|
|
153
|
+
if (diagnostics.code === 'ENOENT' ||
|
|
154
|
+
lowerMessage.includes('not recognized as an internal or external command') ||
|
|
155
|
+
lowerMessage.includes('spawn npm enoent')) {
|
|
156
|
+
lines.push('hint: npm is not available to the server process on PATH');
|
|
157
|
+
}
|
|
158
|
+
else if (lowerMessage.includes('econn') ||
|
|
159
|
+
lowerMessage.includes('etimedout') ||
|
|
160
|
+
lowerMessage.includes('certificate') ||
|
|
161
|
+
lowerMessage.includes('self signed') ||
|
|
162
|
+
lowerMessage.includes('registry')) {
|
|
163
|
+
lines.push('hint: this looks like a registry, network, proxy, or certificate problem');
|
|
164
|
+
}
|
|
165
|
+
else if (diagnostics.code === 1 &&
|
|
166
|
+
diagnostics.stderr === '' &&
|
|
167
|
+
diagnostics.stdout === '') {
|
|
168
|
+
lines.push('hint: npm exited without output; check registry access and npm config in the same shell');
|
|
169
|
+
}
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
87
172
|
async function queryLatestVersion() {
|
|
88
173
|
const checkedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
|
|
89
174
|
try {
|
|
@@ -124,12 +209,11 @@ async function queryLatestVersion() {
|
|
|
124
209
|
return { kind: 'ok', latestVersion, checkedAt };
|
|
125
210
|
}
|
|
126
211
|
catch (error) {
|
|
212
|
+
const diagnostics = extractCommandFailureDiagnostics(error);
|
|
127
213
|
const errorText = error instanceof Error && error.name === 'AbortError'
|
|
128
214
|
? 'npm view dominds version timed out'
|
|
129
|
-
:
|
|
130
|
-
|
|
131
|
-
: String(error);
|
|
132
|
-
log.warn('Dominds latest-version check failed', error, { checkedAt, errorText });
|
|
215
|
+
: formatCommandFailureForUi(diagnostics);
|
|
216
|
+
log.warn('Dominds latest-version check failed', error, { checkedAt, errorText, diagnostics });
|
|
133
217
|
return {
|
|
134
218
|
kind: 'error',
|
|
135
219
|
errorText,
|
package/dist/tools/os.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Operating system interaction tools for shell command execution.
|
|
5
5
|
* Provides shell_cmd and stop_daemon FuncTools with advanced process management.
|
|
6
6
|
*/
|
|
7
|
+
import type { LanguageCode } from '@longrun-ai/kernel/types/language';
|
|
7
8
|
import type { FuncTool, ReminderOwner } from '../tool';
|
|
8
9
|
export declare function resetTrackedDaemonsForTests(): void;
|
|
9
10
|
type ShellSpawnSpec = Readonly<{
|
|
@@ -13,6 +14,7 @@ type ShellSpawnSpec = Readonly<{
|
|
|
13
14
|
windowsVerbatimArguments?: boolean;
|
|
14
15
|
}>;
|
|
15
16
|
export declare function resolveShellCmdSpawnSpecForTests(command: string, shell: string | undefined, platform: NodeJS.Platform): ShellSpawnSpec;
|
|
17
|
+
export declare function detectWindowsShellUsageWarningForTests(command: string, shell: string | undefined, language: LanguageCode, platform: NodeJS.Platform): string | undefined;
|
|
16
18
|
export declare function resolveReadonlyShellSpawnSpecForTests(command: string, platform: NodeJS.Platform): ShellSpawnSpec;
|
|
17
19
|
export declare const shellCmdReminderOwner: ReminderOwner;
|
|
18
20
|
export declare const shellCmdTool: FuncTool;
|
package/dist/tools/os.js
CHANGED
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.getDaemonOutputTool = exports.stopDaemonTool = exports.readonlyShellTool = exports.shellCmdTool = exports.shellCmdReminderOwner = void 0;
|
|
13
13
|
exports.resetTrackedDaemonsForTests = resetTrackedDaemonsForTests;
|
|
14
14
|
exports.resolveShellCmdSpawnSpecForTests = resolveShellCmdSpawnSpecForTests;
|
|
15
|
+
exports.detectWindowsShellUsageWarningForTests = detectWindowsShellUsageWarningForTests;
|
|
15
16
|
exports.resolveReadonlyShellSpawnSpecForTests = resolveReadonlyShellSpawnSpecForTests;
|
|
16
17
|
const time_1 = require("@longrun-ai/kernel/utils/time");
|
|
17
18
|
const child_process_1 = require("child_process");
|
|
@@ -645,7 +646,7 @@ async function spawnCmdRunner(init) {
|
|
|
645
646
|
runnerProcess.send(init);
|
|
646
647
|
});
|
|
647
648
|
}
|
|
648
|
-
function formatCompletedShellCommandOutput(message, t) {
|
|
649
|
+
function formatCompletedShellCommandOutput(message, t, warning) {
|
|
649
650
|
const stdoutHasScrolled = message.stdout.linesScrolledOut > 0;
|
|
650
651
|
const stderrHasScrolled = message.stderr.linesScrolledOut > 0;
|
|
651
652
|
let scrollNotice = '';
|
|
@@ -667,7 +668,7 @@ function formatCompletedShellCommandOutput(message, t) {
|
|
|
667
668
|
if (stderrContent !== '') {
|
|
668
669
|
result += `${t.stderrLabel}\n${fenceConsole}\n${stderrContent}\n${fenceEnd}`;
|
|
669
670
|
}
|
|
670
|
-
const content = result.trim();
|
|
671
|
+
const content = prependShellWarning(result.trim(), warning);
|
|
671
672
|
return message.exitCode === 0 ? (0, tool_1.toolSuccess)(content) : (0, tool_1.toolPartialFailure)(content);
|
|
672
673
|
}
|
|
673
674
|
async function removeDaemonRemindersForPid(dlg, pid) {
|
|
@@ -818,76 +819,42 @@ function parseGetDaemonOutputArgs(args) {
|
|
|
818
819
|
}
|
|
819
820
|
return { pid, stdout, stderr };
|
|
820
821
|
}
|
|
821
|
-
function
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
function stripMatchingOuterQuotes(value) {
|
|
825
|
-
const trimmed = value.trim();
|
|
826
|
-
if (trimmed.length < 2) {
|
|
827
|
-
return trimmed;
|
|
828
|
-
}
|
|
829
|
-
const first = trimmed[0] ?? '';
|
|
830
|
-
const last = trimmed[trimmed.length - 1] ?? '';
|
|
831
|
-
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
832
|
-
return trimmed.slice(1, -1);
|
|
833
|
-
}
|
|
834
|
-
return trimmed;
|
|
835
|
-
}
|
|
836
|
-
function resolveDirectWindowsPowerShellCommand(command) {
|
|
837
|
-
const match = /^\s*(powershell(?:\.exe)?|pwsh(?:\.exe)?)\s+(?:-(?:NoLogo|NoProfile|NonInteractive)\s+)*(?:-|\/)(?:Command|c)\s+([\s\S]+?)\s*$/iu.exec(command);
|
|
838
|
-
if (match === null) {
|
|
839
|
-
return null;
|
|
822
|
+
function getWindowsShellLabel(shell) {
|
|
823
|
+
if (typeof shell !== 'string') {
|
|
824
|
+
return 'cmd.exe';
|
|
840
825
|
}
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
return {
|
|
844
|
-
command: shellCommand,
|
|
845
|
-
args: ['-NoLogo', '-NoProfile', '-EncodedCommand', encodedCommand],
|
|
846
|
-
shellLabel: shellCommand,
|
|
847
|
-
};
|
|
826
|
+
const trimmed = shell.trim();
|
|
827
|
+
return trimmed === '' ? 'cmd.exe' : trimmed;
|
|
848
828
|
}
|
|
849
|
-
function
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
const inner = (match[1] ?? '').trim();
|
|
855
|
-
if (inner.length < 2) {
|
|
856
|
-
return inner;
|
|
857
|
-
}
|
|
858
|
-
const first = inner[0] ?? '';
|
|
859
|
-
const last = inner[inner.length - 1] ?? '';
|
|
860
|
-
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
861
|
-
return inner.slice(1, -1);
|
|
829
|
+
function detectWindowsShellUsageWarning(command, shell, language, platform = process.platform) {
|
|
830
|
+
if (platform !== 'win32') {
|
|
831
|
+
return undefined;
|
|
862
832
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
return command;
|
|
833
|
+
const trimmedCommand = command.trimStart();
|
|
834
|
+
const nestedCmd = /^cmd(?:\.exe)?\s+(?:\/d\s+)?(?:\/s\s+)?\/c\b/iu.test(trimmedCommand);
|
|
835
|
+
const nestedPowerShell = /^(?:powershell(?:\.exe)?|pwsh(?:\.exe)?)\s+(?:-(?:NoLogo|NoProfile|NonInteractive)\s+)*(?:-|\/)(?:Command|c)\b/iu.test(trimmedCommand);
|
|
836
|
+
if (!nestedCmd && !nestedPowerShell) {
|
|
837
|
+
return undefined;
|
|
869
838
|
}
|
|
870
|
-
const
|
|
871
|
-
if (
|
|
872
|
-
return nestedCmd
|
|
839
|
+
const shellLabel = getWindowsShellLabel(shell);
|
|
840
|
+
if (language === 'zh') {
|
|
841
|
+
return nestedCmd
|
|
842
|
+
? `⚠️ 检测到嵌套 shell 写法:${trimmedCommand.startsWith('cmd.exe') ? 'cmd.exe /c' : 'cmd /c'}。shell 参数只负责选择外层执行环境;请直接传入 cmd 原生命令,不要再套一层 cmd /c。当前 shell:${shellLabel}`
|
|
843
|
+
: `⚠️ 检测到嵌套 shell 写法:${trimmedCommand.startsWith('pwsh') ? 'pwsh -Command' : 'powershell -Command'}。shell 参数只负责选择外层执行环境;请直接传入 PowerShell 原生命令,不要再套一层 -Command。当前 shell:${shellLabel}`;
|
|
873
844
|
}
|
|
874
|
-
return
|
|
845
|
+
return nestedCmd
|
|
846
|
+
? `⚠️ Nested shell syntax detected: ${trimmedCommand.startsWith('cmd.exe') ? 'cmd.exe /c' : 'cmd /c'}. The shell parameter selects the outer execution environment only; pass a native cmd command and do not add another cmd /c layer. Current shell: ${shellLabel}`
|
|
847
|
+
: `⚠️ Nested shell syntax detected: ${trimmedCommand.startsWith('pwsh') ? 'pwsh -Command' : 'powershell -Command'}. The shell parameter selects the outer execution environment only; pass a native PowerShell command and do not add another -Command wrapper. Current shell: ${shellLabel}`;
|
|
875
848
|
}
|
|
876
|
-
function
|
|
877
|
-
if (
|
|
878
|
-
return
|
|
849
|
+
function prependShellWarning(content, warning) {
|
|
850
|
+
if (!warning) {
|
|
851
|
+
return content;
|
|
879
852
|
}
|
|
880
|
-
return
|
|
853
|
+
return `${warning}\n\n${content}`;
|
|
881
854
|
}
|
|
882
855
|
function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
883
856
|
const preferredShell = typeof shell === 'string' && shell.trim() !== '' ? shell.trim() : undefined;
|
|
884
857
|
if (platform === 'win32') {
|
|
885
|
-
if (!preferredShell) {
|
|
886
|
-
const directPowerShell = resolveDirectWindowsPowerShellCommand(command);
|
|
887
|
-
if (directPowerShell !== null) {
|
|
888
|
-
return directPowerShell;
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
858
|
if (preferredShell) {
|
|
892
859
|
const base = path_1.default.basename(preferredShell).toLowerCase();
|
|
893
860
|
if (base === 'powershell' ||
|
|
@@ -896,14 +863,14 @@ function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
|
896
863
|
base === 'pwsh.exe') {
|
|
897
864
|
return {
|
|
898
865
|
command: preferredShell,
|
|
899
|
-
args: ['-NoLogo', '-NoProfile', '-
|
|
866
|
+
args: ['-NoLogo', '-NoProfile', '-Command', command],
|
|
900
867
|
shellLabel: preferredShell,
|
|
901
868
|
};
|
|
902
869
|
}
|
|
903
870
|
if (base === 'cmd' || base === 'cmd.exe') {
|
|
904
871
|
return {
|
|
905
872
|
command: preferredShell,
|
|
906
|
-
args: ['/d', '/c',
|
|
873
|
+
args: ['/d', '/c', command],
|
|
907
874
|
shellLabel: preferredShell,
|
|
908
875
|
windowsVerbatimArguments: true,
|
|
909
876
|
};
|
|
@@ -916,7 +883,7 @@ function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
|
916
883
|
}
|
|
917
884
|
return {
|
|
918
885
|
command: 'cmd.exe',
|
|
919
|
-
args: ['/d', '/c',
|
|
886
|
+
args: ['/d', '/c', command],
|
|
920
887
|
shellLabel: 'cmd.exe',
|
|
921
888
|
windowsVerbatimArguments: true,
|
|
922
889
|
};
|
|
@@ -931,11 +898,14 @@ function resolveShellCmdSpawnSpec(command, shell, platform = process.platform) {
|
|
|
931
898
|
function resolveShellCmdSpawnSpecForTests(command, shell, platform) {
|
|
932
899
|
return resolveShellCmdSpawnSpec(command, shell, platform);
|
|
933
900
|
}
|
|
901
|
+
function detectWindowsShellUsageWarningForTests(command, shell, language, platform) {
|
|
902
|
+
return detectWindowsShellUsageWarning(command, shell, language, platform);
|
|
903
|
+
}
|
|
934
904
|
function resolveReadonlyShellSpawnSpec(command, platform = process.platform) {
|
|
935
905
|
if (platform === 'win32') {
|
|
936
906
|
return {
|
|
937
907
|
command: 'cmd.exe',
|
|
938
|
-
args: ['/d', '/c',
|
|
908
|
+
args: ['/d', '/c', command],
|
|
939
909
|
shellLabel: 'cmd.exe',
|
|
940
910
|
windowsVerbatimArguments: true,
|
|
941
911
|
};
|
|
@@ -1187,7 +1157,7 @@ const shellCmdSchema = {
|
|
|
1187
1157
|
},
|
|
1188
1158
|
shell: {
|
|
1189
1159
|
type: 'string',
|
|
1190
|
-
description: 'Shell to use for execution (default: bash on Linux/macOS; cmd.exe on Windows). On Windows, powershell.exe
|
|
1160
|
+
description: 'Shell to use for execution (default: bash on Linux/macOS; cmd.exe on Windows). On Windows, choose cmd.exe, powershell.exe, or pwsh explicitly; command must be native to the selected shell.',
|
|
1191
1161
|
},
|
|
1192
1162
|
scrollbackLines: {
|
|
1193
1163
|
type: 'number',
|
|
@@ -1536,10 +1506,10 @@ ${statusInfo}`,
|
|
|
1536
1506
|
exports.shellCmdTool = {
|
|
1537
1507
|
type: 'func',
|
|
1538
1508
|
name: 'shell_cmd',
|
|
1539
|
-
description: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1509
|
+
description: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. On Windows, use shell to choose cmd.exe, powershell.exe, or pwsh, and pass a command that is native to that shell. Do not nest cmd /c or powershell -Command inside another shell command. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1540
1510
|
descriptionI18n: {
|
|
1541
|
-
en: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1542
|
-
zh: '执行 shell 命令(支持超时)。如果 timeoutSeconds > 0
|
|
1511
|
+
en: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. On Windows, use shell to choose cmd.exe, powershell.exe, or pwsh, and pass a command that is native to that shell. Do not nest cmd /c or powershell -Command inside another shell command. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
1512
|
+
zh: '执行 shell 命令(支持超时)。如果 timeoutSeconds > 0 且命令运行时间超过超时,将转为可追踪的后台守护进程。Windows 上请用 shell 明确选择 cmd.exe、powershell.exe 或 pwsh,并传入该 shell 的原生命令。不要在另一个 shell 命令里再嵌套 cmd /c 或 powershell -Command。守护进程会跨消息持续存在,需要显式调用 stop_daemon 或 get_daemon_output 来管理与查看输出。',
|
|
1543
1513
|
},
|
|
1544
1514
|
parameters: shellCmdSchema,
|
|
1545
1515
|
async call(dlg, caller, args) {
|
|
@@ -1548,6 +1518,7 @@ exports.shellCmdTool = {
|
|
|
1548
1518
|
const parsedArgs = parseShellCmdArgs(args);
|
|
1549
1519
|
const { command, shell, scrollbackLines = 500, timeoutSeconds = 5 } = parsedArgs;
|
|
1550
1520
|
const spawnSpec = resolveShellCmdSpawnSpec(command, shell);
|
|
1521
|
+
const warning = detectWindowsShellUsageWarning(command, shell, language);
|
|
1551
1522
|
try {
|
|
1552
1523
|
const { runnerProcess, initialMessage } = await spawnCmdRunner({
|
|
1553
1524
|
type: 'init',
|
|
@@ -1564,11 +1535,11 @@ exports.shellCmdTool = {
|
|
|
1564
1535
|
scrollbackLines,
|
|
1565
1536
|
});
|
|
1566
1537
|
if (initialMessage.type === 'completed') {
|
|
1567
|
-
return formatCompletedShellCommandOutput(initialMessage, t);
|
|
1538
|
+
return formatCompletedShellCommandOutput(initialMessage, t, warning);
|
|
1568
1539
|
}
|
|
1569
1540
|
if (initialMessage.type === 'failed') {
|
|
1570
1541
|
disconnectRunnerProcess(runnerProcess);
|
|
1571
|
-
return (0, tool_1.toolFailure)(t.failedToExecute(initialMessage.errorText));
|
|
1542
|
+
return (0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(initialMessage.errorText), warning));
|
|
1572
1543
|
}
|
|
1573
1544
|
const daemon = {
|
|
1574
1545
|
pid: initialMessage.daemonPid,
|
|
@@ -1620,15 +1591,15 @@ exports.shellCmdTool = {
|
|
|
1620
1591
|
catch (error) {
|
|
1621
1592
|
await bestEffortKillDaemonProcessGroup(reminderSeedMeta);
|
|
1622
1593
|
disconnectRunnerProcess(runnerProcess);
|
|
1623
|
-
return (0, tool_1.toolFailure)(t.failedToExecute(error instanceof Error
|
|
1594
|
+
return (0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(error instanceof Error
|
|
1624
1595
|
? `daemon reminder persistence failed: ${error.message}`
|
|
1625
|
-
: `daemon reminder persistence failed: ${String(error)}`));
|
|
1596
|
+
: `daemon reminder persistence failed: ${String(error)}`), warning));
|
|
1626
1597
|
}
|
|
1627
1598
|
disconnectRunnerProcess(runnerProcess);
|
|
1628
|
-
return (0, tool_1.toolSuccess)(t.daemonStarted(initialMessage.daemonPid, timeoutSeconds, command));
|
|
1599
|
+
return (0, tool_1.toolSuccess)(prependShellWarning(t.daemonStarted(initialMessage.daemonPid, timeoutSeconds, command), warning));
|
|
1629
1600
|
}
|
|
1630
1601
|
catch (error) {
|
|
1631
|
-
return (0, tool_1.toolFailure)(t.failedToExecute(error instanceof Error ? error.message : String(error)));
|
|
1602
|
+
return (0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(error instanceof Error ? error.message : String(error)), warning));
|
|
1632
1603
|
}
|
|
1633
1604
|
},
|
|
1634
1605
|
};
|
|
@@ -2209,10 +2180,11 @@ exports.readonlyShellTool = {
|
|
|
2209
2180
|
const t = getOsToolMessages(language);
|
|
2210
2181
|
const parsedArgs = parseReadonlyShellArgs(args);
|
|
2211
2182
|
const { command, timeoutMs = 10000 } = parsedArgs;
|
|
2183
|
+
const warning = detectWindowsShellUsageWarning(command, undefined, language);
|
|
2212
2184
|
if (command.includes('\n') || command.includes('\r')) {
|
|
2213
|
-
return (0, tool_1.toolFailure)(language === 'zh'
|
|
2185
|
+
return (0, tool_1.toolFailure)(prependShellWarning(language === 'zh'
|
|
2214
2186
|
? `❌ readonly_shell 不建议执行多行脚本式命令(检测到换行符)。请用单行命令(允许 |、&&、||)。\n收到:${command}`
|
|
2215
|
-
: `❌ readonly_shell does not allow multi-line script-style commands (newline detected). Use a single-line command (|, &&, || are allowed).\nGot: ${command}
|
|
2187
|
+
: `❌ readonly_shell does not allow multi-line script-style commands (newline detected). Use a single-line command (|, &&, || are allowed).\nGot: ${command}`, warning));
|
|
2216
2188
|
}
|
|
2217
2189
|
const validation = validateReadonlyShellCommand(command);
|
|
2218
2190
|
if (!validation.ok) {
|
|
@@ -2222,9 +2194,9 @@ exports.readonlyShellTool = {
|
|
|
2222
2194
|
const suggestion = language === 'zh'
|
|
2223
2195
|
? getReadonlyShellSuggestionZh(validation.failure)
|
|
2224
2196
|
: getReadonlyShellSuggestionEn(validation.failure);
|
|
2225
|
-
return (0, tool_1.toolFailure)(language === 'zh'
|
|
2197
|
+
return (0, tool_1.toolFailure)(prependShellWarning(language === 'zh'
|
|
2226
2198
|
? `❌ readonly_shell 仅允许以下命令前缀:${allowedList}\n另外允许(仅版本探针):node --version|-v、python3 --version|-V\n脚本执行(如 node -e / python3 -c)一律拒绝。\n另外允许:git -C <相对路径> <show|status|diff|log|blame> ...\n另外允许:cd <相对路径> && <允许命令...>(或 ||)\n说明:通过 |/&&/|| 串联时会按子命令逐段校验。\n被拒子命令段:${rejectedSegmentOrCommand}\n允许的等价写法:${suggestion}\n收到:${command}`
|
|
2227
|
-
: `❌ readonly_shell only allows these command prefixes: ${allowedList}\nAlso allowed (exact version probes only): node --version|-v, python3 --version|-V\nNode/python scripts (for example: node -e, python3 -c) are rejected.\nAlso allowed: git -C <relative-path> <show|status|diff|log|blame> ...\nAlso allowed: cd <relative-path> && <allowed command...> (or ||)\nNote: chains via |/&&/|| are validated segment-by-segment.\nRejected segment: ${rejectedSegmentOrCommand}\nAllowed equivalent: ${suggestion}\nGot: ${command}
|
|
2199
|
+
: `❌ readonly_shell only allows these command prefixes: ${allowedList}\nAlso allowed (exact version probes only): node --version|-v, python3 --version|-V\nNode/python scripts (for example: node -e, python3 -c) are rejected.\nAlso allowed: git -C <relative-path> <show|status|diff|log|blame> ...\nAlso allowed: cd <relative-path> && <allowed command...> (or ||)\nNote: chains via |/&&/|| are validated segment-by-segment.\nRejected segment: ${rejectedSegmentOrCommand}\nAllowed equivalent: ${suggestion}\nGot: ${command}`, warning));
|
|
2228
2200
|
}
|
|
2229
2201
|
const forbiddenHiddenDir = detectReadonlyShellForbiddenHiddenDirAccess(path_1.default.resolve(process.cwd()), command);
|
|
2230
2202
|
if (forbiddenHiddenDir) {
|
|
@@ -2277,7 +2249,7 @@ exports.readonlyShellTool = {
|
|
|
2277
2249
|
? `⚠️ 输出已截断,约省略 ${omittedBytes} 字节\n`
|
|
2278
2250
|
: `⚠️ Output truncated; ~${omittedBytes} bytes omitted\n`
|
|
2279
2251
|
: '';
|
|
2280
|
-
let result = `${timeoutMsg}${truncationNotice}`.trimEnd();
|
|
2252
|
+
let result = prependShellWarning(`${timeoutMsg}${truncationNotice}`.trimEnd(), warning);
|
|
2281
2253
|
const stdoutContent = (0, output_limit_1.truncateToolOutputText)(stdoutBuffer.getContent(), {
|
|
2282
2254
|
toolName: 'readonly_shell_stdout',
|
|
2283
2255
|
}).text;
|
|
@@ -2308,7 +2280,7 @@ exports.readonlyShellTool = {
|
|
|
2308
2280
|
}).text;
|
|
2309
2281
|
const fenceConsole = '```console';
|
|
2310
2282
|
const fenceEnd = '```';
|
|
2311
|
-
let result = t.commandCompleted(code, truncationNotice);
|
|
2283
|
+
let result = prependShellWarning(t.commandCompleted(code, truncationNotice), warning);
|
|
2312
2284
|
if (stdoutContent) {
|
|
2313
2285
|
result += `${t.stdoutLabel}\n${fenceConsole}\n${stdoutContent}\n${fenceEnd}\n\n`;
|
|
2314
2286
|
}
|
|
@@ -2319,7 +2291,7 @@ exports.readonlyShellTool = {
|
|
|
2319
2291
|
});
|
|
2320
2292
|
childProcess.on('error', (error) => {
|
|
2321
2293
|
clearTimeout(timeoutHandle);
|
|
2322
|
-
finish((0, tool_1.toolFailure)(t.failedToExecute(error.message)));
|
|
2294
|
+
finish((0, tool_1.toolFailure)(prependShellWarning(t.failedToExecute(error.message), warning)));
|
|
2323
2295
|
});
|
|
2324
2296
|
});
|
|
2325
2297
|
},
|
|
@@ -15,7 +15,8 @@ Typical uses:
|
|
|
15
15
|
Windows notes:
|
|
16
16
|
|
|
17
17
|
- Prefer no-space forward-slash paths such as `D:/path/to/file`
|
|
18
|
-
-
|
|
18
|
+
- `readonly_shell` runs through the platform default shell; pass native commands for that shell
|
|
19
|
+
- Avoid nested `cmd /c` or `powershell -Command`; obvious nested-shell patterns may only warn and are not rewritten
|
|
19
20
|
|
|
20
21
|
Example:
|
|
21
22
|
|
|
@@ -194,7 +194,7 @@ shell_cmd({
|
|
|
194
194
|
command: 'if exist D:/AiWorks/chatgpt-workstation/dist/app.exe echo exists',
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
// Complex PowerShell command: choose PowerShell explicitly
|
|
197
|
+
// Complex PowerShell command: choose PowerShell explicitly and keep the command native to that shell
|
|
198
198
|
shell_cmd({
|
|
199
199
|
command: 'Test-Path D:/AiWorks/chatgpt-workstation/dist/app.exe',
|
|
200
200
|
shell: 'powershell.exe',
|
|
@@ -32,9 +32,10 @@ Execute Shell command.
|
|
|
32
32
|
|
|
33
33
|
**Windows notes:**
|
|
34
34
|
|
|
35
|
+
- Use `shell` to choose the outer execution environment: `cmd.exe`, `powershell.exe`, or `pwsh`
|
|
36
|
+
- Pass a command that is native to the selected shell; do not nest `cmd /c` or `powershell -Command` inside another shell command
|
|
35
37
|
- With the default `cmd.exe` path, prefer no-space forward-slash paths such as `D:/path/to/file`
|
|
36
|
-
-
|
|
37
|
-
- Do not rely on nested `cmd /c "..."`
|
|
38
|
+
- Only very obvious nested-shell patterns may trigger a warning; the tool does not rewrite mixed shell syntax for you
|
|
38
39
|
|
|
39
40
|
**Returns:**
|
|
40
41
|
|
|
@@ -194,7 +194,7 @@ shell_cmd({
|
|
|
194
194
|
command: 'if exist D:/AiWorks/chatgpt-workstation/dist/app.exe echo exists',
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
// 复杂 PowerShell 命令:显式选择 PowerShell
|
|
197
|
+
// 复杂 PowerShell 命令:显式选择 PowerShell,并保持命令为该 shell 的原生命令
|
|
198
198
|
shell_cmd({
|
|
199
199
|
command: 'Test-Path D:/AiWorks/chatgpt-workstation/dist/app.exe',
|
|
200
200
|
shell: 'powershell.exe',
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
|
|
33
33
|
**Windows 提示:**
|
|
34
34
|
|
|
35
|
+
- 使用 `shell` 选择外层执行环境:`cmd.exe`、`powershell.exe` 或 `pwsh`
|
|
36
|
+
- `command` 必须是所选 shell 的原生命令;不要在另一个 shell 命令里嵌套 `cmd /c` 或 `powershell -Command`
|
|
35
37
|
- 默认 `cmd.exe` 路径下,优先使用不带空格的正斜杠路径,如 `D:/path/to/file`
|
|
36
|
-
-
|
|
37
|
-
- 不要依赖 `cmd /c "..."`
|
|
38
|
+
- 只有非常明显的混套模式可能触发警告;工具不会替你改写混合 shell 语法
|
|
38
39
|
|
|
39
40
|
**返回:**
|
|
40
41
|
|