diff --git a/src/plan-orchestrator.ts b/src/plan-orchestrator.ts index 7ed0bc88..902d4623 100644 --- a/src/plan-orchestrator.ts +++ b/src/plan-orchestrator.ts @@ -20,36 +20,10 @@ import type { TerminalMultiplexer } from './mux-interface.js'; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { RESEARCH_AGENT_PROMPT, PLANNER_PROMPT } from './prompts/index.js'; -import { PlanTaskStatus, TddPhase } from './types.js'; +import type { PlanItem } from './types.js'; -// ============================================================================ -// Types -// ============================================================================ - -/** Development phase in TDD cycle (alias for TddPhase) */ -export type PlanPhase = TddPhase; - -/** - * Plan item with TDD structure. - */ -export interface PlanItem { - id?: string; - content: string; - priority: 'P0' | 'P1' | 'P2' | null; - source?: string; - rationale?: string; - verificationCriteria?: string; - testCommand?: string; - dependencies?: string[]; - status?: PlanTaskStatus; - attempts?: number; - lastError?: string; - completedAt?: number; - complexity?: 'low' | 'medium' | 'high'; - tddPhase?: PlanPhase; - pairedWith?: string; - reviewChecklist?: string[]; -} +// Re-export for backward compatibility +export type { PlanItem }; export interface ResearchResult { success: boolean; diff --git a/src/types.ts b/src/types.ts index 2388fafe..314a00ec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,1443 +1 @@ -/** - * @fileoverview Type definitions for Codeman - * - * This module contains all TypeScript interfaces, types, and enums used - * throughout the Codeman application. It provides type safety for: - * - Session management - * - Task queue operations - * - Ralph Loop configuration - * - API requests/responses - * - Mux session handling - * - Inner loop tracking (Ralph Wiggum detection) - */ - -// ========== Resource Management Types ========== - -/** - * Interface for objects that hold resources requiring explicit cleanup. - * Implementing classes should release timers, watchers, and other resources in dispose(). - */ -export interface Disposable { - /** Release all held resources. Safe to call multiple times. */ - dispose(): void; - /** Whether this object has been disposed */ - readonly isDisposed: boolean; -} - -/** - * Configuration for buffer accumulator instances. - * Used for terminal buffers, text output, and other size-limited string storage. - */ -export interface BufferConfig { - /** Maximum buffer size in bytes before trimming */ - maxSize: number; - /** Size to trim to when maxSize is exceeded */ - trimSize: number; - /** Optional callback invoked when buffer is trimmed */ - onTrim?: (trimmedBytes: number) => void; -} - -/** - * Resource types that can be registered for cleanup. - */ -export type CleanupResourceType = 'timer' | 'interval' | 'watcher' | 'listener' | 'stream'; - -/** - * Registration entry for a cleanup resource. - * Used by CleanupManager to track and dispose resources. - */ -export interface CleanupRegistration { - /** Unique identifier for this registration */ - id: string; - /** Type of resource */ - type: CleanupResourceType; - /** Human-readable description for debugging */ - description: string; - /** Cleanup function to call on dispose */ - cleanup: () => void; - /** Timestamp when registered */ - registeredAt: number; -} - -// ========== Core Status Types ========== - -/** Status of a Claude session */ -export type SessionStatus = 'idle' | 'busy' | 'stopped' | 'error'; - -/** Status of a task in the queue */ -export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed'; - -/** Status of the Ralph Loop controller */ -export type RalphLoopStatus = 'stopped' | 'running' | 'paused'; - -/** Task execution status for plan tracking */ -export type PlanTaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'blocked'; - -/** TDD phase categories */ -export type TddPhase = 'setup' | 'test' | 'impl' | 'verify' | 'review'; - -// ========== Session Lifecycle Audit Types ========== - -/** Types of session lifecycle events recorded to the audit log */ -export type LifecycleEventType = - | 'created' // Session object created - | 'started' // PTY process launched (interactive/shell/prompt) - | 'exit' // PTY process exited (with exit code) - | 'deleted' // cleanupSession() called — session removed - | 'detached' // Server shutdown — PTY left alive in tmux for recovery - | 'recovered' // Session restored from tmux on server restart - | 'stale_cleaned' // Removed from state.json by cleanupStaleSessions() - | 'mux_died' // tmux session died (detected by reconciliation) - | 'server_started' // Server started (marker for restart detection) - | 'server_stopped'; // Server shutting down - -/** A single entry in the session lifecycle audit log */ -export interface LifecycleEntry { - ts: number; - event: LifecycleEventType; - sessionId: string; - name?: string; - mode?: string; - reason?: string; - exitCode?: number | null; - extra?: Record; -} - -// ========== Session Types ========== - -/** - * Claude CLI startup permission mode. - * - `'dangerously-skip-permissions'`: Bypass all permission prompts (default) - * - `'normal'`: Standard mode with permission prompts - * - `'allowedTools'`: Only allow specific tools (requires allowedTools list) - */ -export type ClaudeMode = 'dangerously-skip-permissions' | 'normal' | 'allowedTools'; - -/** Session mode: which CLI backend a session runs */ -export type SessionMode = 'claude' | 'shell' | 'opencode'; - -/** OpenCode session configuration */ -export interface OpenCodeConfig { - /** Model identifier (e.g., "anthropic/claude-sonnet-4-5", "openai/gpt-5.2", "ollama/codellama") */ - model?: string; - /** Whether to auto-allow all tool executions (sets permission.* = allow) */ - autoAllowTools?: boolean; - /** Session ID to continue from */ - continueSession?: string; - /** Whether to fork when continuing (branch the conversation) */ - forkSession?: boolean; - /** Custom inline config JSON (passed via OPENCODE_CONFIG_CONTENT) */ - configContent?: string; -} - -/** - * Configuration for creating a new session - */ -export interface SessionConfig { - /** Unique session identifier */ - id: string; - /** Working directory for the session */ - workingDir: string; - /** Timestamp when session was created */ - createdAt: number; -} - -/** - * Available session colors for visual differentiation - */ -export type SessionColor = 'default' | 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink'; - -/** - * Current state of a session - */ -export interface SessionState { - /** Unique session identifier */ - id: string; - /** Process ID of the PTY process, null if not running */ - pid: number | null; - /** Current session status */ - status: SessionStatus; - /** Working directory path */ - workingDir: string; - /** ID of currently assigned task, null if none */ - currentTaskId: string | null; - /** Timestamp when session was created */ - createdAt: number; - /** Timestamp of last activity */ - lastActivityAt: number; - /** Session display name */ - name?: string; - /** Session mode */ - mode?: SessionMode; - /** Auto-clear enabled */ - autoClearEnabled?: boolean; - /** Auto-clear token threshold */ - autoClearThreshold?: number; - /** Auto-compact enabled */ - autoCompactEnabled?: boolean; - /** Auto-compact token threshold */ - autoCompactThreshold?: number; - /** Auto-compact prompt */ - autoCompactPrompt?: string; - /** Image watcher enabled for this session */ - imageWatcherEnabled?: boolean; - /** Total cost in USD */ - totalCost?: number; - /** Input tokens used */ - inputTokens?: number; - /** Output tokens used */ - outputTokens?: number; - /** Whether respawn controller is currently enabled/running */ - respawnEnabled?: boolean; - /** Respawn controller config (if enabled) */ - respawnConfig?: RespawnConfig & { durationMinutes?: number }; - /** Ralph / Todo tracker enabled */ - ralphEnabled?: boolean; - /** Ralph auto-enable disabled (user explicitly turned off Ralph) */ - ralphAutoEnableDisabled?: boolean; - /** Ralph completion phrase (if set) */ - ralphCompletionPhrase?: string; - /** Parent agent ID if this session is a spawned agent */ - parentAgentId?: string; - /** Child agent IDs spawned by this session */ - childAgentIds?: string[]; - /** Nice priority enabled */ - niceEnabled?: boolean; - /** Nice value (-20 to 19) */ - niceValue?: number; - /** User-assigned color for visual differentiation */ - color?: SessionColor; - /** Flicker filter enabled (buffers output after screen clears) */ - flickerFilterEnabled?: boolean; - /** Claude Code CLI version (parsed from terminal, e.g., "2.1.27") */ - cliVersion?: string; - /** Claude model in use (parsed from terminal, e.g., "Opus 4.5") */ - cliModel?: string; - /** Account type (parsed from terminal, e.g., "Claude Max", "API") */ - cliAccountType?: string; - /** Latest CLI version available (parsed from version check) */ - cliLatestVersion?: string; - /** OpenCode-specific configuration (only for mode === 'opencode') */ - openCodeConfig?: OpenCodeConfig; -} - -// ========== Global Stats Types ========== - -/** - * Global statistics across all sessions (including deleted ones). - * Persisted to track cumulative usage over time. - */ -export interface GlobalStats { - /** Total input tokens used across all sessions */ - totalInputTokens: number; - /** Total output tokens used across all sessions */ - totalOutputTokens: number; - /** Total cost in USD across all sessions */ - totalCost: number; - /** Total number of sessions created (lifetime) */ - totalSessionsCreated: number; - /** Timestamp when stats were first recorded */ - firstRecordedAt: number; - /** Timestamp of last update */ - lastUpdatedAt: number; -} - -// ========== Token Usage History Types ========== - -/** - * Daily token usage entry for historical tracking. - */ -export interface TokenUsageEntry { - /** Date in YYYY-MM-DD format */ - date: string; - /** Input tokens used on this day */ - inputTokens: number; - /** Output tokens used on this day */ - outputTokens: number; - /** Estimated cost in USD */ - estimatedCost: number; - /** Number of sessions that contributed to this day's usage */ - sessions: number; -} - -/** - * Token usage statistics with daily tracking. - */ -export interface TokenStats { - /** Daily usage entries (most recent first) */ - daily: TokenUsageEntry[]; - /** Timestamp of last update */ - lastUpdated: number; -} - -// ========== Task Types ========== - -/** - * Definition of a task to be executed - */ -export interface TaskDefinition { - /** Unique task identifier */ - id: string; - /** Prompt to send to Claude */ - prompt: string; - /** Working directory for task execution */ - workingDir: string; - /** Priority level (higher = processed first) */ - priority: number; - /** IDs of tasks that must complete first */ - dependencies: string[]; - /** Custom phrase to detect task completion */ - completionPhrase?: string; - /** Timeout in milliseconds */ - timeoutMs?: number; -} - -/** - * Full state of a task including execution details - */ -export interface TaskState { - /** Unique task identifier */ - id: string; - /** Prompt sent to Claude */ - prompt: string; - /** Working directory for task execution */ - workingDir: string; - /** Priority level (higher = processed first) */ - priority: number; - /** IDs of tasks that must complete first */ - dependencies: string[]; - /** Custom phrase to detect task completion */ - completionPhrase?: string; - /** Timeout in milliseconds */ - timeoutMs?: number; - /** Current task status */ - status: TaskStatus; - /** ID of session running this task, null if not assigned */ - assignedSessionId: string | null; - /** Timestamp when task was created */ - createdAt: number; - /** Timestamp when task started executing */ - startedAt: number | null; - /** Timestamp when task completed */ - completedAt: number | null; - /** Captured output from Claude */ - output: string; - /** Error message if task failed */ - error: string | null; -} - -// ========== Ralph Loop Types ========== - -/** - * State of the Ralph Loop controller - */ -export interface RalphLoopState { - /** Current loop status */ - status: RalphLoopStatus; - /** Timestamp when loop started */ - startedAt: number | null; - /** Minimum duration to run in milliseconds */ - minDurationMs: number | null; - /** Number of tasks completed in this run */ - tasksCompleted: number; - /** Number of tasks auto-generated */ - tasksGenerated: number; - /** Timestamp of last status check */ - lastCheckAt: number | null; -} - -// ========== Application State ========== - -/** - * Complete application state - */ -export interface AppState { - /** Map of session ID to session state */ - sessions: Record; - /** Map of task ID to task state */ - tasks: Record; - /** Ralph Loop controller state */ - ralphLoop: RalphLoopState; - /** Application configuration */ - config: AppConfig; - /** Global statistics (cumulative across all sessions) */ - globalStats?: GlobalStats; - /** Daily token usage statistics */ - tokenStats?: TokenStats; -} - -// ========== Nice Priority Types ========== - -/** - * Configuration for process priority using `nice`. - * Lower priority reduces CPU contention with other processes. - */ -export interface NiceConfig { - /** Whether nice priority is enabled */ - enabled: boolean; - /** Nice value (-20 to 19, default: 10 = lower priority) */ - niceValue: number; -} - -export const DEFAULT_NICE_CONFIG: NiceConfig = { - enabled: false, - niceValue: 10, -}; - -// ========== Respawn Controller Types ========== - -/** - * Configuration for the Respawn Controller - * - * The respawn controller keeps interactive sessions productive by - * automatically cycling through update prompts when Claude goes idle. - */ -export interface RespawnConfig { - /** How long to wait after seeing prompt before considering truly idle (ms) */ - idleTimeoutMs: number; - /** The prompt to send for updating docs */ - updatePrompt: string; - /** Delay between sending steps (ms) */ - interStepDelayMs: number; - /** Whether to enable respawn loop */ - enabled: boolean; - /** Whether to send /clear after update prompt */ - sendClear: boolean; - /** Whether to send /init after /clear */ - sendInit: boolean; - /** Optional prompt to send if /init doesn't trigger work */ - kickstartPrompt?: string; - /** Time to wait after completion message before confirming idle (ms) */ - completionConfirmMs?: number; - /** Fallback timeout when no output received at all (ms) */ - noOutputTimeoutMs?: number; - /** Whether to auto-accept plan mode prompts by pressing Enter (not questions) */ - autoAcceptPrompts?: boolean; - /** Delay before auto-accepting plan mode prompts when no output and no completion message (ms) */ - autoAcceptDelayMs?: number; - /** Whether AI idle check is enabled */ - aiIdleCheckEnabled?: boolean; - /** Model to use for AI idle check */ - aiIdleCheckModel?: string; - /** Maximum characters of terminal buffer for AI check */ - aiIdleCheckMaxContext?: number; - /** Timeout for AI check in ms */ - aiIdleCheckTimeoutMs?: number; - /** Cooldown after WORKING verdict in ms */ - aiIdleCheckCooldownMs?: number; - /** Whether AI plan mode check is enabled for auto-accept */ - aiPlanCheckEnabled?: boolean; - /** Model to use for AI plan mode check */ - aiPlanCheckModel?: string; - /** Maximum characters of terminal buffer for plan check */ - aiPlanCheckMaxContext?: number; - /** Timeout for AI plan check in ms */ - aiPlanCheckTimeoutMs?: number; - /** Cooldown after NOT_PLAN_MODE verdict in ms */ - aiPlanCheckCooldownMs?: number; - - // ========== P2-001: Adaptive Timing ========== - - /** Whether to use adaptive timing based on historical patterns */ - adaptiveTimingEnabled?: boolean; - /** Minimum value for adaptive completion confirm (ms) */ - adaptiveMinConfirmMs?: number; - /** Maximum value for adaptive completion confirm (ms) */ - adaptiveMaxConfirmMs?: number; - - // ========== P2-002: Skip-Clear Optimization ========== - - /** Whether to skip /clear when context is below threshold */ - skipClearWhenLowContext?: boolean; - /** Token percentage threshold below which /clear is skipped (0-100) */ - skipClearThresholdPercent?: number; - - // ========== P2-004: Cycle Metrics ========== - - /** Whether to track and persist cycle metrics */ - trackCycleMetrics?: boolean; -} - -// ========== P2-004: Respawn Cycle Metrics ========== - -/** - * Outcome of a respawn cycle - */ -export type CycleOutcome = - | 'success' // Cycle completed normally - | 'stuck_recovery' // Stuck-state recovery triggered - | 'blocked' // Blocked by circuit breaker or exit signal - | 'error' // Error during cycle - | 'cancelled'; // Cancelled (e.g., controller stopped) - -/** - * Metrics for a single respawn cycle. - * Persisted for post-mortem analysis of long-running loops. - */ -export interface RespawnCycleMetrics { - /** Unique cycle ID (session-id:cycle-number) */ - cycleId: string; - /** Session ID this cycle belongs to */ - sessionId: string; - /** Cycle number within the session */ - cycleNumber: number; - /** Timestamp when cycle started */ - startedAt: number; - /** Timestamp when cycle completed */ - completedAt: number; - /** Total duration of cycle (ms) */ - durationMs: number; - /** What triggered idle detection */ - idleReason: string; - /** Time spent detecting idle (from start of watching to idle confirmed) */ - idleDetectionMs: number; - /** Steps completed in this cycle */ - stepsCompleted: string[]; - /** Whether /clear was skipped (P2-002) */ - clearSkipped: boolean; - /** Outcome of the cycle */ - outcome: CycleOutcome; - /** Error message if outcome is 'error' */ - errorMessage?: string; - /** Token count at start of cycle */ - tokenCountAtStart?: number; - /** Token count at end of cycle */ - tokenCountAtEnd?: number; - /** Completion confirm time used (may be adaptive) */ - completionConfirmMsUsed: number; -} - -/** - * Aggregate metrics across multiple cycles for health scoring. - */ -export interface RespawnAggregateMetrics { - /** Total cycles tracked */ - totalCycles: number; - /** Successful cycles */ - successfulCycles: number; - /** Cycles that required stuck-state recovery */ - stuckRecoveryCycles: number; - /** Blocked cycles */ - blockedCycles: number; - /** Error cycles */ - errorCycles: number; - /** Average cycle duration (ms) */ - avgCycleDurationMs: number; - /** Average idle detection time (ms) */ - avgIdleDetectionMs: number; - /** 90th percentile cycle duration (ms) */ - p90CycleDurationMs: number; - /** Success rate (0-100) */ - successRate: number; - /** Last updated timestamp */ - lastUpdatedAt: number; -} - -// ========== P2-005: Ralph Loop Health Score ========== - -/** - * Health status levels for the Ralph Loop system. - */ -export type HealthStatus = 'excellent' | 'good' | 'degraded' | 'critical'; - -/** - * Comprehensive health score for a Ralph Loop session. - * Aggregates multiple health signals into a single score. - */ -export interface RalphLoopHealthScore { - /** Overall health score (0-100) */ - score: number; - /** Health status based on score thresholds */ - status: HealthStatus; - /** Individual component scores (0-100 each) */ - components: { - /** Based on recent cycle success rate */ - cycleSuccess: number; - /** Based on circuit breaker state */ - circuitBreaker: number; - /** Based on iteration stall metrics */ - iterationProgress: number; - /** Based on AI checker error rate */ - aiChecker: number; - /** Based on stuck-state recovery count */ - stuckRecovery: number; - }; - /** Human-readable summary of health */ - summary: string; - /** Recommendations for improvement */ - recommendations: string[]; - /** Timestamp when score was calculated */ - calculatedAt: number; -} - -// ========== Timing History for Adaptive Timing ========== - -/** - * Historical timing data for adaptive adjustments. - */ -export interface TimingHistory { - /** Rolling window of recent idle detection durations (ms) */ - recentIdleDetectionMs: number[]; - /** Rolling window of recent cycle durations (ms) */ - recentCycleDurationMs: number[]; - /** Calculated adaptive completion confirm value (ms) */ - adaptiveCompletionConfirmMs: number; - /** Number of samples in rolling windows */ - sampleCount: number; - /** Maximum samples to keep */ - maxSamples: number; - /** Last updated timestamp */ - lastUpdatedAt: number; -} - -/** - * Named respawn configuration preset for quick setup - */ -export interface RespawnPreset { - /** Unique preset identifier */ - id: string; - /** User-friendly preset name */ - name: string; - /** Description of when to use this preset */ - description?: string; - /** The respawn configuration (without enabled flag) */ - config: Omit; - /** Duration in minutes (optional default) */ - durationMinutes?: number; - /** Whether this is a built-in preset */ - builtIn?: boolean; - /** Timestamp when created */ - createdAt: number; -} - -/** - * Application configuration - */ -export interface AppConfig { - /** Interval for polling session status (ms) */ - pollIntervalMs: number; - /** Default timeout for tasks (ms) */ - defaultTimeoutMs: number; - /** Maximum concurrent sessions allowed */ - maxConcurrentSessions: number; - /** Path to state file */ - stateFilePath: string; - /** Respawn controller configuration */ - respawn: RespawnConfig; - /** Last used case name (for default selection) */ - lastUsedCase: string | null; - /** Whether Ralph/Todo tracker is globally enabled for all new sessions */ - ralphEnabled: boolean; -} - -// ========== Output Types ========== - -/** - * Output captured from a session - */ -export interface SessionOutput { - /** Standard output content */ - stdout: string; - /** Standard error content */ - stderr: string; - /** Exit code of the process, null if still running */ - exitCode: number | null; -} - -// ========== API Error Handling ========== - -/** - * Standard error codes for API responses - */ -export enum ApiErrorCode { - /** Resource not found */ - NOT_FOUND = 'NOT_FOUND', - /** Invalid input provided */ - INVALID_INPUT = 'INVALID_INPUT', - /** Session is currently busy */ - SESSION_BUSY = 'SESSION_BUSY', - /** Operation failed */ - OPERATION_FAILED = 'OPERATION_FAILED', - /** Resource already exists */ - ALREADY_EXISTS = 'ALREADY_EXISTS', - /** Internal server error */ - INTERNAL_ERROR = 'INTERNAL_ERROR', -} - -/** - * User-friendly error messages for each error code - */ -const ErrorMessages: Record = { - [ApiErrorCode.NOT_FOUND]: 'The requested resource was not found', - [ApiErrorCode.INVALID_INPUT]: 'Invalid input provided', - [ApiErrorCode.SESSION_BUSY]: 'Session is currently busy', - [ApiErrorCode.OPERATION_FAILED]: 'The operation failed', - [ApiErrorCode.ALREADY_EXISTS]: 'Resource already exists', - [ApiErrorCode.INTERNAL_ERROR]: 'An internal error occurred', -}; - -/** - * Hook event types triggered by Claude Code's hooks system - */ -export type HookEventType = - | 'idle_prompt' - | 'permission_prompt' - | 'elicitation_dialog' - | 'stop' - | 'teammate_idle' - | 'task_completed'; - -// ========== API Response Types ========== - -/** - * Standard API response wrapper (discriminated union for type safety) - * @template T Type of the data payload - */ -export type ApiResponse = - | { success: true; data?: T } - | { success: false; error: string; errorCode: ApiErrorCode }; - -/** - * Creates a standardized error response - * @param code Error code - * @param details Optional detailed error message - * @returns Formatted error response - */ -export function createErrorResponse(code: ApiErrorCode, details?: string): ApiResponse { - return { - success: false, - error: details || ErrorMessages[code], - errorCode: code, - }; -} - -/** - * Response for quick start operation - */ -export interface QuickStartResponse { - /** Whether the request succeeded */ - success: boolean; - /** Created session ID */ - sessionId?: string; - /** Path to case folder */ - casePath?: string; - /** Case name */ - caseName?: string; - /** Error message if failed */ - error?: string; -} - -/** - * Information about a case folder - */ -export interface CaseInfo { - /** Case name */ - name: string; - /** Full path to case folder */ - path: string; - /** Whether CLAUDE.md exists */ - hasClaudeMd?: boolean; -} - -// ========== Mux Session Types ========== - -/** - * Persisted respawn configuration for mux sessions. - * Subset of RespawnConfig that gets saved to disk. - */ -export interface PersistedRespawnConfig { - /** Whether respawn was enabled */ - enabled: boolean; - /** How long to wait after seeing prompt before considering truly idle (ms) */ - idleTimeoutMs: number; - /** The prompt to send for updating docs */ - updatePrompt: string; - /** Delay between sending steps (ms) */ - interStepDelayMs: number; - /** Whether to send /clear after update prompt */ - sendClear: boolean; - /** Whether to send /init after /clear */ - sendInit: boolean; - /** Optional prompt to send if /init doesn't trigger work */ - kickstartPrompt?: string; - /** Whether to auto-accept plan mode prompts by pressing Enter (not questions) */ - autoAcceptPrompts?: boolean; - /** Delay before auto-accepting prompts (ms) */ - autoAcceptDelayMs?: number; - /** Time to wait after completion message before confirming idle (ms) */ - completionConfirmMs?: number; - /** Fallback timeout when no output received at all (ms) */ - noOutputTimeoutMs?: number; - /** Whether AI idle check is enabled */ - aiIdleCheckEnabled?: boolean; - /** Model to use for AI idle check */ - aiIdleCheckModel?: string; - /** Maximum characters of terminal buffer for AI check */ - aiIdleCheckMaxContext?: number; - /** Timeout for AI check in ms */ - aiIdleCheckTimeoutMs?: number; - /** Cooldown after WORKING verdict in ms */ - aiIdleCheckCooldownMs?: number; - /** Whether AI plan mode check is enabled for auto-accept */ - aiPlanCheckEnabled?: boolean; - /** Model to use for AI plan mode check */ - aiPlanCheckModel?: string; - /** Maximum characters of terminal buffer for plan check */ - aiPlanCheckMaxContext?: number; - /** Timeout for AI plan check in ms */ - aiPlanCheckTimeoutMs?: number; - /** Cooldown after NOT_PLAN_MODE verdict in ms */ - aiPlanCheckCooldownMs?: number; - /** Duration in minutes if timed respawn was set */ - durationMinutes?: number; -} - -/** - * Process resource statistics - */ -export interface ProcessStats { - /** Memory usage in megabytes */ - memoryMB: number; - /** CPU usage percentage */ - cpuPercent: number; - /** Number of child processes */ - childCount: number; - /** Timestamp of stats collection */ - updatedAt: number; -} - -// ========== Default Configuration ========== - -/** - * Default application configuration values - */ -export const DEFAULT_CONFIG: AppConfig = { - pollIntervalMs: 1000, - defaultTimeoutMs: 300000, // 5 minutes - maxConcurrentSessions: 5, - stateFilePath: '', - respawn: { - idleTimeoutMs: 5000, // 5 seconds of no activity after prompt - updatePrompt: 'update all the docs and CLAUDE.md', - interStepDelayMs: 1000, // 1 second between steps - enabled: false, // disabled by default - sendClear: true, // send /clear after update prompt - sendInit: true, // send /init after /clear - }, - lastUsedCase: null, - ralphEnabled: false, -}; - -// ========== Inner Loop Tracking Types ========== - -/** - * Types for tracking Ralph Wiggum loops and todo lists - * running inside Claude Code sessions. - * - * This allows Codeman to detect and display when Claude Code - * is running its own autonomous loops internally. - */ - -/** Status of a detected todo item */ -export type RalphTodoStatus = 'pending' | 'in_progress' | 'completed'; - -/** - * State of per-session Ralph / Todo tracking (detected from Claude output) - */ -/** - * Confidence scoring for completion detection. - * Helps distinguish genuine completion signals from false positives. - */ -export interface CompletionConfidence { - /** Overall confidence level (0-100) */ - score: number; - /** Whether score is above threshold for triggering completion */ - isConfident: boolean; - /** Individual signal contributions */ - signals: { - /** Promise tag detected with proper formatting */ - hasPromiseTag: boolean; - /** Phrase matches expected completion phrase */ - matchesExpected: boolean; - /** All todos are marked complete */ - allTodosComplete: boolean; - /** EXIT_SIGNAL: true in RALPH_STATUS block */ - hasExitSignal: boolean; - /** Multiple completion indicators present */ - multipleIndicators: boolean; - /** Output context suggests completion (not in prompt/explanation) */ - contextAppropriate: boolean; - }; - /** Timestamp of last confidence calculation */ - calculatedAt: number; -} - -export interface RalphTrackerState { - /** Whether the tracker is actively monitoring (disabled by default) */ - enabled: boolean; - /** Whether a loop is currently active */ - active: boolean; - /** Detected completion phrase (primary) */ - completionPhrase: string | null; - /** Additional valid completion phrases (P1-003: multi-phrase support) */ - alternateCompletionPhrases?: string[]; - /** Timestamp when loop started */ - startedAt: number | null; - /** Number of cycles/iterations detected */ - cycleCount: number; - /** Maximum iterations if detected */ - maxIterations: number | null; - /** Timestamp of last activity */ - lastActivity: number; - /** Elapsed hours if detected */ - elapsedHours: number | null; - /** Current plan version (for versioning UI) */ - planVersion?: number; - /** Number of versions in history (for versioning UI) */ - planHistoryLength?: number; - /** Last completion confidence assessment */ - completionConfidence?: CompletionConfidence; -} - -/** - * Priority levels for todo items. - * Matches @fix_plan.md format (P0=critical, P1=high, P2=normal). - */ -export type RalphTodoPriority = 'P0' | 'P1' | 'P2' | null; - -/** - * A detected todo item from Claude Code output - */ -export interface RalphTodoItem { - /** Unique identifier based on content hash */ - id: string; - /** Todo item text content */ - content: string; - /** Current status */ - status: RalphTodoStatus; - /** Timestamp when detected */ - detectedAt: number; - /** Priority level (P0=critical, P1=high, P2=normal) */ - priority: RalphTodoPriority; - /** P1-009: Estimated time to complete (ms), based on historical patterns */ - estimatedDurationMs?: number; - /** P1-009: Complexity category for progress estimation */ - estimatedComplexity?: 'trivial' | 'simple' | 'moderate' | 'complex'; -} - -/** - * Progress estimation for the todo list - */ -export interface RalphTodoProgress { - /** Total number of todos */ - total: number; - /** Number completed */ - completed: number; - /** Number in progress */ - inProgress: number; - /** Number pending */ - pending: number; - /** Completion percentage (0-100) */ - percentComplete: number; - /** Estimated remaining time (ms), based on historical completion rate */ - estimatedRemainingMs: number | null; - /** Average time per todo completion (ms) */ - avgCompletionTimeMs: number | null; - /** Projected completion timestamp (epoch ms) */ - projectedCompletionAt: number | null; -} - -/** - * Complete Ralph/todo state for a session - */ -export interface RalphSessionState { - /** Session this state belongs to */ - sessionId: string; - /** Loop tracking state */ - loop: RalphTrackerState; - /** Detected todo items */ - todos: RalphTodoItem[]; - /** Timestamp of last update */ - lastUpdated: number; -} - -// ========== RALPH_STATUS Block Types ========== - -/** - * Status values from RALPH_STATUS block. - * - IN_PROGRESS: Work is ongoing - * - COMPLETE: All tasks finished - * - BLOCKED: Needs human intervention - */ -export type RalphStatusValue = 'IN_PROGRESS' | 'COMPLETE' | 'BLOCKED'; - -/** - * Test status from RALPH_STATUS block. - */ -export type RalphTestsStatus = 'PASSING' | 'FAILING' | 'NOT_RUN'; - -/** - * Work type classification for current iteration. - */ -export type RalphWorkType = 'IMPLEMENTATION' | 'TESTING' | 'DOCUMENTATION' | 'REFACTORING'; - -/** - * Parsed RALPH_STATUS block from Claude output. - * - * Claude outputs this at the end of every response: - * ``` - * ---RALPH_STATUS--- - * STATUS: IN_PROGRESS - * TASKS_COMPLETED_THIS_LOOP: 3 - * FILES_MODIFIED: 5 - * TESTS_STATUS: PASSING - * WORK_TYPE: IMPLEMENTATION - * EXIT_SIGNAL: false - * RECOMMENDATION: Continue with database migration - * ---END_RALPH_STATUS--- - * ``` - */ -export interface RalphStatusBlock { - /** Overall loop status */ - status: RalphStatusValue; - /** Number of tasks completed in current iteration */ - tasksCompletedThisLoop: number; - /** Number of files modified in current iteration */ - filesModified: number; - /** Current state of tests */ - testsStatus: RalphTestsStatus; - /** Type of work being performed */ - workType: RalphWorkType; - /** Whether Claude is signaling completion */ - exitSignal: boolean; - /** Claude's recommendation for next steps */ - recommendation: string; - /** Timestamp when this block was parsed */ - parsedAt: number; -} - -// ========== Circuit Breaker Types ========== - -/** - * Circuit breaker states for detecting stuck loops. - * - CLOSED: Normal operation, all checks passing - * - HALF_OPEN: Warning state, some checks failing - * - OPEN: Loop is stuck, requires intervention - */ -export type CircuitBreakerState = 'CLOSED' | 'HALF_OPEN' | 'OPEN'; - -/** - * Reason codes for circuit breaker state transitions. - */ -export type CircuitBreakerReason = - | 'normal_operation' - | 'no_progress_warning' - | 'no_progress_open' - | 'same_error_repeated' - | 'tests_failing_too_long' - | 'progress_detected' - | 'manual_reset'; - -/** - * Circuit breaker status for tracking loop health. - * - * Transitions: - * - CLOSED -> HALF_OPEN: consecutive_no_progress >= 2 - * - CLOSED -> OPEN: consecutive_no_progress >= 3 OR consecutive_same_error >= 5 - * - HALF_OPEN -> CLOSED: progress detected - * - HALF_OPEN -> OPEN: consecutive_no_progress >= 3 - * - OPEN -> CLOSED: manual reset only - */ -export interface CircuitBreakerStatus { - /** Current state of the circuit breaker */ - state: CircuitBreakerState; - /** Number of consecutive iterations with no progress */ - consecutiveNoProgress: number; - /** Number of consecutive iterations with the same error */ - consecutiveSameError: number; - /** Number of consecutive iterations with failing tests */ - consecutiveTestsFailure: number; - /** Last iteration number that showed progress */ - lastProgressIteration: number; - /** Human-readable reason for current state */ - reason: string; - /** Reason code for programmatic handling */ - reasonCode: CircuitBreakerReason; - /** Timestamp of last state transition */ - lastTransitionAt: number; - /** Last error message seen (for same-error tracking) */ - lastErrorMessage: string | null; -} - -/** - * Creates initial circuit breaker status. - */ -export function createInitialCircuitBreakerStatus(): CircuitBreakerStatus { - return { - state: 'CLOSED', - consecutiveNoProgress: 0, - consecutiveSameError: 0, - consecutiveTestsFailure: 0, - lastProgressIteration: 0, - reason: 'Initial state', - reasonCode: 'normal_operation', - lastTransitionAt: Date.now(), - lastErrorMessage: null, - }; -} - -/** - * Creates initial Ralph tracker state - * @returns Fresh Ralph tracker state with defaults - */ -export function createInitialRalphTrackerState(): RalphTrackerState { - return { - enabled: false, // Disabled by default, auto-enables when Ralph patterns detected - active: false, - completionPhrase: null, - startedAt: null, - cycleCount: 0, - maxIterations: null, - lastActivity: Date.now(), - elapsedHours: null, - }; -} - -/** - * Creates initial Ralph session state - * @param sessionId Session ID this state belongs to - * @returns Fresh Ralph session state - */ -export function createInitialRalphSessionState(sessionId: string): RalphSessionState { - return { - sessionId, - loop: createInitialRalphTrackerState(), - todos: [], - lastUpdated: Date.now(), - }; -} - -/** - * Creates initial application state - * @returns Fresh application state with defaults - */ -export function createInitialState(): AppState { - return { - sessions: {}, - tasks: {}, - ralphLoop: { - status: 'stopped', - startedAt: null, - minDurationMs: null, - tasksCompleted: 0, - tasksGenerated: 0, - lastCheckAt: null, - }, - config: { ...DEFAULT_CONFIG }, - globalStats: createInitialGlobalStats(), - }; -} - -/** - * Creates initial global stats object - * @returns Fresh global stats with zero values - */ -export function createInitialGlobalStats(): GlobalStats { - const now = Date.now(); - return { - totalInputTokens: 0, - totalOutputTokens: 0, - totalCost: 0, - totalSessionsCreated: 0, - firstRecordedAt: now, - lastUpdatedAt: now, - }; -} - -// ========== Error Handling Utilities ========== - -/** - * Type guard to check if a value is an Error instance - * @param value The value to check - * @returns True if the value is an Error instance - */ -export function isError(value: unknown): value is Error { - return value instanceof Error; -} - -/** - * Safely extracts an error message from an unknown caught value. - * Handles the TypeScript 4.4+ unknown error type in catch blocks. - * - * @param error The caught error (type unknown in strict mode) - * @returns A string error message - * - * @example - * ```typescript - * try { - * await riskyOperation(); - * } catch (err) { - * console.error('Failed:', getErrorMessage(err)); - * } - * ``` - */ -export function getErrorMessage(error: unknown): string { - if (isError(error)) { - return error.message; - } - if (typeof error === 'string') { - return error; - } - if (error && typeof error === 'object' && 'message' in error) { - return String((error as { message: unknown }).message); - } - return 'An unknown error occurred'; -} - -// ========== Run Summary Types ========== - -/** - * Types of events tracked in the run summary. - * These provide a historical view of what happened during a session. - */ -export type RunSummaryEventType = - | 'session_started' - | 'session_stopped' - | 'respawn_cycle_started' - | 'respawn_cycle_completed' - | 'respawn_state_change' - | 'error' - | 'warning' - | 'token_milestone' - | 'auto_compact' - | 'auto_clear' - | 'idle_detected' - | 'working_detected' - | 'ralph_completion' - | 'ai_check_result' - | 'hook_event' - | 'state_stuck'; - -/** - * Severity levels for run summary events. - */ -export type RunSummaryEventSeverity = 'info' | 'warning' | 'error' | 'success'; - -/** - * A single event in the run summary timeline. - */ -export interface RunSummaryEvent { - /** Unique event identifier */ - id: string; - /** Timestamp when event occurred */ - timestamp: number; - /** Type of event */ - type: RunSummaryEventType; - /** Severity level for display */ - severity: RunSummaryEventSeverity; - /** Short title for the event */ - title: string; - /** Optional detailed description */ - details?: string; - /** Optional additional metadata */ - metadata?: Record; -} - -/** - * Statistics aggregated from run summary events. - */ -export interface RunSummaryStats { - /** Number of respawn cycles completed */ - totalRespawnCycles: number; - /** Total tokens used during this run */ - totalTokensUsed: number; - /** Peak token count observed */ - peakTokens: number; - /** Total time Claude was actively working (ms) */ - totalTimeActiveMs: number; - /** Total time Claude was idle (ms) */ - totalTimeIdleMs: number; - /** Number of errors encountered */ - errorCount: number; - /** Number of warnings encountered */ - warningCount: number; - /** Number of AI idle checks performed */ - aiCheckCount: number; - /** Timestamp when last became idle */ - lastIdleAt: number | null; - /** Timestamp when last started working */ - lastWorkingAt: number | null; - /** Total number of state transitions */ - stateTransitions: number; -} - -/** - * Complete run summary for a session. - * Provides a historical view of session activity for users returning after absence. - */ -export interface RunSummary { - /** Session ID this summary belongs to */ - sessionId: string; - /** Session display name */ - sessionName: string; - /** Timestamp when tracking started */ - startedAt: number; - /** Timestamp of last update */ - lastUpdatedAt: number; - /** Timeline of events (most recent last) */ - events: RunSummaryEvent[]; - /** Aggregated statistics */ - stats: RunSummaryStats; -} - -/** - * Creates initial run summary stats. - */ -export function createInitialRunSummaryStats(): RunSummaryStats { - return { - totalRespawnCycles: 0, - totalTokensUsed: 0, - peakTokens: 0, - totalTimeActiveMs: 0, - totalTimeIdleMs: 0, - errorCount: 0, - warningCount: 0, - aiCheckCount: 0, - lastIdleAt: null, - lastWorkingAt: null, - stateTransitions: 0, - }; -} - -// ========== Active Bash Tool Types ========== - -/** - * Status of an active Bash tool command. - */ -export type ActiveBashToolStatus = 'running' | 'completed'; - -/** - * Represents an active Bash tool command detected in Claude's output. - * Used to display clickable file paths for file-viewing commands. - */ -export interface ActiveBashTool { - /** Unique identifier for this tool invocation */ - id: string; - /** The full command being executed */ - command: string; - /** Extracted file paths from the command (clickable) */ - filePaths: string[]; - /** Timeout string if specified (e.g., "16m 0s") */ - timeout?: string; - /** Timestamp when the tool started */ - startedAt: number; - /** Current status */ - status: ActiveBashToolStatus; - /** Session ID this tool belongs to */ - sessionId: string; -} - -// ========== Image Watcher Types ========== - -/** - * Event emitted when a new image file is detected in a session's working directory. - * Used to trigger automatic image popup display in the web UI. - */ -export interface ImageDetectedEvent { - /** Codeman session ID where the image was detected */ - sessionId: string; - /** Full path to the detected image file */ - filePath: string; - /** Path relative to the session's working directory (for file-raw endpoint) */ - relativePath: string; - /** Image file name (basename) */ - fileName: string; - /** Timestamp when the image was detected */ - timestamp: number; - /** File size in bytes */ - size: number; -} - -// ========== Pane Types ========== - -/** - * Information about a tmux pane within a session. - * Used for agent team teammate pane management. - */ -export interface PaneInfo { - /** Pane ID (e.g., "%0", "%1") — immutable within a tmux session */ - paneId: string; - /** Pane index within the window (0, 1, 2...) */ - paneIndex: number; - /** PID of the process running in the pane */ - panePid: number; - /** Pane width in columns */ - width: number; - /** Pane height in rows */ - height: number; -} - -// ========== Plan Orchestrator Re-exports ========== - -export type { PlanItem } from './plan-orchestrator.js'; - -// ========== Web Push Types ========== - -/** A registered push subscription */ -export interface PushSubscriptionRecord { - id: string; - endpoint: string; - keys: { p256dh: string; auth: string }; - userAgent: string; - createdAt: number; - lastUsedAt: number; - pushPreferences: Record; -} - -/** VAPID key pair for Web Push */ -export interface VapidKeys { - publicKey: string; - privateKey: string; - generatedAt: number; -} - -// ========== Agent Teams Types ========== - -/** Team configuration from ~/.claude/teams/{name}/config.json */ -export interface TeamConfig { - name: string; - leadSessionId: string; - members: TeamMember[]; -} - -/** A single team member (lead or teammate) */ -export interface TeamMember { - agentId: string; - name: string; - agentType: 'team-lead' | 'general-purpose' | string; - color?: string; - backendType?: string; - prompt?: string; - tmuxPaneId?: string; -} - -/** A task from ~/.claude/tasks/{team-name}/{N}.json */ -export interface TeamTask { - id: string; - subject: string; - description?: string; - activeForm?: string; - status: 'pending' | 'in_progress' | 'completed' | string; - blocks: string[]; - blockedBy: string[]; - owner?: string; - metadata?: Record; -} - -/** An inbox message from ~/.claude/teams/{name}/inboxes/{member}.json */ -export interface InboxMessage { - from: string; - text: string; - timestamp: string; - read?: boolean; -} +export * from './types/index.js'; diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 00000000..35bfedcd --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,136 @@ +/** + * @fileoverview API types and error handling + */ + +/** + * Standard error codes for API responses + */ +export enum ApiErrorCode { + /** Resource not found */ + NOT_FOUND = 'NOT_FOUND', + /** Invalid input provided */ + INVALID_INPUT = 'INVALID_INPUT', + /** Session is currently busy */ + SESSION_BUSY = 'SESSION_BUSY', + /** Operation failed */ + OPERATION_FAILED = 'OPERATION_FAILED', + /** Resource already exists */ + ALREADY_EXISTS = 'ALREADY_EXISTS', + /** Internal server error */ + INTERNAL_ERROR = 'INTERNAL_ERROR', +} + +/** + * User-friendly error messages for each error code + */ +const ErrorMessages: Record = { + [ApiErrorCode.NOT_FOUND]: 'The requested resource was not found', + [ApiErrorCode.INVALID_INPUT]: 'Invalid input provided', + [ApiErrorCode.SESSION_BUSY]: 'Session is currently busy', + [ApiErrorCode.OPERATION_FAILED]: 'The operation failed', + [ApiErrorCode.ALREADY_EXISTS]: 'Resource already exists', + [ApiErrorCode.INTERNAL_ERROR]: 'An internal error occurred', +}; + +/** + * Hook event types triggered by Claude Code's hooks system + */ +export type HookEventType = + | 'idle_prompt' + | 'permission_prompt' + | 'elicitation_dialog' + | 'stop' + | 'teammate_idle' + | 'task_completed'; + +// ========== API Response Types ========== + +/** + * Standard API response wrapper (discriminated union for type safety) + * @template T Type of the data payload + */ +export type ApiResponse = + | { success: true; data?: T } + | { success: false; error: string; errorCode: ApiErrorCode }; + +/** + * Creates a standardized error response + * @param code Error code + * @param details Optional detailed error message + * @returns Formatted error response + */ +export function createErrorResponse(code: ApiErrorCode, details?: string): ApiResponse { + return { + success: false, + error: details || ErrorMessages[code], + errorCode: code, + }; +} + +/** + * Response for quick start operation + */ +export interface QuickStartResponse { + /** Whether the request succeeded */ + success: boolean; + /** Created session ID */ + sessionId?: string; + /** Path to case folder */ + casePath?: string; + /** Case name */ + caseName?: string; + /** Error message if failed */ + error?: string; +} + +/** + * Information about a case folder + */ +export interface CaseInfo { + /** Case name */ + name: string; + /** Full path to case folder */ + path: string; + /** Whether CLAUDE.md exists */ + hasClaudeMd?: boolean; +} + +// ========== Error Handling Utilities ========== + +/** + * Type guard to check if a value is an Error instance + * @param value The value to check + * @returns True if the value is an Error instance + */ +export function isError(value: unknown): value is Error { + return value instanceof Error; +} + +/** + * Safely extracts an error message from an unknown caught value. + * Handles the TypeScript 4.4+ unknown error type in catch blocks. + * + * @param error The caught error (type unknown in strict mode) + * @returns A string error message + * + * @example + * ```typescript + * try { + * await riskyOperation(); + * } catch (err) { + * console.error('Failed:', getErrorMessage(err)); + * } + * ``` + */ +export function getErrorMessage(error: unknown): string { + if (isError(error)) { + return error.message; + } + if (typeof error === 'string') { + return error; + } + if (error && typeof error === 'object' && 'message' in error) { + return String((error as { message: unknown }).message); + } + return 'An unknown error occurred'; +} diff --git a/src/types/app-state.ts b/src/types/app-state.ts new file mode 100644 index 00000000..143cb5ae --- /dev/null +++ b/src/types/app-state.ts @@ -0,0 +1,154 @@ +/** + * @fileoverview Application state type definitions + */ + +import type { SessionState } from './session.js'; +import type { TaskState } from './task.js'; +import type { RalphLoopState } from './ralph.js'; +import type { RespawnConfig } from './respawn.js'; + +// ========== Global Stats Types ========== + +/** + * Global statistics across all sessions (including deleted ones). + * Persisted to track cumulative usage over time. + */ +export interface GlobalStats { + /** Total input tokens used across all sessions */ + totalInputTokens: number; + /** Total output tokens used across all sessions */ + totalOutputTokens: number; + /** Total cost in USD across all sessions */ + totalCost: number; + /** Total number of sessions created (lifetime) */ + totalSessionsCreated: number; + /** Timestamp when stats were first recorded */ + firstRecordedAt: number; + /** Timestamp of last update */ + lastUpdatedAt: number; +} + +// ========== Token Usage History Types ========== + +/** + * Daily token usage entry for historical tracking. + */ +export interface TokenUsageEntry { + /** Date in YYYY-MM-DD format */ + date: string; + /** Input tokens used on this day */ + inputTokens: number; + /** Output tokens used on this day */ + outputTokens: number; + /** Estimated cost in USD */ + estimatedCost: number; + /** Number of sessions that contributed to this day's usage */ + sessions: number; +} + +/** + * Token usage statistics with daily tracking. + */ +export interface TokenStats { + /** Daily usage entries (most recent first) */ + daily: TokenUsageEntry[]; + /** Timestamp of last update */ + lastUpdated: number; +} + +/** + * Application configuration + */ +export interface AppConfig { + /** Interval for polling session status (ms) */ + pollIntervalMs: number; + /** Default timeout for tasks (ms) */ + defaultTimeoutMs: number; + /** Maximum concurrent sessions allowed */ + maxConcurrentSessions: number; + /** Path to state file */ + stateFilePath: string; + /** Respawn controller configuration */ + respawn: RespawnConfig; + /** Last used case name (for default selection) */ + lastUsedCase: string | null; + /** Whether Ralph/Todo tracker is globally enabled for all new sessions */ + ralphEnabled: boolean; +} + +/** + * Complete application state + */ +export interface AppState { + /** Map of session ID to session state */ + sessions: Record; + /** Map of task ID to task state */ + tasks: Record; + /** Ralph Loop controller state */ + ralphLoop: RalphLoopState; + /** Application configuration */ + config: AppConfig; + /** Global statistics (cumulative across all sessions) */ + globalStats?: GlobalStats; + /** Daily token usage statistics */ + tokenStats?: TokenStats; +} + +// ========== Default Configuration ========== + +/** + * Default application configuration values + */ +export const DEFAULT_CONFIG: AppConfig = { + pollIntervalMs: 1000, + defaultTimeoutMs: 300000, // 5 minutes + maxConcurrentSessions: 5, + stateFilePath: '', + respawn: { + idleTimeoutMs: 5000, // 5 seconds of no activity after prompt + updatePrompt: 'update all the docs and CLAUDE.md', + interStepDelayMs: 1000, // 1 second between steps + enabled: false, // disabled by default + sendClear: true, // send /clear after update prompt + sendInit: true, // send /init after /clear + }, + lastUsedCase: null, + ralphEnabled: false, +}; + +/** + * Creates initial application state + * @returns Fresh application state with defaults + */ +export function createInitialState(): AppState { + return { + sessions: {}, + tasks: {}, + ralphLoop: { + status: 'stopped', + startedAt: null, + minDurationMs: null, + tasksCompleted: 0, + tasksGenerated: 0, + lastCheckAt: null, + }, + config: { ...DEFAULT_CONFIG }, + globalStats: createInitialGlobalStats(), + }; +} + +/** + * Creates initial global stats object + * @returns Fresh global stats with zero values + */ +export function createInitialGlobalStats(): GlobalStats { + const now = Date.now(); + return { + totalInputTokens: 0, + totalOutputTokens: 0, + totalCost: 0, + totalSessionsCreated: 0, + firstRecordedAt: now, + lastUpdatedAt: now, + }; +} diff --git a/src/types/common.ts b/src/types/common.ts new file mode 100644 index 00000000..fec95849 --- /dev/null +++ b/src/types/common.ts @@ -0,0 +1,49 @@ +/** + * @fileoverview Common/shared type definitions + */ + +/** + * Interface for objects that hold resources requiring explicit cleanup. + * Implementing classes should release timers, watchers, and other resources in dispose(). + */ +export interface Disposable { + /** Release all held resources. Safe to call multiple times. */ + dispose(): void; + /** Whether this object has been disposed */ + readonly isDisposed: boolean; +} + +/** + * Configuration for buffer accumulator instances. + * Used for terminal buffers, text output, and other size-limited string storage. + */ +export interface BufferConfig { + /** Maximum buffer size in bytes before trimming */ + maxSize: number; + /** Size to trim to when maxSize is exceeded */ + trimSize: number; + /** Optional callback invoked when buffer is trimmed */ + onTrim?: (trimmedBytes: number) => void; +} + +/** + * Resource types that can be registered for cleanup. + */ +export type CleanupResourceType = 'timer' | 'interval' | 'watcher' | 'listener' | 'stream'; + +/** + * Registration entry for a cleanup resource. + * Used by CleanupManager to track and dispose resources. + */ +export interface CleanupRegistration { + /** Unique identifier for this registration */ + id: string; + /** Type of resource */ + type: CleanupResourceType; + /** Human-readable description for debugging */ + description: string; + /** Cleanup function to call on dispose */ + cleanup: () => void; + /** Timestamp when registered */ + registeredAt: number; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 00000000..1e190e08 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,18 @@ +/** + * @fileoverview Barrel re-export for all type definitions. + * Split into domain modules for maintainability. + */ + +export * from './common.js'; +export * from './session.js'; +export * from './task.js'; +export * from './app-state.js'; +export * from './respawn.js'; +export * from './ralph.js'; +export * from './api.js'; +export * from './lifecycle.js'; +export * from './run-summary.js'; +export * from './tools.js'; +export * from './teams.js'; +export * from './push.js'; +export * from './plan.js'; diff --git a/src/types/lifecycle.ts b/src/types/lifecycle.ts new file mode 100644 index 00000000..1f5b586f --- /dev/null +++ b/src/types/lifecycle.ts @@ -0,0 +1,28 @@ +/** + * @fileoverview Session lifecycle audit types + */ + +/** Types of session lifecycle events recorded to the audit log */ +export type LifecycleEventType = + | 'created' // Session object created + | 'started' // PTY process launched (interactive/shell/prompt) + | 'exit' // PTY process exited (with exit code) + | 'deleted' // cleanupSession() called — session removed + | 'detached' // Server shutdown — PTY left alive in tmux for recovery + | 'recovered' // Session restored from tmux on server restart + | 'stale_cleaned' // Removed from state.json by cleanupStaleSessions() + | 'mux_died' // tmux session died (detected by reconciliation) + | 'server_started' // Server started (marker for restart detection) + | 'server_stopped'; // Server shutting down + +/** A single entry in the session lifecycle audit log */ +export interface LifecycleEntry { + ts: number; + event: LifecycleEventType; + sessionId: string; + name?: string; + mode?: string; + reason?: string; + exitCode?: number | null; + extra?: Record; +} diff --git a/src/types/plan.ts b/src/types/plan.ts new file mode 100644 index 00000000..68dda632 --- /dev/null +++ b/src/types/plan.ts @@ -0,0 +1,65 @@ +/** + * @fileoverview Plan orchestrator type definitions + */ + +/** Task execution status for plan tracking */ +export type PlanTaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'blocked'; + +/** TDD phase categories */ +export type TddPhase = 'setup' | 'test' | 'impl' | 'verify' | 'review'; + +/** Development phase in TDD cycle (alias for TddPhase) */ +export type PlanPhase = TddPhase; + +/** + * Configuration for process priority using `nice`. + * Lower priority reduces CPU contention with other processes. + */ +export interface NiceConfig { + /** Whether nice priority is enabled */ + enabled: boolean; + /** Nice value (-20 to 19, default: 10 = lower priority) */ + niceValue: number; +} + +export const DEFAULT_NICE_CONFIG: NiceConfig = { + enabled: false, + niceValue: 10, +}; + +/** + * Process resource statistics + */ +export interface ProcessStats { + /** Memory usage in megabytes */ + memoryMB: number; + /** CPU usage percentage */ + cpuPercent: number; + /** Number of child processes */ + childCount: number; + /** Timestamp of stats collection */ + updatedAt: number; +} + +/** + * A single plan item for plan orchestration. + * Moved here from plan-orchestrator.ts to break circular dependency. + */ +export interface PlanItem { + id?: string; + content: string; + priority: 'P0' | 'P1' | 'P2' | null; + source?: string; + rationale?: string; + verificationCriteria?: string; + testCommand?: string; + dependencies?: string[]; + status?: PlanTaskStatus; + attempts?: number; + lastError?: string; + completedAt?: number; + complexity?: 'low' | 'medium' | 'high'; + tddPhase?: PlanPhase; + pairedWith?: string; + reviewChecklist?: string[]; +} diff --git a/src/types/push.ts b/src/types/push.ts new file mode 100644 index 00000000..17e893af --- /dev/null +++ b/src/types/push.ts @@ -0,0 +1,21 @@ +/** + * @fileoverview Web Push notification type definitions + */ + +/** A registered push subscription */ +export interface PushSubscriptionRecord { + id: string; + endpoint: string; + keys: { p256dh: string; auth: string }; + userAgent: string; + createdAt: number; + lastUsedAt: number; + pushPreferences: Record; +} + +/** VAPID key pair for Web Push */ +export interface VapidKeys { + publicKey: string; + privateKey: string; + generatedAt: number; +} diff --git a/src/types/ralph.ts b/src/types/ralph.ts new file mode 100644 index 00000000..a15477fa --- /dev/null +++ b/src/types/ralph.ts @@ -0,0 +1,300 @@ +/** + * @fileoverview Ralph Loop / todo tracking type definitions + */ + +/** Status of the Ralph Loop controller */ +export type RalphLoopStatus = 'stopped' | 'running' | 'paused'; + +/** + * State of the Ralph Loop controller + */ +export interface RalphLoopState { + /** Current loop status */ + status: RalphLoopStatus; + /** Timestamp when loop started */ + startedAt: number | null; + /** Minimum duration to run in milliseconds */ + minDurationMs: number | null; + /** Number of tasks completed in this run */ + tasksCompleted: number; + /** Number of tasks auto-generated */ + tasksGenerated: number; + /** Timestamp of last status check */ + lastCheckAt: number | null; +} + +/** Status of a detected todo item */ +export type RalphTodoStatus = 'pending' | 'in_progress' | 'completed'; + +/** + * Confidence scoring for completion detection. + * Helps distinguish genuine completion signals from false positives. + */ +export interface CompletionConfidence { + /** Overall confidence level (0-100) */ + score: number; + /** Whether score is above threshold for triggering completion */ + isConfident: boolean; + /** Individual signal contributions */ + signals: { + /** Promise tag detected with proper formatting */ + hasPromiseTag: boolean; + /** Phrase matches expected completion phrase */ + matchesExpected: boolean; + /** All todos are marked complete */ + allTodosComplete: boolean; + /** EXIT_SIGNAL: true in RALPH_STATUS block */ + hasExitSignal: boolean; + /** Multiple completion indicators present */ + multipleIndicators: boolean; + /** Output context suggests completion (not in prompt/explanation) */ + contextAppropriate: boolean; + }; + /** Timestamp of last confidence calculation */ + calculatedAt: number; +} + +export interface RalphTrackerState { + /** Whether the tracker is actively monitoring (disabled by default) */ + enabled: boolean; + /** Whether a loop is currently active */ + active: boolean; + /** Detected completion phrase (primary) */ + completionPhrase: string | null; + /** Additional valid completion phrases (P1-003: multi-phrase support) */ + alternateCompletionPhrases?: string[]; + /** Timestamp when loop started */ + startedAt: number | null; + /** Number of cycles/iterations detected */ + cycleCount: number; + /** Maximum iterations if detected */ + maxIterations: number | null; + /** Timestamp of last activity */ + lastActivity: number; + /** Elapsed hours if detected */ + elapsedHours: number | null; + /** Current plan version (for versioning UI) */ + planVersion?: number; + /** Number of versions in history (for versioning UI) */ + planHistoryLength?: number; + /** Last completion confidence assessment */ + completionConfidence?: CompletionConfidence; +} + +/** + * Priority levels for todo items. + * Matches @fix_plan.md format (P0=critical, P1=high, P2=normal). + */ +export type RalphTodoPriority = 'P0' | 'P1' | 'P2' | null; + +/** + * A detected todo item from Claude Code output + */ +export interface RalphTodoItem { + /** Unique identifier based on content hash */ + id: string; + /** Todo item text content */ + content: string; + /** Current status */ + status: RalphTodoStatus; + /** Timestamp when detected */ + detectedAt: number; + /** Priority level (P0=critical, P1=high, P2=normal) */ + priority: RalphTodoPriority; + /** P1-009: Estimated time to complete (ms), based on historical patterns */ + estimatedDurationMs?: number; + /** P1-009: Complexity category for progress estimation */ + estimatedComplexity?: 'trivial' | 'simple' | 'moderate' | 'complex'; +} + +/** + * Progress estimation for the todo list + */ +export interface RalphTodoProgress { + /** Total number of todos */ + total: number; + /** Number completed */ + completed: number; + /** Number in progress */ + inProgress: number; + /** Number pending */ + pending: number; + /** Completion percentage (0-100) */ + percentComplete: number; + /** Estimated remaining time (ms), based on historical completion rate */ + estimatedRemainingMs: number | null; + /** Average time per todo completion (ms) */ + avgCompletionTimeMs: number | null; + /** Projected completion timestamp (epoch ms) */ + projectedCompletionAt: number | null; +} + +/** + * Complete Ralph/todo state for a session + */ +export interface RalphSessionState { + /** Session this state belongs to */ + sessionId: string; + /** Loop tracking state */ + loop: RalphTrackerState; + /** Detected todo items */ + todos: RalphTodoItem[]; + /** Timestamp of last update */ + lastUpdated: number; +} + +// ========== RALPH_STATUS Block Types ========== + +/** + * Status values from RALPH_STATUS block. + * - IN_PROGRESS: Work is ongoing + * - COMPLETE: All tasks finished + * - BLOCKED: Needs human intervention + */ +export type RalphStatusValue = 'IN_PROGRESS' | 'COMPLETE' | 'BLOCKED'; + +/** + * Test status from RALPH_STATUS block. + */ +export type RalphTestsStatus = 'PASSING' | 'FAILING' | 'NOT_RUN'; + +/** + * Work type classification for current iteration. + */ +export type RalphWorkType = 'IMPLEMENTATION' | 'TESTING' | 'DOCUMENTATION' | 'REFACTORING'; + +/** + * Parsed RALPH_STATUS block from Claude output. + * + * Claude outputs this at the end of every response: + * ``` + * ---RALPH_STATUS--- + * STATUS: IN_PROGRESS + * TASKS_COMPLETED_THIS_LOOP: 3 + * FILES_MODIFIED: 5 + * TESTS_STATUS: PASSING + * WORK_TYPE: IMPLEMENTATION + * EXIT_SIGNAL: false + * RECOMMENDATION: Continue with database migration + * ---END_RALPH_STATUS--- + * ``` + */ +export interface RalphStatusBlock { + /** Overall loop status */ + status: RalphStatusValue; + /** Number of tasks completed in current iteration */ + tasksCompletedThisLoop: number; + /** Number of files modified in current iteration */ + filesModified: number; + /** Current state of tests */ + testsStatus: RalphTestsStatus; + /** Type of work being performed */ + workType: RalphWorkType; + /** Whether Claude is signaling completion */ + exitSignal: boolean; + /** Claude's recommendation for next steps */ + recommendation: string; + /** Timestamp when this block was parsed */ + parsedAt: number; +} + +// ========== Circuit Breaker Types ========== + +/** + * Circuit breaker states for detecting stuck loops. + * - CLOSED: Normal operation, all checks passing + * - HALF_OPEN: Warning state, some checks failing + * - OPEN: Loop is stuck, requires intervention + */ +export type CircuitBreakerState = 'CLOSED' | 'HALF_OPEN' | 'OPEN'; + +/** + * Reason codes for circuit breaker state transitions. + */ +export type CircuitBreakerReason = + | 'normal_operation' + | 'no_progress_warning' + | 'no_progress_open' + | 'same_error_repeated' + | 'tests_failing_too_long' + | 'progress_detected' + | 'manual_reset'; + +/** + * Circuit breaker status for tracking loop health. + * + * Transitions: + * - CLOSED -> HALF_OPEN: consecutive_no_progress >= 2 + * - CLOSED -> OPEN: consecutive_no_progress >= 3 OR consecutive_same_error >= 5 + * - HALF_OPEN -> CLOSED: progress detected + * - HALF_OPEN -> OPEN: consecutive_no_progress >= 3 + * - OPEN -> CLOSED: manual reset only + */ +export interface CircuitBreakerStatus { + /** Current state of the circuit breaker */ + state: CircuitBreakerState; + /** Number of consecutive iterations with no progress */ + consecutiveNoProgress: number; + /** Number of consecutive iterations with the same error */ + consecutiveSameError: number; + /** Number of consecutive iterations with failing tests */ + consecutiveTestsFailure: number; + /** Last iteration number that showed progress */ + lastProgressIteration: number; + /** Human-readable reason for current state */ + reason: string; + /** Reason code for programmatic handling */ + reasonCode: CircuitBreakerReason; + /** Timestamp of last state transition */ + lastTransitionAt: number; + /** Last error message seen (for same-error tracking) */ + lastErrorMessage: string | null; +} + +/** + * Creates initial circuit breaker status. + */ +export function createInitialCircuitBreakerStatus(): CircuitBreakerStatus { + return { + state: 'CLOSED', + consecutiveNoProgress: 0, + consecutiveSameError: 0, + consecutiveTestsFailure: 0, + lastProgressIteration: 0, + reason: 'Initial state', + reasonCode: 'normal_operation', + lastTransitionAt: Date.now(), + lastErrorMessage: null, + }; +} + +/** + * Creates initial Ralph tracker state + * @returns Fresh Ralph tracker state with defaults + */ +export function createInitialRalphTrackerState(): RalphTrackerState { + return { + enabled: false, // Disabled by default, auto-enables when Ralph patterns detected + active: false, + completionPhrase: null, + startedAt: null, + cycleCount: 0, + maxIterations: null, + lastActivity: Date.now(), + elapsedHours: null, + }; +} + +/** + * Creates initial Ralph session state + * @param sessionId Session ID this state belongs to + * @returns Fresh Ralph session state + */ +export function createInitialRalphSessionState(sessionId: string): RalphSessionState { + return { + sessionId, + loop: createInitialRalphTrackerState(), + todos: [], + lastUpdated: Date.now(), + }; +} diff --git a/src/types/respawn.ts b/src/types/respawn.ts new file mode 100644 index 00000000..8cad6c6e --- /dev/null +++ b/src/types/respawn.ts @@ -0,0 +1,278 @@ +/** + * @fileoverview Respawn controller type definitions + */ + +/** + * Configuration for the Respawn Controller + * + * The respawn controller keeps interactive sessions productive by + * automatically cycling through update prompts when Claude goes idle. + */ +export interface RespawnConfig { + /** How long to wait after seeing prompt before considering truly idle (ms) */ + idleTimeoutMs: number; + /** The prompt to send for updating docs */ + updatePrompt: string; + /** Delay between sending steps (ms) */ + interStepDelayMs: number; + /** Whether to enable respawn loop */ + enabled: boolean; + /** Whether to send /clear after update prompt */ + sendClear: boolean; + /** Whether to send /init after /clear */ + sendInit: boolean; + /** Optional prompt to send if /init doesn't trigger work */ + kickstartPrompt?: string; + /** Time to wait after completion message before confirming idle (ms) */ + completionConfirmMs?: number; + /** Fallback timeout when no output received at all (ms) */ + noOutputTimeoutMs?: number; + /** Whether to auto-accept plan mode prompts by pressing Enter (not questions) */ + autoAcceptPrompts?: boolean; + /** Delay before auto-accepting plan mode prompts when no output and no completion message (ms) */ + autoAcceptDelayMs?: number; + /** Whether AI idle check is enabled */ + aiIdleCheckEnabled?: boolean; + /** Model to use for AI idle check */ + aiIdleCheckModel?: string; + /** Maximum characters of terminal buffer for AI check */ + aiIdleCheckMaxContext?: number; + /** Timeout for AI check in ms */ + aiIdleCheckTimeoutMs?: number; + /** Cooldown after WORKING verdict in ms */ + aiIdleCheckCooldownMs?: number; + /** Whether AI plan mode check is enabled for auto-accept */ + aiPlanCheckEnabled?: boolean; + /** Model to use for AI plan mode check */ + aiPlanCheckModel?: string; + /** Maximum characters of terminal buffer for plan check */ + aiPlanCheckMaxContext?: number; + /** Timeout for AI plan check in ms */ + aiPlanCheckTimeoutMs?: number; + /** Cooldown after NOT_PLAN_MODE verdict in ms */ + aiPlanCheckCooldownMs?: number; + + // ========== P2-001: Adaptive Timing ========== + + /** Whether to use adaptive timing based on historical patterns */ + adaptiveTimingEnabled?: boolean; + /** Minimum value for adaptive completion confirm (ms) */ + adaptiveMinConfirmMs?: number; + /** Maximum value for adaptive completion confirm (ms) */ + adaptiveMaxConfirmMs?: number; + + // ========== P2-002: Skip-Clear Optimization ========== + + /** Whether to skip /clear when context is below threshold */ + skipClearWhenLowContext?: boolean; + /** Token percentage threshold below which /clear is skipped (0-100) */ + skipClearThresholdPercent?: number; + + // ========== P2-004: Cycle Metrics ========== + + /** Whether to track and persist cycle metrics */ + trackCycleMetrics?: boolean; +} + +// ========== P2-004: Respawn Cycle Metrics ========== + +/** + * Outcome of a respawn cycle + */ +export type CycleOutcome = + | 'success' // Cycle completed normally + | 'stuck_recovery' // Stuck-state recovery triggered + | 'blocked' // Blocked by circuit breaker or exit signal + | 'error' // Error during cycle + | 'cancelled'; // Cancelled (e.g., controller stopped) + +/** + * Metrics for a single respawn cycle. + * Persisted for post-mortem analysis of long-running loops. + */ +export interface RespawnCycleMetrics { + /** Unique cycle ID (session-id:cycle-number) */ + cycleId: string; + /** Session ID this cycle belongs to */ + sessionId: string; + /** Cycle number within the session */ + cycleNumber: number; + /** Timestamp when cycle started */ + startedAt: number; + /** Timestamp when cycle completed */ + completedAt: number; + /** Total duration of cycle (ms) */ + durationMs: number; + /** What triggered idle detection */ + idleReason: string; + /** Time spent detecting idle (from start of watching to idle confirmed) */ + idleDetectionMs: number; + /** Steps completed in this cycle */ + stepsCompleted: string[]; + /** Whether /clear was skipped (P2-002) */ + clearSkipped: boolean; + /** Outcome of the cycle */ + outcome: CycleOutcome; + /** Error message if outcome is 'error' */ + errorMessage?: string; + /** Token count at start of cycle */ + tokenCountAtStart?: number; + /** Token count at end of cycle */ + tokenCountAtEnd?: number; + /** Completion confirm time used (may be adaptive) */ + completionConfirmMsUsed: number; +} + +/** + * Aggregate metrics across multiple cycles for health scoring. + */ +export interface RespawnAggregateMetrics { + /** Total cycles tracked */ + totalCycles: number; + /** Successful cycles */ + successfulCycles: number; + /** Cycles that required stuck-state recovery */ + stuckRecoveryCycles: number; + /** Blocked cycles */ + blockedCycles: number; + /** Error cycles */ + errorCycles: number; + /** Average cycle duration (ms) */ + avgCycleDurationMs: number; + /** Average idle detection time (ms) */ + avgIdleDetectionMs: number; + /** 90th percentile cycle duration (ms) */ + p90CycleDurationMs: number; + /** Success rate (0-100) */ + successRate: number; + /** Last updated timestamp */ + lastUpdatedAt: number; +} + +// ========== P2-005: Ralph Loop Health Score ========== + +/** + * Health status levels for the Ralph Loop system. + */ +export type HealthStatus = 'excellent' | 'good' | 'degraded' | 'critical'; + +/** + * Comprehensive health score for a Ralph Loop session. + * Aggregates multiple health signals into a single score. + */ +export interface RalphLoopHealthScore { + /** Overall health score (0-100) */ + score: number; + /** Health status based on score thresholds */ + status: HealthStatus; + /** Individual component scores (0-100 each) */ + components: { + /** Based on recent cycle success rate */ + cycleSuccess: number; + /** Based on circuit breaker state */ + circuitBreaker: number; + /** Based on iteration stall metrics */ + iterationProgress: number; + /** Based on AI checker error rate */ + aiChecker: number; + /** Based on stuck-state recovery count */ + stuckRecovery: number; + }; + /** Human-readable summary of health */ + summary: string; + /** Recommendations for improvement */ + recommendations: string[]; + /** Timestamp when score was calculated */ + calculatedAt: number; +} + +// ========== Timing History for Adaptive Timing ========== + +/** + * Historical timing data for adaptive adjustments. + */ +export interface TimingHistory { + /** Rolling window of recent idle detection durations (ms) */ + recentIdleDetectionMs: number[]; + /** Rolling window of recent cycle durations (ms) */ + recentCycleDurationMs: number[]; + /** Calculated adaptive completion confirm value (ms) */ + adaptiveCompletionConfirmMs: number; + /** Number of samples in rolling windows */ + sampleCount: number; + /** Maximum samples to keep */ + maxSamples: number; + /** Last updated timestamp */ + lastUpdatedAt: number; +} + +/** + * Named respawn configuration preset for quick setup + */ +export interface RespawnPreset { + /** Unique preset identifier */ + id: string; + /** User-friendly preset name */ + name: string; + /** Description of when to use this preset */ + description?: string; + /** The respawn configuration (without enabled flag) */ + config: Omit; + /** Duration in minutes (optional default) */ + durationMinutes?: number; + /** Whether this is a built-in preset */ + builtIn?: boolean; + /** Timestamp when created */ + createdAt: number; +} + +/** + * Persisted respawn configuration for mux sessions. + * Subset of RespawnConfig that gets saved to disk. + */ +export interface PersistedRespawnConfig { + /** Whether respawn was enabled */ + enabled: boolean; + /** How long to wait after seeing prompt before considering truly idle (ms) */ + idleTimeoutMs: number; + /** The prompt to send for updating docs */ + updatePrompt: string; + /** Delay between sending steps (ms) */ + interStepDelayMs: number; + /** Whether to send /clear after update prompt */ + sendClear: boolean; + /** Whether to send /init after /clear */ + sendInit: boolean; + /** Optional prompt to send if /init doesn't trigger work */ + kickstartPrompt?: string; + /** Whether to auto-accept plan mode prompts by pressing Enter (not questions) */ + autoAcceptPrompts?: boolean; + /** Delay before auto-accepting prompts (ms) */ + autoAcceptDelayMs?: number; + /** Time to wait after completion message before confirming idle (ms) */ + completionConfirmMs?: number; + /** Fallback timeout when no output received at all (ms) */ + noOutputTimeoutMs?: number; + /** Whether AI idle check is enabled */ + aiIdleCheckEnabled?: boolean; + /** Model to use for AI idle check */ + aiIdleCheckModel?: string; + /** Maximum characters of terminal buffer for AI check */ + aiIdleCheckMaxContext?: number; + /** Timeout for AI check in ms */ + aiIdleCheckTimeoutMs?: number; + /** Cooldown after WORKING verdict in ms */ + aiIdleCheckCooldownMs?: number; + /** Whether AI plan mode check is enabled for auto-accept */ + aiPlanCheckEnabled?: boolean; + /** Model to use for AI plan mode check */ + aiPlanCheckModel?: string; + /** Maximum characters of terminal buffer for plan check */ + aiPlanCheckMaxContext?: number; + /** Timeout for AI plan check in ms */ + aiPlanCheckTimeoutMs?: number; + /** Cooldown after NOT_PLAN_MODE verdict in ms */ + aiPlanCheckCooldownMs?: number; + /** Duration in minutes if timed respawn was set */ + durationMinutes?: number; +} diff --git a/src/types/run-summary.ts b/src/types/run-summary.ts new file mode 100644 index 00000000..e2743d30 --- /dev/null +++ b/src/types/run-summary.ts @@ -0,0 +1,116 @@ +/** + * @fileoverview Run summary type definitions + */ + +/** + * Types of events tracked in the run summary. + * These provide a historical view of what happened during a session. + */ +export type RunSummaryEventType = + | 'session_started' + | 'session_stopped' + | 'respawn_cycle_started' + | 'respawn_cycle_completed' + | 'respawn_state_change' + | 'error' + | 'warning' + | 'token_milestone' + | 'auto_compact' + | 'auto_clear' + | 'idle_detected' + | 'working_detected' + | 'ralph_completion' + | 'ai_check_result' + | 'hook_event' + | 'state_stuck'; + +/** + * Severity levels for run summary events. + */ +export type RunSummaryEventSeverity = 'info' | 'warning' | 'error' | 'success'; + +/** + * A single event in the run summary timeline. + */ +export interface RunSummaryEvent { + /** Unique event identifier */ + id: string; + /** Timestamp when event occurred */ + timestamp: number; + /** Type of event */ + type: RunSummaryEventType; + /** Severity level for display */ + severity: RunSummaryEventSeverity; + /** Short title for the event */ + title: string; + /** Optional detailed description */ + details?: string; + /** Optional additional metadata */ + metadata?: Record; +} + +/** + * Statistics aggregated from run summary events. + */ +export interface RunSummaryStats { + /** Number of respawn cycles completed */ + totalRespawnCycles: number; + /** Total tokens used during this run */ + totalTokensUsed: number; + /** Peak token count observed */ + peakTokens: number; + /** Total time Claude was actively working (ms) */ + totalTimeActiveMs: number; + /** Total time Claude was idle (ms) */ + totalTimeIdleMs: number; + /** Number of errors encountered */ + errorCount: number; + /** Number of warnings encountered */ + warningCount: number; + /** Number of AI idle checks performed */ + aiCheckCount: number; + /** Timestamp when last became idle */ + lastIdleAt: number | null; + /** Timestamp when last started working */ + lastWorkingAt: number | null; + /** Total number of state transitions */ + stateTransitions: number; +} + +/** + * Complete run summary for a session. + * Provides a historical view of session activity for users returning after absence. + */ +export interface RunSummary { + /** Session ID this summary belongs to */ + sessionId: string; + /** Session display name */ + sessionName: string; + /** Timestamp when tracking started */ + startedAt: number; + /** Timestamp of last update */ + lastUpdatedAt: number; + /** Timeline of events (most recent last) */ + events: RunSummaryEvent[]; + /** Aggregated statistics */ + stats: RunSummaryStats; +} + +/** + * Creates initial run summary stats. + */ +export function createInitialRunSummaryStats(): RunSummaryStats { + return { + totalRespawnCycles: 0, + totalTokensUsed: 0, + peakTokens: 0, + totalTimeActiveMs: 0, + totalTimeIdleMs: 0, + errorCount: 0, + warningCount: 0, + aiCheckCount: 0, + lastIdleAt: null, + lastWorkingAt: null, + stateTransitions: 0, + }; +} diff --git a/src/types/session.ts b/src/types/session.ts new file mode 100644 index 00000000..83ca88cd --- /dev/null +++ b/src/types/session.ts @@ -0,0 +1,136 @@ +/** + * @fileoverview Session type definitions + */ + +import type { RespawnConfig } from './respawn.js'; + +/** Status of a Claude session */ +export type SessionStatus = 'idle' | 'busy' | 'stopped' | 'error'; + +/** + * Claude CLI startup permission mode. + * - `'dangerously-skip-permissions'`: Bypass all permission prompts (default) + * - `'normal'`: Standard mode with permission prompts + * - `'allowedTools'`: Only allow specific tools (requires allowedTools list) + */ +export type ClaudeMode = 'dangerously-skip-permissions' | 'normal' | 'allowedTools'; + +/** Session mode: which CLI backend a session runs */ +export type SessionMode = 'claude' | 'shell' | 'opencode'; + +/** OpenCode session configuration */ +export interface OpenCodeConfig { + /** Model identifier (e.g., "anthropic/claude-sonnet-4-5", "openai/gpt-5.2", "ollama/codellama") */ + model?: string; + /** Whether to auto-allow all tool executions (sets permission.* = allow) */ + autoAllowTools?: boolean; + /** Session ID to continue from */ + continueSession?: string; + /** Whether to fork when continuing (branch the conversation) */ + forkSession?: boolean; + /** Custom inline config JSON (passed via OPENCODE_CONFIG_CONTENT) */ + configContent?: string; +} + +/** + * Configuration for creating a new session + */ +export interface SessionConfig { + /** Unique session identifier */ + id: string; + /** Working directory for the session */ + workingDir: string; + /** Timestamp when session was created */ + createdAt: number; +} + +/** + * Available session colors for visual differentiation + */ +export type SessionColor = 'default' | 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple' | 'pink'; + +/** + * Current state of a session + */ +export interface SessionState { + /** Unique session identifier */ + id: string; + /** Process ID of the PTY process, null if not running */ + pid: number | null; + /** Current session status */ + status: SessionStatus; + /** Working directory path */ + workingDir: string; + /** ID of currently assigned task, null if none */ + currentTaskId: string | null; + /** Timestamp when session was created */ + createdAt: number; + /** Timestamp of last activity */ + lastActivityAt: number; + /** Session display name */ + name?: string; + /** Session mode */ + mode?: SessionMode; + /** Auto-clear enabled */ + autoClearEnabled?: boolean; + /** Auto-clear token threshold */ + autoClearThreshold?: number; + /** Auto-compact enabled */ + autoCompactEnabled?: boolean; + /** Auto-compact token threshold */ + autoCompactThreshold?: number; + /** Auto-compact prompt */ + autoCompactPrompt?: string; + /** Image watcher enabled for this session */ + imageWatcherEnabled?: boolean; + /** Total cost in USD */ + totalCost?: number; + /** Input tokens used */ + inputTokens?: number; + /** Output tokens used */ + outputTokens?: number; + /** Whether respawn controller is currently enabled/running */ + respawnEnabled?: boolean; + /** Respawn controller config (if enabled) */ + respawnConfig?: RespawnConfig & { durationMinutes?: number }; + /** Ralph / Todo tracker enabled */ + ralphEnabled?: boolean; + /** Ralph auto-enable disabled (user explicitly turned off Ralph) */ + ralphAutoEnableDisabled?: boolean; + /** Ralph completion phrase (if set) */ + ralphCompletionPhrase?: string; + /** Parent agent ID if this session is a spawned agent */ + parentAgentId?: string; + /** Child agent IDs spawned by this session */ + childAgentIds?: string[]; + /** Nice priority enabled */ + niceEnabled?: boolean; + /** Nice value (-20 to 19) */ + niceValue?: number; + /** User-assigned color for visual differentiation */ + color?: SessionColor; + /** Flicker filter enabled (buffers output after screen clears) */ + flickerFilterEnabled?: boolean; + /** Claude Code CLI version (parsed from terminal, e.g., "2.1.27") */ + cliVersion?: string; + /** Claude model in use (parsed from terminal, e.g., "Opus 4.5") */ + cliModel?: string; + /** Account type (parsed from terminal, e.g., "Claude Max", "API") */ + cliAccountType?: string; + /** Latest CLI version available (parsed from version check) */ + cliLatestVersion?: string; + /** OpenCode-specific configuration (only for mode === 'opencode') */ + openCodeConfig?: OpenCodeConfig; +} + +/** + * Output captured from a session + */ +export interface SessionOutput { + /** Standard output content */ + stdout: string; + /** Standard error content */ + stderr: string; + /** Exit code of the process, null if still running */ + exitCode: number | null; +} diff --git a/src/types/task.ts b/src/types/task.ts new file mode 100644 index 00000000..f499eb35 --- /dev/null +++ b/src/types/task.ts @@ -0,0 +1,60 @@ +/** + * @fileoverview Task queue type definitions + */ + +/** Status of a task in the queue */ +export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed'; + +/** + * Definition of a task to be executed + */ +export interface TaskDefinition { + /** Unique task identifier */ + id: string; + /** Prompt to send to Claude */ + prompt: string; + /** Working directory for task execution */ + workingDir: string; + /** Priority level (higher = processed first) */ + priority: number; + /** IDs of tasks that must complete first */ + dependencies: string[]; + /** Custom phrase to detect task completion */ + completionPhrase?: string; + /** Timeout in milliseconds */ + timeoutMs?: number; +} + +/** + * Full state of a task including execution details + */ +export interface TaskState { + /** Unique task identifier */ + id: string; + /** Prompt sent to Claude */ + prompt: string; + /** Working directory for task execution */ + workingDir: string; + /** Priority level (higher = processed first) */ + priority: number; + /** IDs of tasks that must complete first */ + dependencies: string[]; + /** Custom phrase to detect task completion */ + completionPhrase?: string; + /** Timeout in milliseconds */ + timeoutMs?: number; + /** Current task status */ + status: TaskStatus; + /** ID of session running this task, null if not assigned */ + assignedSessionId: string | null; + /** Timestamp when task was created */ + createdAt: number; + /** Timestamp when task started executing */ + startedAt: number | null; + /** Timestamp when task completed */ + completedAt: number | null; + /** Captured output from Claude */ + output: string; + /** Error message if task failed */ + error: string | null; +} diff --git a/src/types/teams.ts b/src/types/teams.ts new file mode 100644 index 00000000..ac826345 --- /dev/null +++ b/src/types/teams.ts @@ -0,0 +1,59 @@ +/** + * @fileoverview Agent Teams type definitions + */ + +/** Team configuration from ~/.claude/teams/{name}/config.json */ +export interface TeamConfig { + name: string; + leadSessionId: string; + members: TeamMember[]; +} + +/** A single team member (lead or teammate) */ +export interface TeamMember { + agentId: string; + name: string; + agentType: 'team-lead' | 'general-purpose' | string; + color?: string; + backendType?: string; + prompt?: string; + tmuxPaneId?: string; +} + +/** A task from ~/.claude/tasks/{team-name}/{N}.json */ +export interface TeamTask { + id: string; + subject: string; + description?: string; + activeForm?: string; + status: 'pending' | 'in_progress' | 'completed' | string; + blocks: string[]; + blockedBy: string[]; + owner?: string; + metadata?: Record; +} + +/** An inbox message from ~/.claude/teams/{name}/inboxes/{member}.json */ +export interface InboxMessage { + from: string; + text: string; + timestamp: string; + read?: boolean; +} + +/** + * Information about a tmux pane within a session. + * Used for agent team teammate pane management. + */ +export interface PaneInfo { + /** Pane ID (e.g., "%0", "%1") — immutable within a tmux session */ + paneId: string; + /** Pane index within the window (0, 1, 2...) */ + paneIndex: number; + /** PID of the process running in the pane */ + panePid: number; + /** Pane width in columns */ + width: number; + /** Pane height in rows */ + height: number; +} diff --git a/src/types/tools.ts b/src/types/tools.ts new file mode 100644 index 00000000..99633747 --- /dev/null +++ b/src/types/tools.ts @@ -0,0 +1,48 @@ +/** + * @fileoverview Tool-related type definitions + */ + +/** + * Status of an active Bash tool command. + */ +export type ActiveBashToolStatus = 'running' | 'completed'; + +/** + * Represents an active Bash tool command detected in Claude's output. + * Used to display clickable file paths for file-viewing commands. + */ +export interface ActiveBashTool { + /** Unique identifier for this tool invocation */ + id: string; + /** The full command being executed */ + command: string; + /** Extracted file paths from the command (clickable) */ + filePaths: string[]; + /** Timeout string if specified (e.g., "16m 0s") */ + timeout?: string; + /** Timestamp when the tool started */ + startedAt: number; + /** Current status */ + status: ActiveBashToolStatus; + /** Session ID this tool belongs to */ + sessionId: string; +} + +/** + * Event emitted when a new image file is detected in a session's working directory. + * Used to trigger automatic image popup display in the web UI. + */ +export interface ImageDetectedEvent { + /** Codeman session ID where the image was detected */ + sessionId: string; + /** Full path to the detected image file */ + filePath: string; + /** Path relative to the session's working directory (for file-raw endpoint) */ + relativePath: string; + /** Image file name (basename) */ + fileName: string; + /** Timestamp when the image was detected */ + timestamp: number; + /** File size in bytes */ + size: number; +}