Merge PR #12: fix 3 issues (sendkeys, session limit regex, stop-and-wait dialog)
This commit is contained in:
+9
-1
@@ -1,4 +1,4 @@
|
|||||||
import { stripAnsi, isRateLimited, findRateLimitMessage } from './patterns.js';
|
import { stripAnsi, isRateLimited, findRateLimitMessage, isLimitPrompt } from './patterns.js';
|
||||||
import { parseResetTime, calculateWaitMs } from './time-parser.js';
|
import { parseResetTime, calculateWaitMs } from './time-parser.js';
|
||||||
import { capturePane, sendKeys, getPaneCommand, isProcessForeground } from './tmux.js';
|
import { capturePane, sendKeys, getPaneCommand, isProcessForeground } from './tmux.js';
|
||||||
import { loadConfig } from './config.js';
|
import { loadConfig } from './config.js';
|
||||||
@@ -67,6 +67,13 @@ export async function processOneTick(state, tmuxAdapter, pane, config, isAlive)
|
|||||||
return 'waiting';
|
return 'waiting';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect the interactive "What do you want to do?" prompt and auto-select "Stop and wait"
|
||||||
|
if (isLimitPrompt(stripped)) {
|
||||||
|
state.waitUntil = Date.now() + 30_000;
|
||||||
|
await tmuxAdapter.sendKeys(pane, ''); // Empty string = just press Enter
|
||||||
|
return 'prompt-confirmed';
|
||||||
|
}
|
||||||
|
|
||||||
return 'monitoring';
|
return 'monitoring';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +102,7 @@ export async function startMonitor(pane, pid) {
|
|||||||
}
|
}
|
||||||
if (result === 'retried') await logger.info(`Sent retry message (attempt ${state.attempts})`);
|
if (result === 'retried') await logger.info(`Sent retry message (attempt ${state.attempts})`);
|
||||||
if (result === 'user-continued') await logger.info('User already continued. Attempt counter reset.');
|
if (result === 'user-continued') await logger.info('User already continued. Attempt counter reset.');
|
||||||
|
if (result === 'prompt-confirmed') await logger.info('Limit prompt detected. Sent Enter to select "Stop and wait for limit to reset".');
|
||||||
if (result === 'max-retries') await logger.warn(`Max retries (${config.maxRetries}) reached. Monitor still active but will not send further retries until rate limit clears.`);
|
if (result === 'max-retries') await logger.warn(`Max retries (${config.maxRetries}) reached. Monitor still active but will not send further retries until rate limit clears.`);
|
||||||
if (result === 'skipped-not-claude') await logger.warn(`Foreground is "${state._lastForeground}", not Claude. Skipping send-keys. (Add to foregroundCommands in ~/.claude-auto-retry.json if this is wrong)`);
|
if (result === 'skipped-not-claude') await logger.warn(`Foreground is "${state._lastForeground}", not Claude. Skipping send-keys. (Add to foregroundCommands in ~/.claude-auto-retry.json if this is wrong)`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+12
-1
@@ -23,7 +23,7 @@ export function stripAnsi(text) {
|
|||||||
// Detection: find a "limit" line and a "resets" line within 6 lines of each other.
|
// Detection: find a "limit" line and a "resets" line within 6 lines of each other.
|
||||||
|
|
||||||
const LIMIT_PATTERNS = [
|
const LIMIT_PATTERNS = [
|
||||||
/(?:hit|exceeded|reached).*(?:your|the)\s*(?:\d+-hour\s+)?limit/i, // "hit/exceeded/reached your limit"
|
/(?:hit|exceeded|reached).*?limit/i, // "hit/exceeded/reached ... limit" (handles "You've hit your session limit")
|
||||||
/\d+-hour limit/i, // "5-hour limit"
|
/\d+-hour limit/i, // "5-hour limit"
|
||||||
/limit reached/i, // "limit reached"
|
/limit reached/i, // "limit reached"
|
||||||
/usage limit/i, // "usage limit"
|
/usage limit/i, // "usage limit"
|
||||||
@@ -85,3 +85,14 @@ export function findRateLimitMessage(text, customPatterns = []) {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detects the interactive Claude Code prompt asking user to wait or upgrade
|
||||||
|
// "What do you want to do?"
|
||||||
|
// "❯ 1. Stop and wait for limit to reset"
|
||||||
|
// " 2. Upgrade your plan"
|
||||||
|
// We auto-select option 1 by sending Enter
|
||||||
|
export function isLimitPrompt(text) {
|
||||||
|
const stripped = stripAnsi(text);
|
||||||
|
return /What do you want to do\?/i.test(stripped) &&
|
||||||
|
/Stop and wait.*limit/i.test(stripped);
|
||||||
|
}
|
||||||
|
|||||||
+17
-2
@@ -8,7 +8,13 @@ export function buildCaptureArgs(pane, lines = 200) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildSendKeysArgs(pane, text) {
|
export function buildSendKeysArgs(pane, text) {
|
||||||
return ['send-keys', '-t', pane, text, 'Enter'];
|
// Use -l flag to send text literally, then C-m to send (carriage return/Enter)
|
||||||
|
// This ensures special characters and spaces in the text are handled correctly
|
||||||
|
if (text) {
|
||||||
|
return [['send-keys', '-t', pane, '-l', text], ['send-keys', '-t', pane, 'C-m']];
|
||||||
|
}
|
||||||
|
// For empty string, just send C-m
|
||||||
|
return [['send-keys', '-t', pane, 'C-m']];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildDisplayArgs(pane, format) {
|
export function buildDisplayArgs(pane, format) {
|
||||||
@@ -32,7 +38,16 @@ export async function capturePane(pane, lines = 200) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendKeys(pane, text) {
|
export async function sendKeys(pane, text) {
|
||||||
await execFileAsync('tmux', buildSendKeysArgs(pane, text));
|
const args = buildSendKeysArgs(pane, text);
|
||||||
|
// If buildSendKeysArgs returns nested arrays (multiple commands), execute them sequentially
|
||||||
|
if (Array.isArray(args[0])) {
|
||||||
|
for (const cmdArgs of args) {
|
||||||
|
await execFileAsync('tmux', cmdArgs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single command
|
||||||
|
await execFileAsync('tmux', args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPaneCommand(pane) {
|
export async function getPaneCommand(pane) {
|
||||||
|
|||||||
Reference in New Issue
Block a user