fix(core): give complete intentional-sleep guidance on first rejection for sleep chains (#4948)

When the model tries `sleep 5 && cmd`, the old error message said 'split
follow-up commands into a separate invocation' but omitted the
`# intentional-sleep: <reason>` syntax. The model would then try standalone
`sleep 5`, get blocked a second time, and only then learn the escape hatch.

Now the first rejection for non-standalone sleep tells the model both steps:
split into two calls and use the intentional-sleep comment. This reduces
failures from 2 to 1.

Also reuses the already-computed `strippedCommand` variable instead of
calling `stripShellWrapper` a second time.
This commit is contained in:
tanzhenxin 2026-06-10 21:32:56 +08:00 committed by GitHub
parent d7327df399
commit be62f0e93d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 6 additions and 9 deletions

View file

@ -195,16 +195,15 @@ describe('ShellTool', () => {
expect(error).toBeNull();
});
it('should not suggest the intentional sleep comment for sleep chains', async () => {
it('should guide model to split and use intentional-sleep for sleep chains', async () => {
const error = shellTool.validateToolParams({
command: 'sleep 5 && echo ok',
is_background: false,
});
expect(error).toContain(
'intentional-sleep escape hatch only applies to standalone sleep commands',
);
expect(error).not.toContain('# intentional-sleep:');
expect(error).toContain('Split into two calls');
expect(error).toContain('intentional-sleep:');
expect(error).toContain('reason');
});
it('should throw an error for a relative directory path', async () => {

View file

@ -4361,16 +4361,14 @@ export class ShellTool extends BaseDeclarativeTool<
// `-c` script. This matches every other sensitive check in this file
// (directory, read-only, command-root extraction, etc.).
if (!params.is_background) {
const sleepPattern = detectBlockedSleepPatternDetails(
stripShellWrapper(params.command),
);
const sleepPattern = detectBlockedSleepPatternDetails(strippedCommand);
if (sleepPattern !== null) {
const intentionalSleepGuidance =
sleepPattern.intentionalSleepRejection ??
(sleepPattern.isStandalone
? 'If you genuinely need a standalone delay (rate limiting, deliberate pacing), ' +
'add a trailing comment like `# intentional-sleep: wait for MCP rate limit reset` (up to 10 minutes).'
: 'The intentional-sleep escape hatch only applies to standalone sleep commands; split follow-up commands into a separate invocation.');
: 'Split into two calls: first `sleep N # intentional-sleep: <reason>` (standalone), then the follow-up command.');
return (
`Blocked: ${sleepPattern.description}. ` +
'Run blocking commands in the background with is_background: true. ' +