Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/store/tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ vi.mock('../lib/log', () => ({
warn: vi.fn(),
}));

import { sendPrompt } from './tasks';
import { pasteDelayMs, sendPrompt } from './tasks';

function writePayloads(): string[] {
return mockInvoke.mock.calls
Expand Down Expand Up @@ -180,3 +180,19 @@ describe('sendPrompt', () => {
expect(writePayloads()[1]).toContain('IMPORTANT: Maintain .claude/steps.json');
});
});

describe('pasteDelayMs', () => {
it('returns 50ms for a short single-line prompt', () => {
expect(pasteDelayMs('hello')).toBe(50);
});

it('scales by line count for a ~31-line prompt', () => {
const text = Array.from({ length: 31 }, (_, i) => `line ${i + 1}`).join('\n');
expect(pasteDelayMs(text)).toBe(Math.min(500, Math.max(50, 31 * 15)));
});

it('caps at 500ms for a very large prompt', () => {
const text = Array.from({ length: 100 }, (_, i) => `line ${i + 1}`).join('\n');
expect(pasteDelayMs(text)).toBe(500);
});
});
18 changes: 14 additions & 4 deletions src/store/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

// Delay between writing pasted text and the Enter key. Claude Code (and other
// TUI agents) process bracketed paste asynchronously — if \r arrives before
// the paste is fully consumed, it gets absorbed into the input buffer instead
// of submitting it. We scale the delay by line count so large prompts (e.g.
// initial task prompts of 30+ lines) reliably submit. Cap at 500ms to avoid
// noticeable lag on normal sends.
export function pasteDelayMs(text: string): number {
const lines = text.split('\n').length;
return Math.min(500, Math.max(50, lines * 15));
}

function isAgentNotFoundError(err: unknown): boolean {
return String(err).toLowerCase().includes('agent not found');
}
Expand Down Expand Up @@ -496,13 +507,12 @@ export async function sendPrompt(taskId: string, agentId: string, text: string):
// bracketed paste, wrap only the prompt text; this avoids Codex's paste-burst
// guard treating rapid synthetic keystrokes plus Enter as a paste.
setTaskLastInputAt(taskId);
const useBracketed = isAgentBracketedPasteEnabled(agentId);
await writeToAgentWhenReady(
agentId,
isAgentBracketedPasteEnabled(agentId)
? `${BRACKETED_PASTE_START}${effectiveText}${BRACKETED_PASTE_END}`
: effectiveText,
useBracketed ? `${BRACKETED_PASTE_START}${effectiveText}${BRACKETED_PASTE_END}` : effectiveText,
);
await new Promise((r) => setTimeout(r, 50));
await new Promise((r) => setTimeout(r, pasteDelayMs(effectiveText)));
await writeToAgentWhenReady(agentId, '\r');
setStore('tasks', taskId, 'lastPrompt', text);
if (task && !hasPromptedAgent) {
Expand Down
Loading