mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
refactor: codebase cleanup — dead code, regex helper, centralized constants, tests
- Remove unused validateTokenCounts/validateTokensAndCost exports and PlanPhase type alias - Add execPattern() helper to eliminate 8 repetitive .lastIndex=0 + exec() loops - Centralize 11 magic number constants into config/ai-defaults.ts and config/server-timing.ts - Remove stale src/tui from tsconfig.json exclude - Fix CLAUDE.md inaccuracies (session helpers, app.js line count, module count) - Add 316 new tests: LRUMap (38), StaleExpirationMap (42), BufferAccumulator (33), respawn helpers (142), system-routes expansion (11→61) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ import { join } from 'node:path';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { getAugmentedPath } from './utils/claude-cli-resolver.js';
|
||||
import { ANSI_ESCAPE_PATTERN_SIMPLE } from './utils/index.js';
|
||||
import { AI_CHECK_MAX_BACKOFF_MS } from './config/ai-defaults.js';
|
||||
|
||||
// ========== Security Validation ==========
|
||||
|
||||
@@ -534,10 +535,7 @@ export abstract class AiCheckerBase<
|
||||
// P1-005: Exponential backoff for errors
|
||||
// Base cooldown * 2^(consecutiveErrors-1), capped at 5 minutes
|
||||
const backoffMultiplier = Math.pow(2, this.consecutiveErrors - 1);
|
||||
const backoffCooldownMs = Math.min(
|
||||
this.config.errorCooldownMs * backoffMultiplier,
|
||||
5 * 60 * 1000 // Max 5 minutes
|
||||
);
|
||||
const backoffCooldownMs = Math.min(this.config.errorCooldownMs * backoffMultiplier, AI_CHECK_MAX_BACKOFF_MS);
|
||||
this.log(`Exponential backoff: ${Math.round(backoffCooldownMs / 1000)}s (error #${this.consecutiveErrors})`);
|
||||
this.startCooldown(backoffCooldownMs);
|
||||
}
|
||||
|
||||
+12
-5
@@ -30,7 +30,14 @@ import {
|
||||
type AiCheckerResultBase,
|
||||
type AiCheckerStateBase,
|
||||
} from './ai-checker-base.js';
|
||||
import { AI_CHECK_MODEL, AI_IDLE_CHECK_MAX_CONTEXT } from './config/ai-defaults.js';
|
||||
import {
|
||||
AI_CHECK_MODEL,
|
||||
AI_IDLE_CHECK_MAX_CONTEXT,
|
||||
AI_IDLE_CHECK_TIMEOUT_MS,
|
||||
AI_IDLE_CHECK_COOLDOWN_MS,
|
||||
AI_IDLE_CHECK_ERROR_COOLDOWN_MS,
|
||||
AI_CHECK_MAX_CONSECUTIVE_ERRORS,
|
||||
} from './config/ai-defaults.js';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
@@ -48,10 +55,10 @@ const DEFAULT_AI_CHECK_CONFIG: AiIdleCheckConfig = {
|
||||
enabled: true,
|
||||
model: AI_CHECK_MODEL,
|
||||
maxContextChars: AI_IDLE_CHECK_MAX_CONTEXT,
|
||||
checkTimeoutMs: 90000,
|
||||
cooldownMs: 180000,
|
||||
errorCooldownMs: 60000,
|
||||
maxConsecutiveErrors: 3,
|
||||
checkTimeoutMs: AI_IDLE_CHECK_TIMEOUT_MS,
|
||||
cooldownMs: AI_IDLE_CHECK_COOLDOWN_MS,
|
||||
errorCooldownMs: AI_IDLE_CHECK_ERROR_COOLDOWN_MS,
|
||||
maxConsecutiveErrors: AI_CHECK_MAX_CONSECUTIVE_ERRORS,
|
||||
};
|
||||
|
||||
/** Pattern to match IDLE or WORKING as the first word of output */
|
||||
|
||||
+12
-5
@@ -29,7 +29,14 @@ import {
|
||||
type AiCheckerResultBase,
|
||||
type AiCheckerStateBase,
|
||||
} from './ai-checker-base.js';
|
||||
import { AI_CHECK_MODEL, AI_PLAN_CHECK_MAX_CONTEXT } from './config/ai-defaults.js';
|
||||
import {
|
||||
AI_CHECK_MODEL,
|
||||
AI_PLAN_CHECK_MAX_CONTEXT,
|
||||
AI_PLAN_CHECK_TIMEOUT_MS,
|
||||
AI_PLAN_CHECK_COOLDOWN_MS,
|
||||
AI_PLAN_CHECK_ERROR_COOLDOWN_MS,
|
||||
AI_CHECK_MAX_CONSECUTIVE_ERRORS,
|
||||
} from './config/ai-defaults.js';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
@@ -47,10 +54,10 @@ const DEFAULT_PLAN_CHECK_CONFIG: AiPlanCheckConfig = {
|
||||
enabled: true,
|
||||
model: AI_CHECK_MODEL,
|
||||
maxContextChars: AI_PLAN_CHECK_MAX_CONTEXT,
|
||||
checkTimeoutMs: 60000,
|
||||
cooldownMs: 30000,
|
||||
errorCooldownMs: 30000,
|
||||
maxConsecutiveErrors: 3,
|
||||
checkTimeoutMs: AI_PLAN_CHECK_TIMEOUT_MS,
|
||||
cooldownMs: AI_PLAN_CHECK_COOLDOWN_MS,
|
||||
errorCooldownMs: AI_PLAN_CHECK_ERROR_COOLDOWN_MS,
|
||||
maxConsecutiveErrors: AI_CHECK_MAX_CONSECUTIVE_ERRORS,
|
||||
};
|
||||
|
||||
/** Pattern to match PLAN_MODE or NOT_PLAN_MODE as the first word(s) of output */
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
/**
|
||||
* @fileoverview Default model and context limits for AI-powered checkers.
|
||||
* @fileoverview Default model, context limits, and timing for AI-powered checkers.
|
||||
*
|
||||
* Centralizes the AI model identifier and context window sizes used by
|
||||
* the idle checker, plan checker, respawn controller defaults, and
|
||||
* respawn route fallbacks. Change the model here when upgrading.
|
||||
* Centralizes the AI model identifier, context window sizes, and timeout/cooldown
|
||||
* defaults used by the idle checker, plan checker, respawn controller defaults,
|
||||
* and respawn route fallbacks. Change values here when tuning AI check behavior.
|
||||
*
|
||||
* @module config/ai-defaults
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Model & Context
|
||||
// ============================================================================
|
||||
|
||||
/** Default model for AI idle and plan checkers */
|
||||
export const AI_CHECK_MODEL = 'claude-opus-4-5-20251101';
|
||||
|
||||
@@ -16,3 +20,39 @@ export const AI_IDLE_CHECK_MAX_CONTEXT = 16000;
|
||||
|
||||
/** Max context chars for plan checker (~2k tokens, plan mode UI is compact) */
|
||||
export const AI_PLAN_CHECK_MAX_CONTEXT = 8000;
|
||||
|
||||
// ============================================================================
|
||||
// AI Idle Checker Timing
|
||||
// ============================================================================
|
||||
|
||||
/** Timeout for AI idle check (90 seconds — thinking can be slow) */
|
||||
export const AI_IDLE_CHECK_TIMEOUT_MS = 90_000;
|
||||
|
||||
/** Cooldown after WORKING verdict (3 minutes) */
|
||||
export const AI_IDLE_CHECK_COOLDOWN_MS = 180_000;
|
||||
|
||||
/** Cooldown after AI idle check error (1 minute) */
|
||||
export const AI_IDLE_CHECK_ERROR_COOLDOWN_MS = 60_000;
|
||||
|
||||
// ============================================================================
|
||||
// AI Plan Checker Timing
|
||||
// ============================================================================
|
||||
|
||||
/** Timeout for AI plan check (60 seconds — allows time for thinking) */
|
||||
export const AI_PLAN_CHECK_TIMEOUT_MS = 60_000;
|
||||
|
||||
/** Cooldown after NOT_PLAN_MODE verdict (30 seconds) */
|
||||
export const AI_PLAN_CHECK_COOLDOWN_MS = 30_000;
|
||||
|
||||
/** Cooldown after AI plan check error (30 seconds) */
|
||||
export const AI_PLAN_CHECK_ERROR_COOLDOWN_MS = 30_000;
|
||||
|
||||
// ============================================================================
|
||||
// Shared AI Checker Limits
|
||||
// ============================================================================
|
||||
|
||||
/** Max consecutive errors before disabling an AI checker */
|
||||
export const AI_CHECK_MAX_CONSECUTIVE_ERRORS = 3;
|
||||
|
||||
/** Maximum exponential backoff cap for AI checker errors (5 minutes) */
|
||||
export const AI_CHECK_MAX_BACKOFF_MS = 5 * 60 * 1000;
|
||||
|
||||
@@ -73,3 +73,16 @@ export const MAX_CONSECUTIVE_ERRORS = 5;
|
||||
|
||||
/** Error counter reset interval — forgives errors after quiet period (ms) */
|
||||
export const ERROR_RESET_MS = 60_000;
|
||||
|
||||
// ============================================================================
|
||||
// Common Cleanup Intervals
|
||||
// ============================================================================
|
||||
|
||||
/** Standard 1-minute cleanup/check interval used by multiple subsystems (ms) */
|
||||
export const CLEANUP_CHECK_INTERVAL_MS = 60_000;
|
||||
|
||||
/** Standard 1-hour max age for stale/completed data (ms) */
|
||||
export const STALE_DATA_MAX_AGE_MS = 60 * 60 * 1000;
|
||||
|
||||
/** Standard 5-minute inactivity timeout for streams and caches (ms) */
|
||||
export const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { existsSync, statSync, realpathSync } from 'node:fs';
|
||||
import { resolve, relative, isAbsolute } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { CLEANUP_CHECK_INTERVAL_MS, INACTIVITY_TIMEOUT_MS } from './config/server-timing.js';
|
||||
|
||||
// ========== Configuration Constants ==========
|
||||
|
||||
@@ -39,7 +40,7 @@ const MAX_STREAMS_PER_SESSION = 5;
|
||||
* Inactivity timeout for streams (5 minutes).
|
||||
* Streams with no data for this long will be auto-closed.
|
||||
*/
|
||||
const STREAM_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
const STREAM_INACTIVITY_TIMEOUT_MS = INACTIVITY_TIMEOUT_MS;
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
@@ -129,7 +130,7 @@ export class FileStreamManager extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
// Start cleanup timer for inactive streams
|
||||
this.cleanupTimer = setInterval(() => this.cleanupInactiveStreams(), 60 * 1000);
|
||||
this.cleanupTimer = setInterval(() => this.cleanupInactiveStreams(), CLEANUP_CHECK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
// ========== Public Methods ==========
|
||||
|
||||
+3
-4
@@ -9,6 +9,7 @@
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { execPattern } from './utils/index.js';
|
||||
|
||||
// Pattern to extract completion phrase from CLAUDE.md
|
||||
// Matches <promise>PHRASE</promise> with optional whitespace
|
||||
@@ -83,9 +84,7 @@ export function parseRalphLoopConfigFromContent(content: string): RalphLoopConfi
|
||||
};
|
||||
|
||||
// Parse each YAML line
|
||||
let match;
|
||||
YAML_LINE_PATTERN.lastIndex = 0;
|
||||
while ((match = YAML_LINE_PATTERN.exec(yaml)) !== null) {
|
||||
execPattern(YAML_LINE_PATTERN, yaml, (match) => {
|
||||
const key = match[1].toLowerCase();
|
||||
const value = match[2].trim();
|
||||
|
||||
@@ -103,7 +102,7 @@ export function parseRalphLoopConfigFromContent(content: string): RalphLoopConfi
|
||||
config.completionPromise = value.toUpperCase();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { CLEANUP_CHECK_INTERVAL_MS } from './config/server-timing.js';
|
||||
|
||||
/**
|
||||
* RalphStallDetector - Detects iteration stalls in the Ralph loop.
|
||||
@@ -57,7 +58,7 @@ export class RalphStallDetector extends EventEmitter {
|
||||
// Check every minute
|
||||
this._iterationStallTimer = setInterval(() => {
|
||||
this.checkIterationStall();
|
||||
}, 60 * 1000);
|
||||
}, CLEANUP_CHECK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+17
-21
@@ -55,6 +55,7 @@ import {
|
||||
stringSimilarity,
|
||||
Debouncer,
|
||||
CleanupManager,
|
||||
execPattern,
|
||||
} from './utils/index.js';
|
||||
import { MAX_LINE_BUFFER_SIZE } from './config/buffer-limits.js';
|
||||
import { MAX_TODOS_PER_SESSION } from './config/map-limits.js';
|
||||
@@ -63,6 +64,7 @@ import type { EnhancedPlanTask, CheckpointReview } from './ralph-plan-tracker.js
|
||||
import { RalphFixPlanWatcher, generateFixPlanMarkdown, importFixPlanMarkdown } from './ralph-fix-plan-watcher.js';
|
||||
import { RalphStallDetector } from './ralph-stall-detector.js';
|
||||
import { RalphStatusParser } from './ralph-status-parser.js';
|
||||
import { STALE_DATA_MAX_AGE_MS, INACTIVITY_TIMEOUT_MS } from './config/server-timing.js';
|
||||
|
||||
// Re-export sub-module types for backward compatibility
|
||||
export type { EnhancedPlanTask, CheckpointReview } from './ralph-plan-tracker.js';
|
||||
@@ -72,9 +74,9 @@ export type { EnhancedPlanTask, CheckpointReview } from './ralph-plan-tracker.js
|
||||
|
||||
/**
|
||||
* Todo items older than this duration (in milliseconds) will be auto-expired.
|
||||
* Default: 1 hour (60 * 60 * 1000)
|
||||
* Default: 1 hour
|
||||
*/
|
||||
const TODO_EXPIRY_MS = 60 * 60 * 1000;
|
||||
const TODO_EXPIRY_MS = STALE_DATA_MAX_AGE_MS;
|
||||
|
||||
/**
|
||||
* Minimum interval between on-demand cleanup checks (in milliseconds).
|
||||
@@ -88,7 +90,7 @@ const CLEANUP_THROTTLE_MS = 30 * 1000;
|
||||
* Actively purges expired todos even when no terminal data is flowing.
|
||||
* Default: 5 minutes
|
||||
*/
|
||||
const TODO_CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
|
||||
const TODO_CLEANUP_INTERVAL_MS = INACTIVITY_TIMEOUT_MS;
|
||||
|
||||
/**
|
||||
* Similarity threshold for todo deduplication.
|
||||
@@ -1666,35 +1668,32 @@ export class RalphTracker extends EventEmitter {
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
if (hasCheckbox) {
|
||||
TODO_CHECKBOX_PATTERN.lastIndex = 0;
|
||||
while ((match = TODO_CHECKBOX_PATTERN.exec(line)) !== null) {
|
||||
execPattern(TODO_CHECKBOX_PATTERN, line, (match) => {
|
||||
const checked = match[1].toLowerCase() === 'x';
|
||||
const content = match[2].trim();
|
||||
const status: RalphTodoStatus = checked ? 'completed' : 'pending';
|
||||
this.upsertTodo(content, status);
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (hasTodoIndicator) {
|
||||
TODO_INDICATOR_PATTERN.lastIndex = 0;
|
||||
while ((match = TODO_INDICATOR_PATTERN.exec(line)) !== null) {
|
||||
execPattern(TODO_INDICATOR_PATTERN, line, (match) => {
|
||||
const icon = match[1];
|
||||
const content = match[2].trim();
|
||||
const status = this.iconToStatus(icon);
|
||||
this.upsertTodo(content, status);
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (hasStatus) {
|
||||
TODO_STATUS_PATTERN.lastIndex = 0;
|
||||
while ((match = TODO_STATUS_PATTERN.exec(line)) !== null) {
|
||||
execPattern(TODO_STATUS_PATTERN, line, (match) => {
|
||||
const content = match[1].trim();
|
||||
const status = match[2] as RalphTodoStatus;
|
||||
this.upsertTodo(content, status);
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (hasNativeCheckbox) {
|
||||
@@ -1715,8 +1714,7 @@ export class RalphTracker extends EventEmitter {
|
||||
}
|
||||
|
||||
if (hasCheckmark) {
|
||||
TODO_TASK_CREATED_PATTERN.lastIndex = 0;
|
||||
while ((match = TODO_TASK_CREATED_PATTERN.exec(line)) !== null) {
|
||||
execPattern(TODO_TASK_CREATED_PATTERN, line, (match) => {
|
||||
const taskNum = parseInt(match[1], 10);
|
||||
const content = match[2].trim();
|
||||
if (content.length >= 5) {
|
||||
@@ -1725,10 +1723,9 @@ export class RalphTracker extends EventEmitter {
|
||||
this.upsertTodo(content, 'pending');
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TODO_TASK_SUMMARY_PATTERN.lastIndex = 0;
|
||||
while ((match = TODO_TASK_SUMMARY_PATTERN.exec(line)) !== null) {
|
||||
execPattern(TODO_TASK_SUMMARY_PATTERN, line, (match) => {
|
||||
const taskNum = parseInt(match[1], 10);
|
||||
const content = match[2].trim();
|
||||
if (content.length >= 5) {
|
||||
@@ -1739,10 +1736,9 @@ export class RalphTracker extends EventEmitter {
|
||||
this.upsertTodo(this._taskNumberToContent.get(taskNum) || content, 'pending');
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TODO_TASK_STATUS_PATTERN.lastIndex = 0;
|
||||
while ((match = TODO_TASK_STATUS_PATTERN.exec(line)) !== null) {
|
||||
execPattern(TODO_TASK_STATUS_PATTERN, line, (match) => {
|
||||
const taskNum = parseInt(match[1], 10);
|
||||
const statusStr = match[2].trim();
|
||||
const status: RalphTodoStatus =
|
||||
@@ -1752,7 +1748,7 @@ export class RalphTracker extends EventEmitter {
|
||||
this.upsertTodo(content, status);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
TODO_PLAIN_CHECKMARK_PATTERN.lastIndex = 0;
|
||||
|
||||
@@ -62,7 +62,15 @@ import {
|
||||
import { RespawnAdaptiveTiming } from './respawn-adaptive-timing.js';
|
||||
import { RespawnCycleMetricsTracker } from './respawn-metrics.js';
|
||||
import { calculateHealthScore, shouldSkipClear, type HealthInputs } from './respawn-health.js';
|
||||
import { AI_CHECK_MODEL, AI_IDLE_CHECK_MAX_CONTEXT, AI_PLAN_CHECK_MAX_CONTEXT } from './config/ai-defaults.js';
|
||||
import {
|
||||
AI_CHECK_MODEL,
|
||||
AI_IDLE_CHECK_MAX_CONTEXT,
|
||||
AI_PLAN_CHECK_MAX_CONTEXT,
|
||||
AI_IDLE_CHECK_TIMEOUT_MS,
|
||||
AI_IDLE_CHECK_COOLDOWN_MS,
|
||||
AI_PLAN_CHECK_TIMEOUT_MS,
|
||||
AI_PLAN_CHECK_COOLDOWN_MS,
|
||||
} from './config/ai-defaults.js';
|
||||
import type {
|
||||
RespawnCycleMetrics,
|
||||
RespawnAggregateMetrics,
|
||||
@@ -559,13 +567,13 @@ const DEFAULT_CONFIG: RespawnConfig = {
|
||||
aiIdleCheckEnabled: true, // use AI to confirm idle state
|
||||
aiIdleCheckModel: AI_CHECK_MODEL,
|
||||
aiIdleCheckMaxContext: AI_IDLE_CHECK_MAX_CONTEXT,
|
||||
aiIdleCheckTimeoutMs: 90000, // 90 seconds (thinking can be slow)
|
||||
aiIdleCheckCooldownMs: 180000, // 3 minutes after WORKING verdict
|
||||
aiIdleCheckTimeoutMs: AI_IDLE_CHECK_TIMEOUT_MS,
|
||||
aiIdleCheckCooldownMs: AI_IDLE_CHECK_COOLDOWN_MS,
|
||||
aiPlanCheckEnabled: true, // use AI to confirm plan mode before auto-accept
|
||||
aiPlanCheckModel: AI_CHECK_MODEL,
|
||||
aiPlanCheckMaxContext: AI_PLAN_CHECK_MAX_CONTEXT,
|
||||
aiPlanCheckTimeoutMs: 60000, // 60 seconds (thinking can be slow)
|
||||
aiPlanCheckCooldownMs: 30000, // 30 seconds after NOT_PLAN_MODE
|
||||
aiPlanCheckTimeoutMs: AI_PLAN_CHECK_TIMEOUT_MS,
|
||||
aiPlanCheckCooldownMs: AI_PLAN_CHECK_COOLDOWN_MS,
|
||||
stuckStateDetectionEnabled: true, // detect stuck states
|
||||
stuckStateWarningMs: 300000, // 5 minutes warning threshold
|
||||
stuckStateRecoveryMs: 600000, // 10 minutes recovery threshold
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@ import {
|
||||
RunSummaryStats,
|
||||
createInitialRunSummaryStats,
|
||||
} from './types.js';
|
||||
import { CLEANUP_CHECK_INTERVAL_MS } from './config/server-timing.js';
|
||||
|
||||
/** Maximum events to keep per session (FIFO trimming) */
|
||||
const MAX_EVENTS = 1000;
|
||||
@@ -36,7 +37,7 @@ const TOKEN_MILESTONE_INTERVAL = 50000;
|
||||
const STATE_STUCK_WARNING_MS = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
/** State stuck check interval (ms) */
|
||||
const STATE_STUCK_CHECK_INTERVAL = 60 * 1000; // 1 minute
|
||||
const STATE_STUCK_CHECK_INTERVAL = CLEANUP_CHECK_INTERVAL_MS;
|
||||
|
||||
/**
|
||||
* Tracks events and statistics for a session's run summary.
|
||||
|
||||
+9
-7
@@ -49,7 +49,13 @@ import { TaskTracker, type BackgroundTask } from './task-tracker.js';
|
||||
import { RalphTracker } from './ralph-tracker.js';
|
||||
import { BashToolParser } from './bash-tool-parser.js';
|
||||
import { BufferAccumulator } from './utils/buffer-accumulator.js';
|
||||
import { ANSI_ESCAPE_PATTERN_FULL, TOKEN_PATTERN, SPINNER_PATTERN, MAX_SESSION_TOKENS } from './utils/index.js';
|
||||
import {
|
||||
ANSI_ESCAPE_PATTERN_FULL,
|
||||
TOKEN_PATTERN,
|
||||
SPINNER_PATTERN,
|
||||
MAX_SESSION_TOKENS,
|
||||
execPattern,
|
||||
} from './utils/index.js';
|
||||
import {
|
||||
MAX_TERMINAL_BUFFER_SIZE,
|
||||
TRIM_TERMINAL_TO as TERMINAL_BUFFER_TRIM_SIZE,
|
||||
@@ -1692,16 +1698,12 @@ export class Session extends EventEmitter {
|
||||
// Quick pre-check: skip expensive regex if no common tool patterns present
|
||||
if (!cleanLine.includes('(') || !cleanLine.includes(')')) return;
|
||||
|
||||
// Reset regex lastIndex for global pattern
|
||||
TASK_TOOL_PATTERN.lastIndex = 0;
|
||||
|
||||
let match;
|
||||
while ((match = TASK_TOOL_PATTERN.exec(cleanLine)) !== null) {
|
||||
execPattern(TASK_TOOL_PATTERN, cleanLine, (match) => {
|
||||
const description = match[2].trim();
|
||||
if (description && description.length > 0) {
|
||||
this._taskCache.add(Date.now(), description);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@ import { join, basename } from 'node:path';
|
||||
import { execFile } from 'node:child_process';
|
||||
import { readFile, readdir, stat as statAsync } from 'node:fs/promises';
|
||||
import { PENDING_TOOL_CALL_TTL_MS, MAX_PENDING_TOOL_CALLS, MAX_TRACKED_AGENTS } from './config/map-limits.js';
|
||||
import { STALE_DATA_MAX_AGE_MS } from './config/server-timing.js';
|
||||
import { CleanupManager, KeyedDebouncer } from './utils/index.js';
|
||||
|
||||
// ========== Types ==========
|
||||
@@ -151,7 +152,7 @@ const POLL_INTERVAL_MS = 1000; // Base poll interval (lightweight checks)
|
||||
const FULL_SCAN_EVERY_N_POLLS = 5; // Full directory traversal every 5th poll (5s)
|
||||
const LIVENESS_CHECK_MS = 10000; // Check if subagent processes are still alive every 10s
|
||||
const FILE_ALIVE_THRESHOLD_MS = 30000; // File mtime within 30s = agent alive (primary check)
|
||||
const STALE_COMPLETED_MAX_AGE_MS = 60 * 60 * 1000; // Remove completed agents older than 1 hour
|
||||
const STALE_COMPLETED_MAX_AGE_MS = STALE_DATA_MAX_AGE_MS; // Remove completed agents older than 1 hour
|
||||
const STALE_IDLE_MAX_AGE_MS = 4 * 60 * 60 * 1000; // Remove idle agents older than 4 hours
|
||||
const STARTUP_MAX_FILE_AGE_MS = 4 * 60 * 60 * 1000; // Only load files modified in last 4 hours on startup
|
||||
|
||||
|
||||
+2
-1
@@ -22,6 +22,7 @@
|
||||
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { assertNever } from './utils/index.js';
|
||||
import { STALE_DATA_MAX_AGE_MS } from './config/server-timing.js';
|
||||
|
||||
// ========== Configuration Constants ==========
|
||||
|
||||
@@ -36,7 +37,7 @@ const MAX_COMPLETED_TASKS = 100;
|
||||
* Entries older than this are cleaned up to prevent unbounded growth.
|
||||
* Default: 1 hour
|
||||
*/
|
||||
const PENDING_TOOL_USE_MAX_AGE_MS = 60 * 60 * 1000;
|
||||
const PENDING_TOOL_USE_MAX_AGE_MS = STALE_DATA_MAX_AGE_MS;
|
||||
|
||||
/**
|
||||
* Maximum number of pending tool uses to allow.
|
||||
|
||||
+2
-5
@@ -6,7 +6,7 @@
|
||||
* Key exports:
|
||||
* - PlanItem — a single task with priority (P0/P1/P2), TDD phase, dependencies, verification criteria
|
||||
* - PlanTaskStatus — 'pending' | 'in_progress' | 'completed' | 'failed' | 'blocked'
|
||||
* - TddPhase / PlanPhase — 'setup' | 'test' | 'impl' | 'verify' | 'review'
|
||||
* - TddPhase — 'setup' | 'test' | 'impl' | 'verify' | 'review'
|
||||
*
|
||||
* Used by PlanOrchestrator (`src/plan-orchestrator.ts`) and the plan API routes
|
||||
* (`src/web/routes/plan-routes.ts`). Served at `GET /api/sessions/:id/plan/tasks`.
|
||||
@@ -21,9 +21,6 @@ export type PlanTaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed'
|
||||
/** TDD phase categories */
|
||||
export type TddPhase = 'setup' | 'test' | 'impl' | 'verify' | 'review';
|
||||
|
||||
/** Development phase in TDD cycle (alias for TddPhase) */
|
||||
export type PlanPhase = TddPhase;
|
||||
|
||||
/**
|
||||
* A single plan item for plan orchestration.
|
||||
* Moved here from plan-orchestrator.ts to break circular dependency.
|
||||
@@ -42,7 +39,7 @@ export interface PlanItem {
|
||||
lastError?: string;
|
||||
completedAt?: number;
|
||||
complexity?: 'low' | 'medium' | 'high';
|
||||
tddPhase?: PlanPhase;
|
||||
tddPhase?: TddPhase;
|
||||
pairedWith?: string;
|
||||
reviewChecklist?: string[];
|
||||
}
|
||||
|
||||
+2
-1
@@ -20,8 +20,9 @@ export {
|
||||
createAnsiPatternSimple,
|
||||
stripAnsi,
|
||||
SAFE_PATH_PATTERN,
|
||||
execPattern,
|
||||
} from './regex-patterns.js';
|
||||
export { MAX_SESSION_TOKENS, validateTokenCounts, validateTokensAndCost } from './token-validation.js';
|
||||
export { MAX_SESSION_TOKENS } from './token-validation.js';
|
||||
export { stringSimilarity, fuzzyPhraseMatch, todoContentHash } from './string-similarity.js';
|
||||
export { assertNever } from './type-safety.js';
|
||||
export { wrapWithNice } from './nice-wrapper.js';
|
||||
|
||||
@@ -79,3 +79,15 @@ export function stripAnsi(text: string): string {
|
||||
export const SPINNER_PATTERN = /[⠋⠙⠹⠸⠼⠴⠦⠧]/;
|
||||
|
||||
export const SAFE_PATH_PATTERN = /^[a-zA-Z0-9_/\-. ~]+$/;
|
||||
|
||||
/**
|
||||
* Execute a global regex pattern against data, calling the callback for each match.
|
||||
* Automatically resets lastIndex before execution.
|
||||
*/
|
||||
export function execPattern(pattern: RegExp, data: string, callback: (match: RegExpExecArray) => void): void {
|
||||
pattern.lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(data)) !== null) {
|
||||
callback(match);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @fileoverview Token validation utilities.
|
||||
* @fileoverview Token validation constants.
|
||||
*
|
||||
* Centralizes token count validation logic used across the codebase.
|
||||
* Claude's context window is ~200k tokens, so 500k is a generous upper bound.
|
||||
*
|
||||
* @module utils/token-validation
|
||||
@@ -12,58 +11,3 @@
|
||||
* Claude's context is ~200k, so 500k is a safe upper bound for validation.
|
||||
*/
|
||||
export const MAX_SESSION_TOKENS = 500_000;
|
||||
|
||||
/**
|
||||
* Validates token counts are within acceptable bounds.
|
||||
* Rejects negative values and values exceeding MAX_SESSION_TOKENS.
|
||||
*
|
||||
* @param inputTokens - Input token count to validate
|
||||
* @param outputTokens - Output token count to validate
|
||||
* @returns Object with isValid flag and optional error reason
|
||||
*/
|
||||
export function validateTokenCounts(inputTokens: number, outputTokens: number): { isValid: boolean; reason?: string } {
|
||||
if (inputTokens < 0 || outputTokens < 0) {
|
||||
return {
|
||||
isValid: false,
|
||||
reason: `Negative token values: input=${inputTokens}, output=${outputTokens}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (inputTokens > MAX_SESSION_TOKENS || outputTokens > MAX_SESSION_TOKENS) {
|
||||
return {
|
||||
isValid: false,
|
||||
reason: `Token values exceed maximum (${MAX_SESSION_TOKENS}): input=${inputTokens}, output=${outputTokens}`,
|
||||
};
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates token counts and cost for restoration/persistence.
|
||||
* Returns true if all values are valid.
|
||||
*
|
||||
* @param inputTokens - Input token count
|
||||
* @param outputTokens - Output token count
|
||||
* @param cost - Cost value (must be non-negative)
|
||||
* @returns Object with isValid flag and optional error reason
|
||||
*/
|
||||
export function validateTokensAndCost(
|
||||
inputTokens: number,
|
||||
outputTokens: number,
|
||||
cost: number
|
||||
): { isValid: boolean; reason?: string } {
|
||||
const tokenValidation = validateTokenCounts(inputTokens, outputTokens);
|
||||
if (!tokenValidation.isValid) {
|
||||
return tokenValidation;
|
||||
}
|
||||
|
||||
if (cost < 0) {
|
||||
return {
|
||||
isValid: false,
|
||||
reason: `Negative cost value: ${cost}`,
|
||||
};
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
@@ -11,7 +11,15 @@ import { SseEvent } from '../sse-events.js';
|
||||
import { findSessionOrFail, autoConfigureRalph } from '../route-helpers.js';
|
||||
import type { SessionPort, EventPort, RespawnPort, ConfigPort, InfraPort } from '../ports/index.js';
|
||||
import { getLifecycleLog } from '../../session-lifecycle-log.js';
|
||||
import { AI_CHECK_MODEL, AI_IDLE_CHECK_MAX_CONTEXT, AI_PLAN_CHECK_MAX_CONTEXT } from '../../config/ai-defaults.js';
|
||||
import {
|
||||
AI_CHECK_MODEL,
|
||||
AI_IDLE_CHECK_MAX_CONTEXT,
|
||||
AI_PLAN_CHECK_MAX_CONTEXT,
|
||||
AI_IDLE_CHECK_TIMEOUT_MS,
|
||||
AI_IDLE_CHECK_COOLDOWN_MS,
|
||||
AI_PLAN_CHECK_TIMEOUT_MS,
|
||||
AI_PLAN_CHECK_COOLDOWN_MS,
|
||||
} from '../../config/ai-defaults.js';
|
||||
|
||||
/** No-op EventPort used to suppress broadcasts during pre-start ralph configuration. */
|
||||
const noopEventPort: EventPort = {
|
||||
@@ -188,14 +196,18 @@ export function registerRespawnRoutes(
|
||||
aiIdleCheckModel: config.aiIdleCheckModel ?? currentConfig?.aiIdleCheckModel ?? AI_CHECK_MODEL,
|
||||
aiIdleCheckMaxContext:
|
||||
config.aiIdleCheckMaxContext ?? currentConfig?.aiIdleCheckMaxContext ?? AI_IDLE_CHECK_MAX_CONTEXT,
|
||||
aiIdleCheckTimeoutMs: config.aiIdleCheckTimeoutMs ?? currentConfig?.aiIdleCheckTimeoutMs ?? 90000,
|
||||
aiIdleCheckCooldownMs: config.aiIdleCheckCooldownMs ?? currentConfig?.aiIdleCheckCooldownMs ?? 180000,
|
||||
aiIdleCheckTimeoutMs:
|
||||
config.aiIdleCheckTimeoutMs ?? currentConfig?.aiIdleCheckTimeoutMs ?? AI_IDLE_CHECK_TIMEOUT_MS,
|
||||
aiIdleCheckCooldownMs:
|
||||
config.aiIdleCheckCooldownMs ?? currentConfig?.aiIdleCheckCooldownMs ?? AI_IDLE_CHECK_COOLDOWN_MS,
|
||||
aiPlanCheckEnabled: config.aiPlanCheckEnabled ?? currentConfig?.aiPlanCheckEnabled ?? true,
|
||||
aiPlanCheckModel: config.aiPlanCheckModel ?? currentConfig?.aiPlanCheckModel ?? AI_CHECK_MODEL,
|
||||
aiPlanCheckMaxContext:
|
||||
config.aiPlanCheckMaxContext ?? currentConfig?.aiPlanCheckMaxContext ?? AI_PLAN_CHECK_MAX_CONTEXT,
|
||||
aiPlanCheckTimeoutMs: config.aiPlanCheckTimeoutMs ?? currentConfig?.aiPlanCheckTimeoutMs ?? 60000,
|
||||
aiPlanCheckCooldownMs: config.aiPlanCheckCooldownMs ?? currentConfig?.aiPlanCheckCooldownMs ?? 30000,
|
||||
aiPlanCheckTimeoutMs:
|
||||
config.aiPlanCheckTimeoutMs ?? currentConfig?.aiPlanCheckTimeoutMs ?? AI_PLAN_CHECK_TIMEOUT_MS,
|
||||
aiPlanCheckCooldownMs:
|
||||
config.aiPlanCheckCooldownMs ?? currentConfig?.aiPlanCheckCooldownMs ?? AI_PLAN_CHECK_COOLDOWN_MS,
|
||||
durationMinutes: currentConfig?.durationMinutes,
|
||||
};
|
||||
ctx.mux.updateRespawnConfig(id, merged);
|
||||
|
||||
+3
-2
@@ -120,6 +120,7 @@ import {
|
||||
ITERATION_PAUSE_MS,
|
||||
BATCH_FLUSH_THRESHOLD,
|
||||
STATS_COLLECTION_INTERVAL_MS,
|
||||
INACTIVITY_TIMEOUT_MS,
|
||||
} from '../config/server-timing.js';
|
||||
|
||||
// DEC mode 2026 - Synchronized Output
|
||||
@@ -230,7 +231,7 @@ export class WebServer extends EventEmitter {
|
||||
// Adaptive batching: track rapid events to extend batch window (per-session)
|
||||
// StaleExpirationMap auto-cleans entries for sessions that stop generating output
|
||||
private lastTerminalEventTime: StaleExpirationMap<string, number> = new StaleExpirationMap({
|
||||
ttlMs: 5 * 60 * 1000, // 5 minutes - auto-expire stale session timing data
|
||||
ttlMs: INACTIVITY_TIMEOUT_MS, // 5 minutes - auto-expire stale session timing data
|
||||
refreshOnGet: false, // Don't refresh on reads, only on explicit sets
|
||||
});
|
||||
// Centralized cleanup for standalone timers (intervals + resettable timeouts)
|
||||
@@ -2397,7 +2398,7 @@ export class WebServer extends EventEmitter {
|
||||
() => {
|
||||
this.recordPeriodicTokenUsage();
|
||||
},
|
||||
5 * 60 * 1000,
|
||||
INACTIVITY_TIMEOUT_MS,
|
||||
{ description: 'periodic token recording' }
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user