From f604deba3e53ea3dd20120deb860e9b3d8c627d2 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Fri, 13 Nov 2026 13:58:22 +0100 Subject: [PATCH] chore: remove backend code nothing references Found with knip (exports, types, class members) and confirmed by a text search of src, test, scripts and packages before each removal. - Per-CLI getNotFoundMessage() (nine of them) with their message constants: missingCliMessage() builds that text from the registry now. - Registry leftovers: renderCliCommand, asCliId, cliIds, isCliRunnable, createCliResolverForTest, profileExists, and two name lists that only repeated schema enums. - A task-description cache in Session that was written on every chunk of terminal output (one regex pass per line) and never read since the subagent watcher takes descriptions from transcripts. The cache module, its pattern and both parse methods go with it. - Session.parentAgentId / childAgentIds: never set by anything, so the two optional SessionState fields were never on the wire. - Unreferenced methods on TmuxManager (pipe-pane pair, syncMouseMode), RespawnController, StateStore.recoverFromBackup (load() already falls back to the .bak file), TeamWatcher, SseStreamManager and the AI checkers' getErrorVerdict. - updateCaseEnvVars: the settings.local.json env path that envOverrides replaced. - Re-exports in the utils, cli-registry, routes and prompts barrels that no importer used, and unused test helpers (MockStateStore, the mobile throttle helpers and others). Eight mobile tests imported the WebServer type from a path that does not exist (../src instead of ../../src); type-only, so nothing failed. Co-Authored-By: Claude Fable 5.2 --- scripts/test-local-llm-harnesses.ts | 2 +- src/ai-checker-base.ts | 5 - src/ai-idle-checker.ts | 4 - src/ai-plan-checker.ts | 4 - src/config/cli-registry/argv.ts | 12 +-- src/config/cli-registry/index.ts | 57 +---------- src/config/cli-registry/profiles.ts | 10 -- src/config/cli-registry/registry.ts | 14 +-- src/config/cli-registry/schema.ts | 2 - src/cron/cron-input.ts | 2 - src/hooks-config.ts | 32 ------- src/prompts/index.ts | 2 +- src/ralph-tracker.ts | 7 -- src/respawn-controller.ts | 35 +------ src/session-task-cache.ts | 101 -------------------- src/session.ts | 130 +------------------------- src/state-store.ts | 23 ----- src/team-watcher.ts | 13 +-- src/tmux-manager.ts | 76 --------------- src/types/foreign-tmux.ts | 14 --- src/types/local-model-install.ts | 3 - src/types/session.ts | 4 - src/utils/antigravity-cli-resolver.ts | 13 +-- src/utils/claude-cli-resolver.ts | 7 +- src/utils/cli-launcher.ts | 20 ---- src/utils/cli-resolver.ts | 14 --- src/utils/codex-cli-resolver.ts | 7 +- src/utils/copilot-cli-resolver.ts | 12 +-- src/utils/deepseek-cli-resolver.ts | 7 +- src/utils/gemini-cli-resolver.ts | 7 +- src/utils/grok-cli-resolver.ts | 12 +-- src/utils/index.ts | 52 +---------- src/utils/omp-cli-resolver.ts | 12 +-- src/utils/opencode-cli-resolver.ts | 7 +- src/utils/pi-cli-resolver.ts | 12 +-- src/web/ports/tab-layout-port.ts | 2 - src/web/routes/index.ts | 4 +- src/web/sse-stream-manager.ts | 4 - test/hooks-config.test.ts | 2 - test/mobile/accessibility.test.ts | 2 +- test/mobile/device-matrix.test.ts | 2 +- test/mobile/devices.ts | 7 -- test/mobile/helpers/assertions.ts | 15 --- test/mobile/helpers/browser.ts | 3 - test/mobile/helpers/cdp.ts | 35 ------- test/mobile/helpers/server.ts | 10 -- test/mobile/helpers/touch-sim.ts | 16 ---- test/mobile/keyboard.test.ts | 2 +- test/mobile/layout.test.ts | 2 +- test/mobile/settings.test.ts | 2 +- test/mobile/subagent-windows.test.ts | 2 +- test/mobile/tabs.test.ts | 2 +- test/mobile/visual-regression.test.ts | 2 +- test/mocks/index.ts | 7 +- test/mocks/mock-session.ts | 49 ---------- test/mocks/mock-state-store.ts | 60 ------------ test/mocks/test-helpers.ts | 35 +------ 57 files changed, 36 insertions(+), 963 deletions(-) delete mode 100644 src/session-task-cache.ts delete mode 100644 test/mocks/mock-state-store.ts diff --git a/scripts/test-local-llm-harnesses.ts b/scripts/test-local-llm-harnesses.ts index a47b43b4..60acbdd8 100644 --- a/scripts/test-local-llm-harnesses.ts +++ b/scripts/test-local-llm-harnesses.ts @@ -72,7 +72,7 @@ import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs'; import { tmpdir, homedir } from 'node:os'; import { join, delimiter, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { enabledClis } from '../src/config/cli-registry/index.js'; +import { enabledClis } from '../src/config/cli-registry/registry.js'; import type { CliEntry } from '../src/config/cli-registry/types.js'; import { buildCustomModelInjection, diff --git a/src/ai-checker-base.ts b/src/ai-checker-base.ts index 3ade7c2d..18b4c780 100644 --- a/src/ai-checker-base.ts +++ b/src/ai-checker-base.ts @@ -190,11 +190,6 @@ export abstract class AiCheckerBase< */ protected abstract getNegativeVerdict(): V; - /** - * Get the error verdict value. - */ - protected abstract getErrorVerdict(): V; - /** * Create an error result. */ diff --git a/src/ai-idle-checker.ts b/src/ai-idle-checker.ts index 4b462aec..df991dc6 100644 --- a/src/ai-idle-checker.ts +++ b/src/ai-idle-checker.ts @@ -165,10 +165,6 @@ export class AiIdleChecker extends AiCheckerBase -): string { - return renderLaunch(entry.launch, params, engineValues, gatesPassed); -} diff --git a/src/config/cli-registry/index.ts b/src/config/cli-registry/index.ts index d1698967..575b00fc 100644 --- a/src/config/cli-registry/index.ts +++ b/src/config/cli-registry/index.ts @@ -3,59 +3,4 @@ * @module config/cli-registry */ -export type { - ArgSpec, - CliCapabilities, - CliCredStore, - CliDiscovery, - CliEntry, - CliEnv, - CliId, - CliIdentityProbe, - CliLaunch, - CliOverlays, - CliRegistryFile, - CliVariant, - CliVersionProbe, - Cond, - EngineValue, - ParamSpec, - QuoteStyle, -} from './types.js'; -export { - matchesPattern, - TOKEN_PATTERNS, - SAFE_BARE_TOKEN, - compileVersionRegex, - MAX_VERSION_OUTPUT, -} from './patterns.js'; -export type { TokenPattern } from './patterns.js'; -export { renderLaunch, renderCliCommand } from './argv.js'; -export type { EngineValues, ParamValues } from './argv.js'; -export { CliEntrySchema } from './schema.js'; -export type { ValidatedCliEntry } from './schema.js'; -export { STOCK_CLIS } from './stock.js'; -export { - asCliId, - cliIds, - enabledCliIds, - enabledClis, - getCli, - listClis, - loadCliRegistry, - reloadCliRegistry, - resolveInstallCommandForPlatform, - resolveRegistry, -} from './registry.js'; -export type { LoadResult } from './registry.js'; -export { - COMPOSER_ANCHOR_KINDS, - isKnownLauncherProfile, - isKnownPredictProfile, - isKnownSetenvProfile, - LAUNCHER_PROFILE_NAMES, - PREDICT_PROFILES, - SETENV_PROFILE_NAMES, - TRANSCRIPT_READER_NAMES, -} from './profiles.js'; -export type { LauncherProfileName, SetenvProfileName } from './profiles.js'; +export { enabledCliIds, getCli } from './registry.js'; diff --git a/src/config/cli-registry/profiles.ts b/src/config/cli-registry/profiles.ts index 11519142..b05efa88 100644 --- a/src/config/cli-registry/profiles.ts +++ b/src/config/cli-registry/profiles.ts @@ -76,16 +76,6 @@ export const SETENV_PROFILE_NAMES = [ export type LauncherProfileName = (typeof LAUNCHER_PROFILE_NAMES)[number]; export type SetenvProfileName = (typeof SETENV_PROFILE_NAMES)[number]; -/** - * Transcript readers, selected via `capabilities.transcript`. Unlike the profile registries - * above this one is closed over the schema enum itself rather than an open string, since - * transcript format is a small, genuinely fixed set — see CliCapabilities['transcript']. - */ -export const TRANSCRIPT_READER_NAMES = ['claude-jsonl', 'codex-rollout', 'deepseek-zstd', 'none'] as const; - -/** Composer-row finders, selected via `capabilities.echo.anchor.kind`. Also schema-closed. */ -export const COMPOSER_ANCHOR_KINDS = ['glyph', 'cursor', 'none'] as const; - /** True when `name` is a predictive-echo profile this build actually implements. */ export function isKnownPredictProfile(name: string | undefined): boolean { return name !== undefined && Object.prototype.hasOwnProperty.call(PREDICT_PROFILES, name); diff --git a/src/config/cli-registry/registry.ts b/src/config/cli-registry/registry.ts index 8f251eeb..f67a091d 100644 --- a/src/config/cli-registry/registry.ts +++ b/src/config/cli-registry/registry.ts @@ -32,18 +32,10 @@ import { existsSync, readFileSync, renameSync, statSync } from 'node:fs'; import { dataPath } from '../instance.js'; -import type { CliEntry, CliId, CliRegistryFile } from './types.js'; +import type { CliEntry, CliRegistryFile } from './types.js'; import { CliEntrySchema } from './schema.js'; import { STOCK_CLIS } from './stock.js'; -/** Construct a validated CliId. Throws if `raw` is not a well-formed id — call at API boundaries. */ -export function asCliId(raw: string): CliId { - if (!/^[a-z][a-z0-9-]{0,23}$/.test(raw)) { - throw new Error(`invalid CLI id: ${JSON.stringify(raw)}`); - } - return raw as CliId; -} - function filePath(): string { return dataPath('clis.json'); } @@ -227,10 +219,6 @@ export function getCli(id: string): CliEntry | undefined { return listClis().find((e) => (e.id as string) === id); } -export function cliIds(): string[] { - return listClis().map((e) => e.id as string); -} - /** Every enabled entry's id, in registry order. */ export function enabledCliIds(): string[] { return enabledClis().map((e) => e.id as string); diff --git a/src/config/cli-registry/schema.ts b/src/config/cli-registry/schema.ts index 1cc5fef1..ad5185f9 100644 --- a/src/config/cli-registry/schema.ts +++ b/src/config/cli-registry/schema.ts @@ -817,5 +817,3 @@ export const CliEntrySchema = z }); } }); - -export type ValidatedCliEntry = z.infer; diff --git a/src/cron/cron-input.ts b/src/cron/cron-input.ts index 885f993f..9a12f9c7 100644 --- a/src/cron/cron-input.ts +++ b/src/cron/cron-input.ts @@ -7,8 +7,6 @@ import type { ConcurrencyPolicy, InputMode, PromptMode, ScheduleType } from '../types/cron.js'; import type { SessionMode } from '../types/session.js'; -export type { CronJob, CronJobRun, CronJobRunStatus, TriggerType } from '../types/cron.js'; - export interface CronJobInput { name: string; agentType: SessionMode; diff --git a/src/hooks-config.ts b/src/hooks-config.ts index 8c841c7a..70008bc5 100644 --- a/src/hooks-config.ts +++ b/src/hooks-config.ts @@ -13,7 +13,6 @@ * - `applyWorkspaceHooks(workspace, install?)` — the ONE install-vs-refresh decision * point every claude-session create path routes through (see its doc comment) * - `ensureCodemanHooks(casePath)` — safely installs/updates hooks for a managed case - * - `updateCaseEnvVars(casePath, envVars)` — merges env vars into settings * * Hook events generated: `idle_prompt`, `permission_prompt`, `elicitation_dialog`, * `elicitation_complete`, `elicitation_response`, `stop`, `teammate_idle`, @@ -632,37 +631,6 @@ export async function stripCaseEnvKeys(casePath: string, keysToRemove: readonly }); } -/** - * Updates env vars in .claude/settings.local.json for the given case path. - * Merges with existing env field; removes vars set to empty string. - */ -export async function updateCaseEnvVars(casePath: string, envVars: Record): Promise { - await withSafeSettingsWrite(casePath, 'env vars', async (claudeDir, settingsPath) => { - if (!(await pathExistsForWrite(claudeDir))) { - await mkdir(claudeDir, { recursive: true }); - } - - let existing: Record = {}; - try { - existing = JSON.parse(await readFile(settingsPath, 'utf-8')); - } catch { - existing = {}; - } - - const currentEnv = (existing.env as Record) || {}; - for (const [key, value] of Object.entries(envVars)) { - if (value) { - currentEnv[key] = value; - } else { - delete currentEnv[key]; - } - } - existing.env = currentEnv; - - await writeFile(settingsPath, JSON.stringify(existing, null, 2) + '\n'); - }); -} - /** * Updates the `model` field in .claude/settings.local.json for the given case path. * Pass a non-empty string to set, or empty/null to remove. diff --git a/src/prompts/index.ts b/src/prompts/index.ts index 3e91bc30..9d40c145 100644 --- a/src/prompts/index.ts +++ b/src/prompts/index.ts @@ -8,4 +8,4 @@ export { RESEARCH_AGENT_PROMPT } from './research-agent.js'; export { PLANNER_PROMPT } from './planner.js'; export { PHASE_EXECUTION_PROMPT, TEAM_LEAD_PROMPT, REPLAN_PROMPT, SINGLE_TASK_PROMPT } from './orchestrator.js'; -export { RALPH_STATUS_CONTRACT, buildRalphLoopPrompt, type RalphLoopPromptOptions } from './ralph.js'; +export { buildRalphLoopPrompt } from './ralph.js'; diff --git a/src/ralph-tracker.ts b/src/ralph-tracker.ts index 40f25158..239f4f91 100644 --- a/src/ralph-tracker.ts +++ b/src/ralph-tracker.ts @@ -882,13 +882,6 @@ export class RalphTracker extends EventEmitter { } } - /** - * Check if a phrase matches any valid completion phrase (primary or alternate). - */ - isValidCompletionPhrase(phrase: string): boolean { - return this.findMatchingCompletionPhrase(phrase) !== null; - } - /** * Find which completion phrase (primary or alternate) matches the given phrase. */ diff --git a/src/respawn-controller.ts b/src/respawn-controller.ts index c1b775ee..55b20a20 100644 --- a/src/respawn-controller.ts +++ b/src/respawn-controller.ts @@ -30,7 +30,7 @@ * - `DetectionStatus`, `ActiveTimerInfo` — status types * * Key methods: `start()`, `stop()`, `getStatus()`, `getConfig()`, - * `getDetectionStatus()`, `getActiveTimers()`, `getAggregateMetrics()`, + * `getDetectionStatus()`, `getActiveTimers()`, * `getTimingHistory()`, `getHealthScore()` * * @dependencies session (PTY output), ai-idle-checker, ai-plan-checker, @@ -76,14 +76,7 @@ import { AI_PLAN_CHECK_TIMEOUT_MS, AI_PLAN_CHECK_COOLDOWN_MS, } from './config/ai-defaults.js'; -import { - getErrorMessage, - type RespawnCycleMetrics, - type RespawnAggregateMetrics, - type RalphLoopHealthScore, - type TimingHistory, - type CycleOutcome, -} from './types.js'; +import { getErrorMessage, type RalphLoopHealthScore, type TimingHistory, type CycleOutcome } from './types.js'; // ========== Constants ========== @@ -1903,13 +1896,6 @@ export class RespawnController extends EventEmitter { this.emit('actionLog', action); } - /** - * Get recent action log entries. - */ - getRecentActions(): ActionLogEntry[] { - return [...this.recentActions]; - } - // ========== Multi-Layer Detection Methods ========== // Pattern detection delegated to ./respawn-patterns.js (isCompletionMessage, hasWorkingPattern, extractTokenCount) @@ -3178,23 +3164,6 @@ export class RespawnController extends EventEmitter { } } - /** - * Get aggregate metrics for monitoring. - * @returns Copy of aggregate metrics - */ - getAggregateMetrics(): RespawnAggregateMetrics { - return this.cycleMetrics.getAggregate(); - } - - /** - * Get recent cycle metrics for analysis. - * @param limit - Maximum number of metrics to return (default: 20) - * @returns Recent cycle metrics, newest first - */ - getRecentCycleMetrics(limit: number = 20): RespawnCycleMetrics[] { - return this.cycleMetrics.getRecent(limit); - } - // ========== P2-005: Health Score (delegated to respawn-health.ts) ========== /** diff --git a/src/session-task-cache.ts b/src/session-task-cache.ts deleted file mode 100644 index f08252e7..00000000 --- a/src/session-task-cache.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @fileoverview LRU cache for task descriptions parsed from terminal output. - * - * Stores descriptions extracted from Claude Code's Task tool invocations - * (e.g., "Explore(Check files)") keyed by timestamp. Used to correlate - * with SubagentWatcher discoveries for better window titles. - * - * @module session-task-cache - */ - -import { LRUMap } from './utils/lru-map.js'; - -/** Default maximum number of task descriptions to keep */ -const DEFAULT_MAX_SIZE = 100; - -/** Default maximum age for task descriptions (30 seconds) */ -const DEFAULT_MAX_AGE_MS = 30_000; - -/** - * LRU cache for task descriptions parsed from terminal output. - * - * Descriptions are keyed by the timestamp when they were parsed. - * Old entries are automatically cleaned up based on maxAgeMs. - * Size is bounded by the underlying LRUMap. - */ -export class SessionTaskCache { - private readonly cache: LRUMap; - private readonly maxAgeMs: number; - - constructor(maxSize: number = DEFAULT_MAX_SIZE, maxAgeMs: number = DEFAULT_MAX_AGE_MS) { - this.cache = new LRUMap({ maxSize }); - this.maxAgeMs = maxAgeMs; - } - - /** - * Add a task description at the given timestamp. - */ - add(timestamp: number, description: string): void { - this.cache.set(timestamp, description); - } - - /** - * Remove task descriptions older than maxAgeMs. - * LRUMap maintains insertion order, so we can break early - * once we find a non-expired entry. - */ - private cleanupOld(): void { - const cutoff = Date.now() - this.maxAgeMs; - for (const timestamp of this.cache.keysInOrder()) { - if (timestamp < cutoff) { - this.cache.delete(timestamp); - } else { - break; - } - } - } - - /** - * Get all recent task descriptions sorted by timestamp (most recent first). - */ - getAll(): Array<{ timestamp: number; description: string }> { - this.cleanupOld(); - const results: Array<{ timestamp: number; description: string }> = []; - for (const [timestamp, description] of this.cache) { - results.push({ timestamp, description }); - } - return results.sort((a, b) => b.timestamp - a.timestamp); - } - - /** - * Find a task description that was parsed close to a given timestamp. - * Used to correlate with SubagentWatcher discoveries. - * - * @param subagentStartTime - The timestamp when the subagent was discovered - * @param maxAgeMs - Maximum age difference to consider (default 10 seconds) - * @returns The matching description or undefined - */ - findNear(subagentStartTime: number, maxAgeMs: number = 10000): string | undefined { - this.cleanupOld(); - - let bestMatch: { timestamp: number; description: string } | undefined; - let bestDiff = Infinity; - - for (const [timestamp, description] of this.cache) { - const diff = Math.abs(subagentStartTime - timestamp); - if (diff < maxAgeMs && diff < bestDiff) { - bestMatch = { timestamp, description }; - bestDiff = diff; - } - } - - return bestMatch?.description; - } - - /** - * Clear all cached descriptions. - */ - clear(): void { - this.cache.clear(); - } -} diff --git a/src/session.ts b/src/session.ts index e428484f..27e9f9f2 100644 --- a/src/session.ts +++ b/src/session.ts @@ -17,7 +17,7 @@ * * Key methods: `runPrompt()`, `startInteractive()`, `startShell()`, * `writeViaMux()`, `toState()`, `stop()`, `resize()`, `isIdle()`, - * `setAutoCompact()`, `findTaskDescriptionNear()`, `getTerminalBuffer()` + * `setAutoCompact()`, `getTerminalBuffer()` * * @dependencies session-cli-builder (args/env), session-auto-ops (auto-compact/clear), * ralph-tracker (todo/completion parsing), bash-tool-parser (tool invocation tracking), @@ -117,7 +117,6 @@ import { SPINNER_PATTERN, CLAUDE_WORKING_LINE_PATTERN, MAX_SESSION_TOKENS, - execPattern, getClaudeCliVersion, getClaudeBinaryPath, spawnPtyWithHelperRepair, @@ -156,7 +155,6 @@ import { } from './session-cli-builder.js'; import { SessionAutoOps, type ContextReport } from './session-auto-ops.js'; import { detectUsageLimitPause } from './usage-limit-patterns.js'; -import { SessionTaskCache } from './session-task-cache.js'; import { InteractivePtyExitBreaker } from './session-pty-exit-breaker.js'; import { isGeneratedSessionName, SubmittedPromptTracker } from './session-auto-name.js'; import { parseTerminalAttachmentRequests } from './attachment-magic.js'; @@ -222,12 +220,6 @@ const MAX_CLAUDE_SESSION_CHAIN = 32; // eslint-disable-next-line no-control-regex const FOCUS_ESCAPE_FILTER = /\x1b\[\?1004[hl]|\x1b\[[IO]/g; -// Pattern to match Task tool invocations in terminal output -// Matches: "Explore(Description)", "Task(Description)", "Bash(Description)", etc. -// The prefix characters vary (●, ·, ✶, etc.) so we don't require them -// We look for the tool name followed by (description) -const TASK_TOOL_PATTERN = /\b(Explore|Task|Bash|Plan|general-purpose)\(([^)]+)\)/g; - // Pre-compiled patterns for hot paths (avoid regex compilation per call) /** Pattern to strip leading ANSI escapes and whitespace from terminal buffer */ // eslint-disable-next-line no-control-regex @@ -235,8 +227,6 @@ const LEADING_ANSI_WHITESPACE_PATTERN = /^(\x1b\[\??[\d;]*[A-Za-z]|[\s\r\n])+/; /** Pattern to match Ctrl+L (form feed) characters */ // eslint-disable-next-line no-control-regex const CTRL_L_PATTERN = /\x0c/g; -/** Pattern to split by newlines (CR or LF) */ -const NEWLINE_SPLIT_PATTERN = /\r?\n/; /** * True for external-CLI run modes (non-Claude) that use their own TUI and output format: @@ -574,9 +564,6 @@ export class Session extends EventEmitter { readonly createdAt: number; readonly mode: SessionMode; - // Task description cache (extracted to SessionTaskCache) - private _taskCache = new SessionTaskCache(); - private _name: string; private _nameSource: SessionNameSource; /** `pinCliSessionName` as of the last spawn (see {@link _refreshCliNamePin}). */ @@ -834,10 +821,6 @@ export class Session extends EventEmitter { // Ralph tracking (Ralph Wiggum loops and todo lists inside Claude Code) private _ralphTracker: RalphTracker; - // Agent tree tracking - private _parentAgentId: string | null = null; - private _childAgentIds: string[] = []; - // Bounded dedup set for terminal attachment magic-links already requested. private _attachmentMagicSeen = new Set(); private _attachmentHistory: SessionAttachmentHistoryItem[] = []; @@ -961,9 +944,7 @@ export class Session extends EventEmitter { toolsUpdate: (tools: ActiveBashTool[]) => void; } | null = null; - // Task descriptions parsed from terminal output — delegated to SessionTaskCache - - // Throttle expensive PTY processing (Ralph, bash parser, task descriptions) + // Throttle expensive PTY processing (Ralph, bash parser) // Accumulates clean data between processing windows to avoid running regex on every chunk private _lastExpensiveProcessTime: number = 0; private _pendingCleanData: string = ''; @@ -1999,14 +1980,6 @@ export class Session extends EventEmitter { return this._taskTracker; } - get runningTaskCount(): number { - return this._taskTracker.getRunningCount(); - } - - get taskTree(): BackgroundTask[] { - return this._taskTracker.getTaskTree(); - } - get taskStats(): { total: number; running: number; completed: number; failed: number } { return this._taskTracker.getStats(); } @@ -2028,38 +2001,10 @@ export class Session extends EventEmitter { return this._ralphTracker.getTodoStats(); } - // Bash tool tracking getters - get bashToolParser(): BashToolParser { - return this._bashToolParser; - } - get activeTools(): ActiveBashTool[] { return this._bashToolParser.activeTools; } - get parentAgentId(): string | null { - return this._parentAgentId; - } - - set parentAgentId(value: string | null) { - this._parentAgentId = value; - } - - get childAgentIds(): string[] { - return [...this._childAgentIds]; - } - - addChildAgentId(agentId: string): void { - if (!this._childAgentIds.includes(agentId)) { - this._childAgentIds.push(agentId); - } - } - - removeChildAgentId(agentId: string): void { - const idx = this._childAgentIds.indexOf(agentId); - if (idx >= 0) this._childAgentIds.splice(idx, 1); - } - // Nice priority config getters and setters get niceConfig(): NiceConfig { return { ...this._niceConfig }; @@ -2519,8 +2464,6 @@ export class Session extends EventEmitter { ralphEnabled: this._ralphTracker.enabled, ralphAutoEnableDisabled: this._ralphTracker.autoEnableDisabled || undefined, ralphCompletionPhrase: this._ralphTracker.loopState.completionPhrase || undefined, - parentAgentId: this._parentAgentId || undefined, - childAgentIds: this._childAgentIds.length > 0 ? this._childAgentIds : undefined, niceEnabled: this._niceConfig.enabled, niceValue: this._niceConfig.niceValue, color: this._color, @@ -4411,7 +4354,7 @@ export class Session extends EventEmitter { } /** - * Process expensive parsers (ANSI strip, Ralph, bash tool, token, CLI info, task descriptions). + * Process expensive parsers (ANSI strip, Ralph, bash tool, token, CLI info). * Called on a throttled schedule (every EXPENSIVE_PROCESS_INTERVAL_MS) instead of on every * PTY data chunk. Receives accumulated raw data to process in one batch. */ @@ -4464,11 +4407,6 @@ export class Session extends EventEmitter { this.parseClaudeCodeInfo(getCleanData()); } - // Parse task descriptions from terminal output (e.g., "Explore(Check files)") - if (rawData.includes('(') && rawData.includes(')')) { - this.parseTaskDescriptionsFromTerminalData(getCleanData()); - } - // Legacy gerunds, Claude-only. The status-line pattern above already ran for every // CLI that declares one, so this adds only the older wording. Current Claude // randomizes the word ("Actualizing…", "Finagling…"), so these catch a fraction of @@ -4970,67 +4908,10 @@ export class Session extends EventEmitter { } else if (trimmed) { this._textOutput.append(line + '\n'); } - - // Parse task descriptions from terminal output (e.g., "Explore(Description)") - // This captures the short description from Claude Code's Task tool output - // Use direct method since cleanLine is already ANSI-stripped (line 1460) - this.parseTaskDescriptionsDirect(cleanLine); } // Note: BufferAccumulator auto-trims when max size exceeded } - /** - * Parse task descriptions from terminal data (may contain multiple lines). - * Called from interactive mode's onData handler with ANSI-stripped data. - * @param cleanData - Terminal data with ANSI codes already stripped - */ - private parseTaskDescriptionsFromTerminalData(cleanData: string): void { - // Quick pre-check: skip if no parentheses present - if (!cleanData.includes('(') || !cleanData.includes(')')) return; - - // Split by newlines and process each line (data already ANSI-stripped) - const lines = cleanData.split(NEWLINE_SPLIT_PATTERN); - for (const line of lines) { - this.parseTaskDescriptionsDirect(line); - } - } - - /** - * Parse task descriptions from a pre-cleaned line (no ANSI codes). - * Used by both processOutput() and parseTaskDescriptionsFromTerminalData(). - */ - private parseTaskDescriptionsDirect(cleanLine: string): void { - // Quick pre-check: skip expensive regex if no common tool patterns present - if (!cleanLine.includes('(') || !cleanLine.includes(')')) return; - - execPattern(TASK_TOOL_PATTERN, cleanLine, (match) => { - const description = match[2].trim(); - if (description && description.length > 0) { - this._taskCache.add(Date.now(), description); - } - }); - } - - /** - * Get recent task descriptions parsed from terminal output. - * Returns descriptions sorted by timestamp (most recent first). - */ - getRecentTaskDescriptions(): Array<{ timestamp: number; description: string }> { - return this._taskCache.getAll(); - } - - /** - * Find a task description that was parsed close to a given timestamp. - * Used to correlate with SubagentWatcher discoveries. - * - * @param subagentStartTime - The timestamp when the subagent was discovered - * @param maxAgeMs - Maximum age difference to consider (default 10 seconds) - * @returns The matching description or undefined - */ - findTaskDescriptionNear(subagentStartTime: number, maxAgeMs: number = 10000): string | undefined { - return this._taskCache.findNear(subagentStartTime, maxAgeMs); - } - // Parse token count from Claude's status line in interactive mode // Matches patterns like "123.4k tokens", "5234 tokens", "1.2M tokens" // @@ -5981,10 +5862,6 @@ export class Session extends EventEmitter { this._status = killMux ? 'stopped' : 'idle'; this._currentTaskId = null; - // Clear task description cache and agent tree to prevent memory leak - this._taskCache.clear(); - this._childAgentIds = []; - // Kill the associated mux session if requested if (killMux && this._mux) { // Try to kill mux session even if _muxSession is not set (e.g., restored sessions) @@ -6038,6 +5915,5 @@ export class Session extends EventEmitter { this._messages = []; this._taskTracker.clear(); this._ralphTracker.clear(); - this._taskCache.clear(); } } diff --git a/src/state-store.ts b/src/state-store.ts index 88510c93..8e05de5e 100644 --- a/src/state-store.ts +++ b/src/state-store.ts @@ -429,29 +429,6 @@ export class StateStore { } } - /** - * Attempt to recover state from backup file. - * Call this if main state file is corrupt. - */ - recoverFromBackup(): boolean { - const backupPath = this.filePath + '.bak'; - try { - if (existsSync(backupPath)) { - const backupContent = readFileSync(backupPath, 'utf-8'); - const parsed = JSON.parse(backupContent) as Partial; - this.state = this._mergeWithInitialState(parsed); - console.log('[StateStore] Successfully recovered state from backup'); - // Reset circuit breaker after successful recovery - this.circuitBreakerOpen = false; - this.consecutiveSaveFailures = 0; - return true; - } - } catch (err) { - console.error('[StateStore] Failed to recover from backup:', err); - } - return false; - } - /** * Reset the circuit breaker (for manual intervention). */ diff --git a/src/team-watcher.ts b/src/team-watcher.ts index 1822c0e6..d54d1c69 100644 --- a/src/team-watcher.ts +++ b/src/team-watcher.ts @@ -15,7 +15,7 @@ import { join } from 'node:path'; import { watch as chokidarWatch, type FSWatcher as ChokidarWatcher } from 'chokidar'; -import type { TeamConfig, TeamMember, TeamTask, InboxMessage } from './types.js'; +import type { TeamConfig, TeamTask, InboxMessage } from './types.js'; import { LRUMap } from './utils/lru-map.js'; import { TEAM_POLL_INTERVAL_MS, MAX_CACHED_TEAMS, MAX_CACHED_TASKS } from './config/team-config.js'; @@ -175,17 +175,6 @@ export class TeamWatcher extends EventEmitter { return activeTasks > 0; } - /** Get teammates that have real tmux panes (not in-process) */ - getTmuxPaneTeammates(teamName: string): Array { - const team = this.teams.get(teamName); - if (!team) return []; - - return team.members.filter( - (m): m is TeamMember & { tmuxPaneId: string } => - m.agentType !== 'team-lead' && !!m.tmuxPaneId && m.tmuxPaneId !== 'in-process' - ); - } - /** Get count of active teammates for a session */ getActiveTeammateCount(sessionId: string): number { const team = this.getTeamForSession(sessionId); diff --git a/src/tmux-manager.ts b/src/tmux-manager.ts index 7d2e5451..5f77b116 100644 --- a/src/tmux-manager.ts +++ b/src/tmux-manager.ts @@ -4451,21 +4451,6 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer { } } - /** - * Sync mouse mode based on pane count: enable if split (>1 pane), disable if single. - * Called by TeamWatcher when teammates spawn/despawn panes. - * Uses `tmux list-panes` for bulletproof detection — counts actual panes, not config. - */ - async syncMouseMode(muxName: string): Promise { - if (IS_TEST_MODE) return true; - const panes = await this.listPanes(muxName); - if (panes.length > 1) { - return this.enableMouseMode(muxName); - } else { - return this.disableMouseMode(muxName); - } - } - /** * List all panes in a tmux session. * Returns structured info for each pane. @@ -4782,67 +4767,6 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer { } } - /** - * Start piping pane output to a file using tmux pipe-pane. - * Only pipes output direction (-O) to avoid echoing input. - */ - startPipePane(muxName: string, paneTarget: string, outputFile: string): boolean { - if (IS_TEST_MODE) return true; - if (!isValidMuxName(muxName)) { - console.error('[TmuxManager] Invalid session name in startPipePane:', muxName); - return false; - } - if (!SAFE_PANE_TARGET_PATTERN.test(paneTarget)) { - console.error('[TmuxManager] Invalid pane target:', paneTarget); - return false; - } - if (!isValidPath(outputFile)) { - console.error('[TmuxManager] Invalid output file path:', outputFile); - return false; - } - - const target = paneTarget.startsWith('%') ? `${muxName}.${paneTarget}` : `${muxName}.%${paneTarget}`; - - try { - execSync(`${this.tmux()} pipe-pane -O -t ${shellescape(target)} ${shellescape('cat >> ' + outputFile)}`, { - encoding: 'utf-8', - timeout: EXEC_TIMEOUT_MS, - }); - return true; - } catch (err) { - console.error('[TmuxManager] Failed to start pipe-pane:', err); - return false; - } - } - - /** - * Stop piping pane output (calling pipe-pane with no command stops piping). - */ - stopPipePane(muxName: string, paneTarget: string): boolean { - if (IS_TEST_MODE) return true; - if (!isValidMuxName(muxName)) { - console.error('[TmuxManager] Invalid session name in stopPipePane:', muxName); - return false; - } - if (!SAFE_PANE_TARGET_PATTERN.test(paneTarget)) { - console.error('[TmuxManager] Invalid pane target:', paneTarget); - return false; - } - - const target = paneTarget.startsWith('%') ? `${muxName}.${paneTarget}` : `${muxName}.%${paneTarget}`; - - try { - execSync(`${this.tmux()} pipe-pane -t ${shellescape(target)}`, { - encoding: 'utf-8', - timeout: EXEC_TIMEOUT_MS, - }); - return true; - } catch (err) { - console.error('[TmuxManager] Failed to stop pipe-pane:', err); - return false; - } - } - getAttachCommand(): string { return 'tmux'; } diff --git a/src/types/foreign-tmux.ts b/src/types/foreign-tmux.ts index 47955a66..2ad2e2ac 100644 --- a/src/types/foreign-tmux.ts +++ b/src/types/foreign-tmux.ts @@ -29,20 +29,6 @@ import type { SessionMode } from './session.js'; /** Where a foreign tmux server lives. Mirrors Codeman's own three locations. */ export type ForeignTmuxLocationKind = 'local' | 'docker' | 'remote'; -/** - * A location to scan. `hostId` refers to the saved docker-host / remote-host - * registry entry, so the transport can be rebuilt without trusting the browser. - */ -export interface ForeignTmuxLocation { - kind: ForeignTmuxLocationKind; - /** Registry id (`docker-hosts.json` / `remote-hosts.json`). Absent for local. */ - hostId?: string; - /** Display label for the UI. Absent for local. */ - label?: string; - /** Container to `docker exec` into (`kind === 'docker'`). */ - containerName?: string; -} - /** One pane row from the probe script, after parsing. */ export interface ForeignPaneProbe { socketPath: string; diff --git a/src/types/local-model-install.ts b/src/types/local-model-install.ts index 39dc7d83..5e2e4e13 100644 --- a/src/types/local-model-install.ts +++ b/src/types/local-model-install.ts @@ -698,9 +698,6 @@ export interface LocalModelRefusal { message: LocalModelInstallMessage; } -/** What every verb answers on success: the view after the verb. */ -export type LocalModelVerbAnswer = LocalModelInstallView; - /** * Why a verb was refused with 409. * - `job-running`: a job is running (a second install, a resume, a remove or a runtime action). diff --git a/src/types/session.ts b/src/types/session.ts index f3441ff8..edf089a2 100644 --- a/src/types/session.ts +++ b/src/types/session.ts @@ -1103,10 +1103,6 @@ export interface SessionState { 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) */ diff --git a/src/utils/antigravity-cli-resolver.ts b/src/utils/antigravity-cli-resolver.ts index f42197eb..3934ded4 100644 --- a/src/utils/antigravity-cli-resolver.ts +++ b/src/utils/antigravity-cli-resolver.ts @@ -9,11 +9,7 @@ import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { - createCliExecutableResolver, - formatCliNotFoundMessage, - type CliResolverHost, -} from './cli-executable-resolver.js'; +import { createCliExecutableResolver, type CliResolverHost } from './cli-executable-resolver.js'; /** Common directories where the Antigravity CLI binary may be installed */ /** @@ -23,9 +19,6 @@ import { */ const ANTIGRAVITY_SEARCH_DIRS = (): string[] => (getCli('antigravity')?.discovery.searchDirs ?? []).map(expandHome); -const ANTIGRAVITY_NOT_FOUND = - 'Antigravity CLI not found. Install with: curl -fsSL https://antigravity.google/cli/install.sh | bash'; - function createAntigravityResolver(host?: CliResolverHost, now?: () => number) { return createCliExecutableResolver({ binary: 'agy', searchDirs: ANTIGRAVITY_SEARCH_DIRS, now }, host); } @@ -53,7 +46,3 @@ export function resolveAntigravityDir(): string | null { export function isAntigravityAvailable(): boolean { return resolveAntigravityDir() !== null; } - -export function getAntigravityNotFoundMessage(): string { - return formatCliNotFoundMessage(ANTIGRAVITY_NOT_FOUND, antigravityResolver.diagnostics()); -} diff --git a/src/utils/claude-cli-resolver.ts b/src/utils/claude-cli-resolver.ts index 04a1b15f..d84267df 100644 --- a/src/utils/claude-cli-resolver.ts +++ b/src/utils/claude-cli-resolver.ts @@ -12,7 +12,7 @@ import { execFileSync } from 'node:child_process'; import { delimiter, join } from 'node:path'; import { homedir } from 'node:os'; import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js'; -import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-executable-resolver.js'; +import { createCliExecutableResolver } from './cli-executable-resolver.js'; /** Common directories where the Claude CLI binary may be installed */ const CLAUDE_SEARCH_DIRS = [ @@ -24,7 +24,6 @@ const CLAUDE_SEARCH_DIRS = [ ]; const claudeResolver = createCliExecutableResolver({ binary: 'claude', searchDirs: CLAUDE_SEARCH_DIRS }); -const CLAUDE_NOT_FOUND = 'Claude CLI not found. Install it with: curl -fsSL https://claude.ai/install.sh | bash'; /** * Returns true if the Claude CLI binary can be located (via `which` or one of @@ -46,10 +45,6 @@ export function findClaudeDir(): string | null { return claudeResolver.resolve()?.directory ?? null; } -export function getClaudeNotFoundMessage(): string { - return formatCliNotFoundMessage(CLAUDE_NOT_FOUND, claudeResolver.diagnostics()); -} - /** * Returns an absolute path to the `claude` binary, falling back to the bare * name `'claude'` when it cannot be located (so PATH resolution still gets a diff --git a/src/utils/cli-launcher.ts b/src/utils/cli-launcher.ts index 2e483c3a..b4a18df9 100644 --- a/src/utils/cli-launcher.ts +++ b/src/utils/cli-launcher.ts @@ -82,26 +82,6 @@ export async function resolveCliLaunchError(mode: string, rawConfig?: Record number -): RegistryResolver { - return createRegistryResolver(entry, probe ?? probeCliCandidate, host, now); -} - /** * One memoized resolver per id, for the process lifetime — the same caching the per-CLI * modules already do for themselves, just keyed by id so generic code holding only a diff --git a/src/utils/codex-cli-resolver.ts b/src/utils/codex-cli-resolver.ts index ccf4dd03..13c75fcb 100644 --- a/src/utils/codex-cli-resolver.ts +++ b/src/utils/codex-cli-resolver.ts @@ -10,7 +10,7 @@ import { spawn } from 'node:child_process'; import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-executable-resolver.js'; +import { createCliExecutableResolver } from './cli-executable-resolver.js'; import { parseCodexRateLimitsResponse, type StatusTelemetry } from '../usage-telemetry.js'; /** @@ -22,7 +22,6 @@ const CODEX_SEARCH_DIRS = (): string[] => (getCli('codex')?.discovery.searchDirs const CODEX_BINARY = process.platform === 'win32' ? 'codex.exe' : 'codex'; const codexResolver = createCliExecutableResolver({ binary: CODEX_BINARY, searchDirs: CODEX_SEARCH_DIRS }); -const CODEX_NOT_FOUND = 'Codex CLI not found. Install with: npm install -g @openai/codex'; /** * Finds the directory containing the `codex` binary. @@ -47,10 +46,6 @@ export function isCodexAvailable(): boolean { return resolveCodexDir() !== null; } -export function getCodexNotFoundMessage(): string { - return formatCliNotFoundMessage(CODEX_NOT_FOUND, codexResolver.diagnostics()); -} - type CodexRateLimitsRequest = (binaryPath: string, clientVersion: string) => Promise; const APP_SERVER_TIMEOUT_MS = 10_000; diff --git a/src/utils/copilot-cli-resolver.ts b/src/utils/copilot-cli-resolver.ts index 636dbf2c..c1410fd0 100644 --- a/src/utils/copilot-cli-resolver.ts +++ b/src/utils/copilot-cli-resolver.ts @@ -17,11 +17,7 @@ import { execFileSync } from 'node:child_process'; import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js'; import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { - createCliExecutableResolver, - formatCliNotFoundMessage, - type CliResolverHost, -} from './cli-executable-resolver.js'; +import { createCliExecutableResolver, type CliResolverHost } from './cli-executable-resolver.js'; /** * Directories probed after `which`, read from this CLI's registry entry so the spawn @@ -36,8 +32,6 @@ const COPILOT_SEARCH_DIRS = (): string[] => (getCli('copilot')?.discovery.search */ export const COPILOT_VERSION_REGEX = /GitHub Copilot CLI (\d+\.\d+\.\d+)/; -const COPILOT_NOT_FOUND = 'GitHub Copilot CLI not found. Install with: npm install -g @github/copilot'; - /** * Run `copilot --version` on a candidate path and return the trimmed version when * it is the GitHub Copilot CLI. Returns null for anything else — a missing @@ -125,10 +119,6 @@ export function isCopilotAvailable(): boolean { return resolveCopilotDir() !== null; } -export function getCopilotNotFoundMessage(): string { - return formatCliNotFoundMessage(COPILOT_NOT_FOUND, copilotResolver.diagnostics()); -} - /** * Version reported by the resolved `copilot` binary, or null when copilot is * unavailable. Surfaced through `GET /api/copilot/status` so a misresolution is diff --git a/src/utils/deepseek-cli-resolver.ts b/src/utils/deepseek-cli-resolver.ts index 498967aa..2bf25433 100644 --- a/src/utils/deepseek-cli-resolver.ts +++ b/src/utils/deepseek-cli-resolver.ts @@ -31,7 +31,7 @@ */ import { execFileSync } from 'node:child_process'; -import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { readdirSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { homedir } from 'node:os'; import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js'; @@ -379,11 +379,6 @@ export function getDeepSeekCliVersion(): string | null { return deepSeekResolver.resolve()?.metadata ?? null; } -/** Does the named profile exist and can it drive a pane? */ -export function profileExists(name: string): boolean { - return existsSync(join(resolveDshHome(), 'profiles', name, 'package.json')); -} - /** * Why a DeepSeek session cannot start, or null when it can. * diff --git a/src/utils/gemini-cli-resolver.ts b/src/utils/gemini-cli-resolver.ts index a1903a92..bfdf029c 100644 --- a/src/utils/gemini-cli-resolver.ts +++ b/src/utils/gemini-cli-resolver.ts @@ -9,7 +9,7 @@ import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-executable-resolver.js'; +import { createCliExecutableResolver } from './cli-executable-resolver.js'; /** Common directories where the Gemini CLI binary may be installed */ /** @@ -20,7 +20,6 @@ import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-exe const GEMINI_SEARCH_DIRS = (): string[] => (getCli('gemini')?.discovery.searchDirs ?? []).map(expandHome); const geminiResolver = createCliExecutableResolver({ binary: 'gemini', searchDirs: GEMINI_SEARCH_DIRS }); -const GEMINI_NOT_FOUND = 'Gemini CLI not found. Install with: npm install -g @google/gemini-cli'; /** * Finds the directory containing the `gemini` binary. @@ -38,7 +37,3 @@ export function resolveGeminiDir(): string | null { export function isGeminiAvailable(): boolean { return resolveGeminiDir() !== null; } - -export function getGeminiNotFoundMessage(): string { - return formatCliNotFoundMessage(GEMINI_NOT_FOUND, geminiResolver.diagnostics()); -} diff --git a/src/utils/grok-cli-resolver.ts b/src/utils/grok-cli-resolver.ts index 5d5f324e..c63ee589 100644 --- a/src/utils/grok-cli-resolver.ts +++ b/src/utils/grok-cli-resolver.ts @@ -23,11 +23,7 @@ import { execFileSync } from 'node:child_process'; import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js'; import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { - createCliExecutableResolver, - formatCliNotFoundMessage, - type CliResolverHost, -} from './cli-executable-resolver.js'; +import { createCliExecutableResolver, type CliResolverHost } from './cli-executable-resolver.js'; /** Common directories where the Grok CLI binary may be installed */ /** @@ -49,8 +45,6 @@ const GROK_SEARCH_DIRS = (): string[] => (getCli('grok')?.discovery.searchDirs ? */ export const GROK_VERSION_REGEX = /(?:^|\s)(\d+\.\d+\.\d+)/; -const GROK_NOT_FOUND = 'Grok CLI not found. Install with: curl -fsSL https://x.ai/cli/install.sh | bash'; - /** * Run `grok --version` on a candidate path and return the version token when it * looks like the coding agent. Returns null for anything else: a missing @@ -137,10 +131,6 @@ export function isGrokAvailable(): boolean { return resolveGrokDir() !== null; } -export function getGrokNotFoundMessage(): string { - return formatCliNotFoundMessage(GROK_NOT_FOUND, grokResolver.diagnostics()); -} - /** * Version reported by the resolved `grok` binary, or null when grok is * unavailable. Surfaced through `GET /api/grok/status` so a misresolution is diff --git a/src/utils/index.ts b/src/utils/index.ts index 93124e5a..245b6be1 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,7 +12,7 @@ export { Debouncer, KeyedDebouncer } from './debouncer.js'; export { startEventLoopMonitor } from './event-loop-monitor.js'; export type { EventLoopMonitorHandle } from './event-loop-monitor.js'; export { StaleExpirationMap } from './stale-expiration-map.js'; -export { isProcessRunning, waitForProcessesExit, PROCESS_EXIT_POLL_MS } from './process-exit-wait.js'; +export { waitForProcessesExit } from './process-exit-wait.js'; export { ANSI_ESCAPE_PATTERN_FULL, ANSI_ESCAPE_PATTERN_SIMPLE, @@ -30,59 +30,13 @@ export { assertNever } from './type-safety.js'; export { toSingleLine } from './single-line.js'; export { wrapWithNice } from './nice-wrapper.js'; export { resolveLocalShell, loginShellArgs } from './shell-resolver.js'; -export { - findClaudeDir, - getAugmentedPath, - getClaudeCliVersion, - getClaudeBinaryPath, - getClaudeNotFoundMessage, -} from './claude-cli-resolver.js'; +export { getAugmentedPath, getClaudeCliVersion, getClaudeBinaryPath } from './claude-cli-resolver.js'; export { spawnPtyWithHelperRepair } from './node-pty-repair.js'; -export { resolveOpenCodeDir, getOpenCodeNotFoundMessage } from './opencode-cli-resolver.js'; -export { - resolveCodexDir, - resolveCodexBinaryPath, - isCodexAvailable, - getCodexNotFoundMessage, - readCodexPlanUsage, -} from './codex-cli-resolver.js'; -export { resolveGeminiDir, isGeminiAvailable, getGeminiNotFoundMessage } from './gemini-cli-resolver.js'; -export { - resolveAntigravityDir, - isAntigravityAvailable, - getAntigravityNotFoundMessage, -} from './antigravity-cli-resolver.js'; -export { resolvePiDir, isPiAvailable, getPiCliVersion, getPiNotFoundMessage } from './pi-cli-resolver.js'; -export { resolveGrokDir, isGrokAvailable, getGrokCliVersion, getGrokNotFoundMessage } from './grok-cli-resolver.js'; -export { - resolveDeepSeekDir, - isDeepSeekAvailable, - isDeepSeekRunnable, - getDeepSeekCliVersion, - getDeepSeekNotFoundMessage, - listDeepSeekProfiles, - resolveDefaultDeepSeekProfile, - isLaunchableProfile, - resolveDshHome, - profileExists, -} from './deepseek-cli-resolver.js'; -export type { DeepSeekProfile, DeepSeekProfileKind } from './deepseek-cli-resolver.js'; -export { compileFileQuery, matchFileQuery } from './file-query.js'; -export type { FileQueryMatcher } from './file-query.js'; -export { resolveOmpDir, isOmpAvailable, getOmpNotFoundMessage, getOmpCliVersion } from './omp-cli-resolver.js'; -export { - resolveCopilotDir, - isCopilotAvailable, - getCopilotNotFoundMessage, - getCopilotCliVersion, -} from './copilot-cli-resolver.js'; -export { readCopilotPlanUsage, resolveCopilotToken } from './copilot-plan-usage.js'; export { boundedPathExists, describeUnknownPath, probePath, probePathKind, isNearStalledPath, - unknownPathReason, } from './bounded-path-probe.js'; -export type { PathProbeState, PathProbeKind, PathProbeOptions } from './bounded-path-probe.js'; +export type { PathProbeOptions } from './bounded-path-probe.js'; diff --git a/src/utils/omp-cli-resolver.ts b/src/utils/omp-cli-resolver.ts index 9c169366..9fb4d50c 100644 --- a/src/utils/omp-cli-resolver.ts +++ b/src/utils/omp-cli-resolver.ts @@ -17,11 +17,7 @@ import { execFileSync } from 'node:child_process'; import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js'; import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { - createCliExecutableResolver, - formatCliNotFoundMessage, - type CliResolverHost, -} from './cli-executable-resolver.js'; +import { createCliExecutableResolver, type CliResolverHost } from './cli-executable-resolver.js'; /** * Directories probed after `which`, read from this CLI's registry entry so the spawn @@ -42,8 +38,6 @@ const OMP_SEARCH_DIRS = (): string[] => (getCli('omp')?.discovery.searchDirs ?? */ export const OMP_VERSION_REGEX = /(?:^|\s)omp\/(\d+\.\d+\.\d+)/; -const OMP_NOT_FOUND = 'OMP CLI not found. Install with: curl -fsSL https://omp.sh/install | sh'; - /** * Run `omp --version` on a candidate path and return the trimmed version when * it looks like the coding agent. Returns null for anything else — a missing @@ -125,10 +119,6 @@ export function isOmpAvailable(): boolean { return resolveOmpDir() !== null; } -export function getOmpNotFoundMessage(): string { - return formatCliNotFoundMessage(OMP_NOT_FOUND, ompResolver.diagnostics()); -} - /** * Version reported by the resolved `omp` binary, or null when omp is * unavailable. Surfaced through `GET /api/omp/status` so a misresolution is diff --git a/src/utils/opencode-cli-resolver.ts b/src/utils/opencode-cli-resolver.ts index f1d71012..07bbeb17 100644 --- a/src/utils/opencode-cli-resolver.ts +++ b/src/utils/opencode-cli-resolver.ts @@ -9,7 +9,7 @@ import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-executable-resolver.js'; +import { createCliExecutableResolver } from './cli-executable-resolver.js'; /** Common directories where the OpenCode CLI binary may be installed */ /** @@ -20,7 +20,6 @@ import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-exe const OPENCODE_SEARCH_DIRS = (): string[] => (getCli('opencode')?.discovery.searchDirs ?? []).map(expandHome); const openCodeResolver = createCliExecutableResolver({ binary: 'opencode', searchDirs: OPENCODE_SEARCH_DIRS }); -const OPENCODE_NOT_FOUND = 'OpenCode CLI not found. Install with: curl -fsSL https://opencode.ai/install | bash'; /** * Finds the directory containing the `opencode` binary. @@ -39,7 +38,3 @@ export function resolveOpenCodeDir(): string | null { export function isOpenCodeAvailable(): boolean { return resolveOpenCodeDir() !== null; } - -export function getOpenCodeNotFoundMessage(): string { - return formatCliNotFoundMessage(OPENCODE_NOT_FOUND, openCodeResolver.diagnostics()); -} diff --git a/src/utils/pi-cli-resolver.ts b/src/utils/pi-cli-resolver.ts index b6ad3a35..62a93852 100644 --- a/src/utils/pi-cli-resolver.ts +++ b/src/utils/pi-cli-resolver.ts @@ -19,11 +19,7 @@ import { execFileSync } from 'node:child_process'; import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js'; import { getCli } from '../config/cli-registry/registry.js'; import { expandHome } from './cli-resolver.js'; -import { - createCliExecutableResolver, - formatCliNotFoundMessage, - type CliResolverHost, -} from './cli-executable-resolver.js'; +import { createCliExecutableResolver, type CliResolverHost } from './cli-executable-resolver.js'; /** Common directories where the Pi CLI binary may be installed */ /** @@ -48,8 +44,6 @@ const PI_SEARCH_DIRS = (): string[] => (getCli('pi')?.discovery.searchDirs ?? [] */ export const PI_VERSION_REGEX = /(?:^|\s)(\d+\.\d+\.\d+)/; -const PI_NOT_FOUND = 'Pi CLI not found. Install with: npm install -g --ignore-scripts @earendil-works/pi-coding-agent'; - /** * Run `pi --version` on a candidate path and return the trimmed version when it * looks like the coding agent. Returns null for anything else — a missing @@ -132,10 +126,6 @@ export function isPiAvailable(): boolean { return resolvePiDir() !== null; } -export function getPiNotFoundMessage(): string { - return formatCliNotFoundMessage(PI_NOT_FOUND, piResolver.diagnostics()); -} - /** * Version reported by the resolved `pi` binary, or null when pi is unavailable. * Surfaced through `GET /api/pi/status` so a misresolution is diagnosable from the UI. diff --git a/src/web/ports/tab-layout-port.ts b/src/web/ports/tab-layout-port.ts index ad42da88..b5e198df 100644 --- a/src/web/ports/tab-layout-port.ts +++ b/src/web/ports/tab-layout-port.ts @@ -1,8 +1,6 @@ /** @fileoverview Owner-scoped tab-layout capabilities exposed to route modules. */ import type { TabLayoutService } from '../../tab-layout-service.js'; -export type { LegacyOrderActor, LegacyOrderPutResult, SessionOrderProjectionChange } from '../../tab-layout-service.js'; - export interface TabLayoutPort { readonly tabLayouts: TabLayoutService; } diff --git a/src/web/routes/index.ts b/src/web/routes/index.ts index 31db304e..472e050c 100644 --- a/src/web/routes/index.ts +++ b/src/web/routes/index.ts @@ -45,10 +45,8 @@ export { closeAllLlamaSwapLogTails, detectCustomModelSwapDisplacements, pruneIdleLlamaSwapLogTails, - type CustomModelSessionLike, - type CustomModelSwapDisplacement, } from './custom-model-routes.js'; -export { registerCliRegistryRoutes, readCliManagementEnabled, type CliListItem } from './cli-registry-routes.js'; +export { registerCliRegistryRoutes } from './cli-registry-routes.js'; export { registerLocalModelRoutes } from './local-model-routes.js'; export { registerAgentSessionRoutes } from './agent-session-routes.js'; diff --git a/src/web/sse-stream-manager.ts b/src/web/sse-stream-manager.ts index 7a2c9c03..66f7a877 100644 --- a/src/web/sse-stream-manager.ts +++ b/src/web/sse-stream-manager.ts @@ -127,10 +127,6 @@ export class SseStreamManager { return this.remoteSseClients.size; } - get isTunnelActive(): boolean { - return this._isTunnelActive; - } - setTunnelActive(active: boolean): void { this._isTunnelActive = active; } diff --git a/test/hooks-config.test.ts b/test/hooks-config.test.ts index 9077fc5d..91291074 100644 --- a/test/hooks-config.test.ts +++ b/test/hooks-config.test.ts @@ -37,7 +37,6 @@ import { resolveStatusLineCliCommand, settingsWriteBlocker, stripCaseEnvKeys, - updateCaseEnvVars, updateCaseModel, writeHooksConfig, } from '../src/hooks-config.js'; @@ -296,7 +295,6 @@ describe('writeHooksConfig', () => { await ensureCodemanHooks(caseDir); await refreshStaleCodemanHooks(caseDir); await updateCaseModel(caseDir, 'opus'); - await updateCaseEnvVars(caseDir, { CLAUDE_CODE_NEW: 'value' }); await stripCaseEnvKeys(caseDir, ['CLAUDE_CODE_KEEP']); await applyStatusLineConfig(caseDir, true); await applyStatusLineConfig(caseDir, false); diff --git a/test/mobile/accessibility.test.ts b/test/mobile/accessibility.test.ts index b96b10a5..95f9a5f5 100644 --- a/test/mobile/accessibility.test.ts +++ b/test/mobile/accessibility.test.ts @@ -10,7 +10,7 @@ import { assertZoomNotDisabled, getCSSProperty, } from './helpers/assertions.js'; import { REPRESENTATIVE_DEVICES } from './devices.js'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; let baseUrl: string; diff --git a/test/mobile/device-matrix.test.ts b/test/mobile/device-matrix.test.ts index b0f78fa0..906ee184 100644 --- a/test/mobile/device-matrix.test.ts +++ b/test/mobile/device-matrix.test.ts @@ -1,7 +1,7 @@ // Ephemeral port - Cross-device parametric tests import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import type { BrowserContext, Page } from 'playwright'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; import { createTestServer, stopTestServer } from './helpers/server.js'; import { createDevicePage, closeAllBrowsers } from './helpers/browser.js'; import { SELECTORS, BREAKPOINTS, BODY_CLASSES } from './helpers/constants.js'; diff --git a/test/mobile/devices.ts b/test/mobile/devices.ts index 4d2864cd..386222ce 100644 --- a/test/mobile/devices.ts +++ b/test/mobile/devices.ts @@ -321,13 +321,6 @@ export const SMALL_TABLETS: DeviceEntry[] = DEVICE_REGISTRY.filter((d) => d.cate export const STANDARD_TABLETS: DeviceEntry[] = DEVICE_REGISTRY.filter((d) => d.category === 'standard-tablet'); export const LARGE_TABLETS: DeviceEntry[] = DEVICE_REGISTRY.filter((d) => d.category === 'large-tablet'); -// --------------------------------------------------------------------------- -// Platform exports -// --------------------------------------------------------------------------- - -export const IOS_DEVICES: DeviceEntry[] = DEVICE_REGISTRY.filter((d) => d.isIOS); -export const ANDROID_DEVICES: DeviceEntry[] = DEVICE_REGISTRY.filter((d) => !d.isIOS); - // --------------------------------------------------------------------------- // Representative devices — one per category for quick smoke tests // --------------------------------------------------------------------------- diff --git a/test/mobile/helpers/assertions.ts b/test/mobile/helpers/assertions.ts index 8521a34e..58314d07 100644 --- a/test/mobile/helpers/assertions.ts +++ b/test/mobile/helpers/assertions.ts @@ -156,21 +156,6 @@ export async function assertNotHasClass(page: Page, selector: string, className: expect(has).toBe(false); } -/** Assert element's computed transform includes a translateY value */ -export async function assertTranslateY( - page: Page, - selector: string, - expectedY: number, - tolerance: number = 2, -): Promise { - const transform = await getCSSProperty(page, selector, 'transform'); - // transform is a matrix(...) string; extract translateY - // matrix(a, b, c, d, tx, ty) — ty is the 6th value - const match = transform.match(/matrix\([^,]+,[^,]+,[^,]+,[^,]+,[^,]+,\s*([^)]+)\)/); - const ty = match ? parseFloat(match[1]) : 0; - expect(Math.abs(ty - expectedY)).toBeLessThanOrEqual(tolerance); -} - /** Check viewport meta tag doesn't disable zoom */ export async function assertZoomNotDisabled(page: Page): Promise { const meta = await page.evaluate(() => { diff --git a/test/mobile/helpers/browser.ts b/test/mobile/helpers/browser.ts index 41091419..0fe68e90 100644 --- a/test/mobile/helpers/browser.ts +++ b/test/mobile/helpers/browser.ts @@ -60,6 +60,3 @@ export async function closeAllBrowsers(): Promise { } } -export function isChromium(engine: 'chromium' | 'webkit'): boolean { - return engine === 'chromium'; -} diff --git a/test/mobile/helpers/cdp.ts b/test/mobile/helpers/cdp.ts index 0407fe33..d55e0462 100644 --- a/test/mobile/helpers/cdp.ts +++ b/test/mobile/helpers/cdp.ts @@ -39,38 +39,3 @@ export async function dispatchTouchEvent( }); } -/** Throttle CPU to simulate mobile hardware (rate = slowdown factor, e.g. 4 = 4x slower) */ -export async function setCPUThrottle(cdp: CDPSession, rate: number): Promise { - await cdp.send('Emulation.setCPUThrottlingRate', { rate }); -} - -/** Clear CPU throttling */ -export async function clearCPUThrottle(cdp: CDPSession): Promise { - await cdp.send('Emulation.setCPUThrottlingRate', { rate: 1 }); -} - -/** Emulate network conditions */ -export async function setNetworkThrottle( - cdp: CDPSession, - downloadKbps: number, - uploadKbps: number, - latencyMs: number, -): Promise { - await cdp.send('Network.enable'); - await cdp.send('Network.emulateNetworkConditions', { - offline: false, - downloadThroughput: (downloadKbps * 1024) / 8, - uploadThroughput: (uploadKbps * 1024) / 8, - latency: latencyMs, - }); -} - -/** Clear network throttling */ -export async function clearNetworkThrottle(cdp: CDPSession): Promise { - await cdp.send('Network.emulateNetworkConditions', { - offline: false, - downloadThroughput: -1, - uploadThroughput: -1, - latency: 0, - }); -} diff --git a/test/mobile/helpers/server.ts b/test/mobile/helpers/server.ts index 2ec0bb3b..17ae0040 100644 --- a/test/mobile/helpers/server.ts +++ b/test/mobile/helpers/server.ts @@ -1,23 +1,13 @@ import { WebServer } from '../../../src/web/server.js'; -const servers = new Set(); - /** A test server on an ephemeral port; read the port from `server.boundPort` once this resolves. */ export async function createTestServer(): Promise { const server = new WebServer(0, false, true); // testMode = true await server.start(); - servers.add(server); return server; } export async function stopTestServer(server: WebServer): Promise { await server.stop(); - servers.delete(server); } -export async function stopAllTestServers(): Promise { - for (const server of servers) { - await server.stop(); - } - servers.clear(); -} diff --git a/test/mobile/helpers/touch-sim.ts b/test/mobile/helpers/touch-sim.ts index 9b57000e..acec9289 100644 --- a/test/mobile/helpers/touch-sim.ts +++ b/test/mobile/helpers/touch-sim.ts @@ -243,19 +243,3 @@ export async function tapViaSynthetic(page: Page, selector: string): Promise { - if (options.isChromium !== false) { - try { - await tapViaCDP(page, selector); - return; - } catch { - // Fall through - } - } - await tapViaSynthetic(page, selector); -} diff --git a/test/mobile/keyboard.test.ts b/test/mobile/keyboard.test.ts index 1dfd4c97..23576bc2 100644 --- a/test/mobile/keyboard.test.ts +++ b/test/mobile/keyboard.test.ts @@ -24,7 +24,7 @@ import { getCSSProperty, } from './helpers/assertions.js'; import { REPRESENTATIVE_DEVICES } from './devices.js'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; let baseUrl: string; diff --git a/test/mobile/layout.test.ts b/test/mobile/layout.test.ts index 46680e6e..04dba598 100644 --- a/test/mobile/layout.test.ts +++ b/test/mobile/layout.test.ts @@ -1,7 +1,7 @@ // Ephemeral port - General mobile layout tests import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import type { BrowserContext, Page } from 'playwright'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; import { createTestServer, stopTestServer } from './helpers/server.js'; import { createDevicePage, closeAllBrowsers } from './helpers/browser.js'; import { SELECTORS, BREAKPOINTS, BODY_CLASSES, WAIT, MIN_TOUCH_TARGET } from './helpers/constants.js'; diff --git a/test/mobile/settings.test.ts b/test/mobile/settings.test.ts index cb6e8b17..129a1a13 100644 --- a/test/mobile/settings.test.ts +++ b/test/mobile/settings.test.ts @@ -7,7 +7,7 @@ import { createDevicePage, closeAllBrowsers } from './helpers/browser.js'; import { showKeyboard, hideKeyboard } from './helpers/keyboard-sim.js'; import { assertVisible, assertHidden, getCSSProperty, getCSSNumericValue } from './helpers/assertions.js'; import { DEVICE_REGISTRY, REPRESENTATIVE_DEVICES } from './devices.js'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; let baseUrl: string; diff --git a/test/mobile/subagent-windows.test.ts b/test/mobile/subagent-windows.test.ts index c51a8df2..3db91480 100644 --- a/test/mobile/subagent-windows.test.ts +++ b/test/mobile/subagent-windows.test.ts @@ -7,7 +7,7 @@ import { createDevicePage, closeAllBrowsers } from './helpers/browser.js'; import { showKeyboard, hideKeyboard } from './helpers/keyboard-sim.js'; import { getCSSProperty, getCSSNumericValue } from './helpers/assertions.js'; import { REPRESENTATIVE_DEVICES } from './devices.js'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; let baseUrl: string; diff --git a/test/mobile/tabs.test.ts b/test/mobile/tabs.test.ts index a751509b..4af8d66e 100644 --- a/test/mobile/tabs.test.ts +++ b/test/mobile/tabs.test.ts @@ -7,7 +7,7 @@ import { createDevicePage, closeAllBrowsers } from './helpers/browser.js'; import { swipe, swipeViaCDP, swipeViaSynthetic } from './helpers/touch-sim.js'; import { assertHidden, assertVisible, getCSSProperty, getCSSNumericValue } from './helpers/assertions.js'; import { REPRESENTATIVE_DEVICES } from './devices.js'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; let baseUrl: string; diff --git a/test/mobile/visual-regression.test.ts b/test/mobile/visual-regression.test.ts index d5d10a49..7a7e4f49 100644 --- a/test/mobile/visual-regression.test.ts +++ b/test/mobile/visual-regression.test.ts @@ -1,7 +1,7 @@ // Ephemeral port - Screenshot comparison tests import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import type { BrowserContext, Page } from 'playwright'; -import type { WebServer } from '../src/web/server.js'; +import type { WebServer } from '../../src/web/server.js'; import { createTestServer, stopTestServer } from './helpers/server.js'; import { createDevicePage, closeAllBrowsers } from './helpers/browser.js'; import { SELECTORS, VISUAL, VISUAL_BREAKPOINTS, WAIT } from './helpers/constants.js'; diff --git a/test/mocks/index.ts b/test/mocks/index.ts index 29bc0c8e..cd9f3802 100644 --- a/test/mocks/index.ts +++ b/test/mocks/index.ts @@ -2,14 +2,11 @@ * Shared test mocks — import from here instead of defining inline. * * @example - * import { MockSession, MockStateStore, terminalOutputs } from './mocks/index.js'; + * import { MockSession, createMockRouteContext } from './mocks/index.js'; */ -export { MockSession, createMockSession, terminalOutputs } from './mock-session.js'; -export { MockStateStore } from './mock-state-store.js'; +export { MockSession, createMockSession } from './mock-session.js'; export { - waitForEvent, - createDeferred, safeRmHomeTree, isUnderTestHome, isSandboxedHome, diff --git a/test/mocks/mock-session.ts b/test/mocks/mock-session.ts index 3918acfd..34e9fd6a 100644 --- a/test/mocks/mock-session.ts +++ b/test/mocks/mock-session.ts @@ -534,55 +534,6 @@ export class MockSession extends EventEmitter { getMessages = vi.fn(() => []); } -/** - * Generate realistic terminal output for testing. - * Must match the patterns used in MockSession's simulate* methods. - */ -export const terminalOutputs = { - /** Standard completion message */ - completion(duration: string = '2m 46s'): string { - return `\n\u273b Worked for ${duration}\n 123.4k tokens used\n`; - }, - - /** Working spinner output */ - working(activity: string = 'Thinking'): string { - return `${activity}... \u280b`; - }, - - /** Plan mode prompt */ - planMode(question: string = 'Would you like to proceed?'): string { - return [`\n${question}\n`, '\u276f 1. Yes\n', ' 2. No\n', ' 3. Type your own\n'].join(''); - }, - - /** Prompt character */ - prompt(): string { - return '\n\u276f '; - }, - - /** Token count display */ - tokens(count: number): string { - const formatted = - count >= 1000000 - ? `${(count / 1000000).toFixed(1)}M` - : count >= 1000 - ? `${(count / 1000).toFixed(1)}k` - : String(count); - return ` ${formatted} tokens\n`; - }, - - /** Large output for buffer testing */ - largeOutput(sizeKb: number = 100): string { - const baseText = 'Lorem ipsum dolor sit amet. '.repeat(100); - const repetitions = Math.ceil((sizeKb * 1024) / baseText.length); - return baseText.repeat(repetitions).slice(0, sizeKb * 1024); - }, - - /** ANSI colored output */ - ansiColored(text: string): string { - return `\x1b[32m${text}\x1b[0m`; - }, -}; - /** * Convenience factory. */ diff --git a/test/mocks/mock-state-store.ts b/test/mocks/mock-state-store.ts deleted file mode 100644 index e9d09bff..00000000 --- a/test/mocks/mock-state-store.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Shared MockStateStore for tests. - * - * Includes methods for both session management and Ralph loop testing. - * All methods are vi.fn() spies — tests can override return values as needed. - * - * NOTE: This is for direct instantiation in new tests. Existing tests that - * use vi.mock('../src/state-store.js') keep their inline definitions. - */ -import { vi } from 'vitest'; - -export class MockStateStore { - state: Record = { - sessions: {} as Record, - config: { maxConcurrentSessions: 5 }, - ralphLoop: { status: 'stopped' }, - tasks: {} as Record, - }; - - // Session methods - getConfig = vi.fn(() => this.state.config); - getSessions = vi.fn(() => this.state.sessions as Record); - getSession = vi.fn((id: string) => (this.state.sessions as Record)[id]); - setSession = vi.fn((id: string, state: unknown) => { - (this.state.sessions as Record)[id] = state; - }); - removeSession = vi.fn((id: string) => { - delete (this.state.sessions as Record)[id]; - }); - - // Ralph state methods - getRalphLoopState = vi.fn(() => this.state.ralphLoop); - setRalphLoopState = vi.fn((update: Record) => { - this.state.ralphLoop = { ...(this.state.ralphLoop as Record), ...update }; - }); - - // Task methods - getTasks = vi.fn(() => this.state.tasks); - setTask = vi.fn(); - removeTask = vi.fn(); - - // Settings methods - getSettings = vi.fn(() => ({})); - setSettings = vi.fn(); - - // Generic persistence - save = vi.fn(); - load = vi.fn(); - - /** Reset all state and mocks for clean test isolation */ - reset(): void { - this.state = { - sessions: {}, - config: { maxConcurrentSessions: 5 }, - ralphLoop: { status: 'stopped' }, - tasks: {}, - }; - vi.clearAllMocks(); - } -} diff --git a/test/mocks/test-helpers.ts b/test/mocks/test-helpers.ts index 12c0a280..924ebdfe 100644 --- a/test/mocks/test-helpers.ts +++ b/test/mocks/test-helpers.ts @@ -1,43 +1,10 @@ /** - * Reusable async test helpers. + * Guards for tests that delete under the throwaway HOME. */ import { rmSync } from 'node:fs'; import { basename, resolve } from 'node:path'; -/** Wait for an EventEmitter to emit a specific event, with timeout */ -export function waitForEvent( - emitter: { once: (event: string, listener: (...args: unknown[]) => void) => void }, - event: string, - timeoutMs = 5000 -): Promise { - return new Promise((resolve, reject) => { - const timer = setTimeout( - () => reject(new Error(`Timed out waiting for event "${event}" after ${timeoutMs}ms`)), - timeoutMs - ); - emitter.once(event, (...args: unknown[]) => { - clearTimeout(timer); - resolve(args.length === 1 ? args[0] : args); - }); - }); -} - -/** Create a deferred promise with external resolve/reject */ -export function createDeferred(): { - promise: Promise; - resolve: (value: T) => void; - reject: (reason?: unknown) => void; -} { - let resolve!: (value: T) => void; - let reject!: (reason?: unknown) => void; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - return { promise, resolve, reject }; -} - /** * Prefix of the throwaway HOME `test/setup.ts` creates (`mkdtempSync(join(tmpdir(), 'codeman-vitest-'))`). * Pinned by test/test-env-isolation.test.ts, so renaming one without the other fails there.