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.
@@ -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
- : error instanceof Error
130
- ? error.message
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,
@@ -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 encodePowerShellCommand(command) {
822
- return Buffer.from(command, 'utf16le').toString('base64');
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 shellCommand = match[1] ?? '';
842
- const encodedCommand = encodePowerShellCommand(stripMatchingOuterQuotes(match[2] ?? ''));
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 unwrapNestedWindowsCmd(command) {
850
- const match = /^\s*cmd(?:\.exe)?\s+(?:\/d\s+)?(?:\/s\s+)?\/c\s+([\s\S]+?)\s*$/iu.exec(command);
851
- if (match === null) {
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
- return inner;
864
- }
865
- function normalizeWindowsCmdCommand(command) {
866
- const directPowerShell = resolveDirectWindowsPowerShellCommand(command);
867
- if (directPowerShell !== null) {
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 nestedCmd = unwrapNestedWindowsCmd(command);
871
- if (nestedCmd !== null) {
872
- return nestedCmd.replace(/\\"/g, '"').replace(/^(\s*if\s+(?:not\s+)?exist\s+)'([^'\r\n]+)'/iu, (_whole, prefix, quotedPath) => `${prefix}"${normalizeWindowsPathLiteral(quotedPath)}"`);
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 command.replace(/\\"/g, '"').replace(/^(\s*if\s+(?:not\s+)?exist\s+)'([^'\r\n]+)'/iu, (_whole, prefix, quotedPath) => `${prefix}"${normalizeWindowsPathLiteral(quotedPath)}"`);
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 normalizeWindowsPathLiteral(pathLiteral) {
877
- if (pathLiteral.startsWith('\\\\')) {
878
- return pathLiteral;
849
+ function prependShellWarning(content, warning) {
850
+ if (!warning) {
851
+ return content;
879
852
  }
880
- return pathLiteral.replace(/\\{2,}/g, '\\');
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', '-EncodedCommand', encodePowerShellCommand(command)],
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', normalizeWindowsCmdCommand(command)],
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', normalizeWindowsCmdCommand(command)],
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', normalizeWindowsCmdCommand(command)],
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/pwsh commands are passed via -EncodedCommand.',
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 且命令运行时间超过超时,将转为可追踪的后台守护进程。守护进程会跨消息持续存在,需要显式调用 stop_daemon 或 get_daemon_output 来管理与查看输出。',
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
- - Avoid nested `cmd /c "..."`
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
 
@@ -15,7 +15,8 @@
15
15
  Windows 注意:
16
16
 
17
17
  - 优先使用不带空格的正斜杠路径,如 `D:/path/to/file`
18
- - 避免嵌套 `cmd /c "..."`
18
+ - `readonly_shell` 通过平台默认 shell 执行;请传入该 shell 的原生命令
19
+ - 避免嵌套 `cmd /c` 或 `powershell -Command`;明显混套模式最多只会警告,不会被改写
19
20
 
20
21
  示例:
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; Dominds encodes the command automatically
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
- - For complex PowerShell commands, pass `shell: 'powershell.exe'` or `shell: 'pwsh'`; the command is encoded automatically to avoid quote and backslash ambiguity
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
- - 复杂 PowerShell 命令建议显式传 `shell: 'powershell.exe'` 或 `shell: 'pwsh'`;系统会自动用编码方式传递命令,避免引号和反斜杠歧义
37
- - 不要依赖 `cmd /c "..."`
38
+ - 只有非常明显的混套模式可能触发警告;工具不会替你改写混合 shell 语法
38
39
 
39
40
  **返回:**
40
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dominds",
3
- "version": "1.25.5",
3
+ "version": "1.25.6",
4
4
  "description": "Dominds CLI and aggregation shell for the LongRun AI kernel/runtime packages.",
5
5
  "type": "commonjs",
6
6
  "publishConfig": {