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 get<Cli>NotFoundMessage() (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 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-11-13 13:58:22 +01:00
1 parent 60f1900081
commit f604deba3e
57 files changed
+36 -963

No files matched your search

-5
View File
@@ -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.
*/
-4
View File
@@ -165,10 +165,6 @@ export class AiIdleChecker extends AiCheckerBase<AiCheckVerdict, AiIdleCheckConf
return 'WORKING';
}
protected getErrorVerdict(): AiCheckVerdict {
return 'ERROR';
}
protected createErrorResult(reasoning: string, durationMs: number): AiCheckResult {
return { verdict: 'ERROR', reasoning, durationMs };
}
-4
View File
@@ -139,10 +139,6 @@ export class AiPlanChecker extends AiCheckerBase<
return 'NOT_PLAN_MODE';
}
protected getErrorVerdict(): AiPlanCheckVerdict {
return 'ERROR';
}
protected createErrorResult(reasoning: string, durationMs: number): AiPlanCheckResult {
return { verdict: 'ERROR', reasoning, durationMs };
}
+1 -11
View File
@@ -26,7 +26,7 @@
* @module config/cli-registry/argv
*/
import type { ArgSpec, CliEntry, CliLaunch, Cond, EngineValue, ParamSpec, QuoteStyle } from './types.js';
import type { ArgSpec, CliLaunch, Cond, EngineValue, ParamSpec, QuoteStyle } from './types.js';
import { matchesPattern } from './patterns.js';
import { SAFE_BARE_TOKEN } from './patterns.js';
@@ -178,13 +178,3 @@ export function renderLaunch(
return rendered.join(' || ');
}
/** Convenience: render an entry's launch command straight from a `CliEntry`. */
export function renderCliCommand(
entry: CliEntry,
params: ParamValues,
engineValues: EngineValues,
gatesPassed?: ReadonlySet<string>
): string {
return renderLaunch(entry.launch, params, engineValues, gatesPassed);
}
+1 -56
View File
@@ -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';
-10
View File
@@ -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);
+1 -13
View File
@@ -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);
-2
View File
@@ -817,5 +817,3 @@ export const CliEntrySchema = z
});
}
});
export type ValidatedCliEntry = z.infer<typeof CliEntrySchema>;
-2
View File
@@ -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;
-32
View File
@@ -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<string, string>): Promise<void> {
await withSafeSettingsWrite(casePath, 'env vars', async (claudeDir, settingsPath) => {
if (!(await pathExistsForWrite(claudeDir))) {
await mkdir(claudeDir, { recursive: true });
}
let existing: Record<string, unknown> = {};
try {
existing = JSON.parse(await readFile(settingsPath, 'utf-8'));
} catch {
existing = {};
}
const currentEnv = (existing.env as Record<string, string>) || {};
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.
+1 -1
View File
@@ -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';
-7
View File
@@ -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.
*/
+2 -33
View File
@@ -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) ==========
/**
-101
View File
@@ -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<number, string>;
private readonly maxAgeMs: number;
constructor(maxSize: number = DEFAULT_MAX_SIZE, maxAgeMs: number = DEFAULT_MAX_AGE_MS) {
this.cache = new LRUMap<number, string>({ 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();
}
}
+3 -127
View File
@@ -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<string>();
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();
}
}
-23
View File
@@ -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<AppState>;
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).
*/
+1 -12
View File
@@ -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<TeamMember & { tmuxPaneId: string }> {
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);
-76
View File
@@ -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<boolean> {
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';
}
-14
View File
@@ -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;
-3
View File
@@ -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).
-4
View File
@@ -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) */
+1 -12
View File
@@ -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());
}
+1 -6
View File
@@ -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
-20
View File
@@ -82,26 +82,6 @@ export async function resolveCliLaunchError(mode: string, rawConfig?: Record<str
return resolveCliBinDir(mode) === null ? missingCliMessage(mode) : null;
}
/**
* Is this CLI actually usable? For an ordinary CLI that is exactly "its binary resolved".
* For a launcher it is that AND whatever its profile demands.
*
* ⚠️ A named-but-unimplemented profile fails CLOSED. In practice `schema.ts` rejects such an
* entry at load time, so this is the second line of defence rather than the first — but the
* direction matters: offering a Run that always fails is worse than reporting unavailable.
*/
export function isCliRunnable(id: string): boolean {
const entry = getCli(id);
if (!entry) return false;
// No binary to find (`shell`): tmux-manager resolves the login shell in code.
const resolved = entry.discovery.binaries.length === 0 ? true : resolveCliBinDir(id) !== null;
const profileName = entry.discovery.launcherProfile;
if (profileName === undefined) return resolved;
const profile = LAUNCHER_PROFILES[profileName];
if (!profile) return false;
return resolved && profile.isRunnable();
}
/**
* The launcher's default target, for the `launcherDefaultTarget` engine value. Null for
* every non-launcher CLI, which is what makes the corresponding launch arg drop out.
-14
View File
@@ -172,20 +172,6 @@ function createRegistryResolver(
};
}
/**
* Build an isolated resolver for `entry` around an injected probe, host and clock — the
* test seam. Omitting `probe` keeps the ambient, VITEST-gated one, which is exactly what
* the hermeticity tests exercise.
*/
export function createCliResolverForTest(
entry: CliEntry,
probe?: CliCandidateProbe,
host?: CliResolverHost,
now?: () => 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
+1 -6
View File
@@ -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<unknown>;
const APP_SERVER_TIMEOUT_MS = 10_000;
+1 -11
View File
@@ -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
+1 -6
View File
@@ -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.
*
+1 -6
View File
@@ -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());
}
+1 -11
View File
@@ -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
+3 -49
View File
@@ -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';
+1 -11
View File
@@ -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
+1 -6
View File
@@ -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());
}
+1 -11
View File
@@ -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.
-2
View File
@@ -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;
}
+1 -3
View File
@@ -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';
-4
View File
@@ -127,10 +127,6 @@ export class SseStreamManager {
return this.remoteSseClients.size;
}
get isTunnelActive(): boolean {
return this._isTunnelActive;
}
setTunnelActive(active: boolean): void {
this._isTunnelActive = active;
}