mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-21 15:55:50 +00:00
style: 完成所有文件的lint
This commit is contained in:
@@ -1,140 +1,104 @@
|
||||
import figures from 'figures'
|
||||
import { join } from 'path'
|
||||
import React, {
|
||||
Suspense,
|
||||
use,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { KeybindingWarnings } from 'src/components/KeybindingWarnings.js'
|
||||
import { McpParsingWarnings } from 'src/components/mcp/McpParsingWarnings.js'
|
||||
import { getModelMaxOutputTokens } from 'src/utils/context.js'
|
||||
import { getClaudeConfigHomeDir } from 'src/utils/envUtils.js'
|
||||
import type { SettingSource } from 'src/utils/settings/constants.js'
|
||||
import { getOriginalCwd } from '../bootstrap/state.js'
|
||||
import type { CommandResultDisplay } from '../commands.js'
|
||||
import { Pane } from '@anthropic/ink'
|
||||
import { PressEnterToContinue } from '../components/PressEnterToContinue.js'
|
||||
import { SandboxDoctorSection } from '../components/sandbox/SandboxDoctorSection.js'
|
||||
import { ValidationErrorsList } from '../components/ValidationErrorsList.js'
|
||||
import { useSettingsErrors } from '../hooks/notifs/useSettingsErrors.js'
|
||||
import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js'
|
||||
import { Box, Text } from '@anthropic/ink'
|
||||
import { useKeybindings } from '../keybindings/useKeybinding.js'
|
||||
import { useAppState } from '../state/AppState.js'
|
||||
import { getPluginErrorMessage } from '../types/plugin.js'
|
||||
import {
|
||||
getGcsDistTags,
|
||||
getNpmDistTags,
|
||||
type NpmDistTags,
|
||||
} from '../utils/autoUpdater.js'
|
||||
import {
|
||||
type ContextWarnings,
|
||||
checkContextWarnings,
|
||||
} from '../utils/doctorContextWarnings.js'
|
||||
import {
|
||||
type DiagnosticInfo,
|
||||
getDoctorDiagnostic,
|
||||
} from '../utils/doctorDiagnostic.js'
|
||||
import { validateBoundedIntEnvVar } from '../utils/envValidation.js'
|
||||
import { pathExists } from '../utils/file.js'
|
||||
import figures from 'figures';
|
||||
import { join } from 'path';
|
||||
import React, { Suspense, use, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { KeybindingWarnings } from 'src/components/KeybindingWarnings.js';
|
||||
import { McpParsingWarnings } from 'src/components/mcp/McpParsingWarnings.js';
|
||||
import { getModelMaxOutputTokens } from 'src/utils/context.js';
|
||||
import { getClaudeConfigHomeDir } from 'src/utils/envUtils.js';
|
||||
import type { SettingSource } from 'src/utils/settings/constants.js';
|
||||
import { getOriginalCwd } from '../bootstrap/state.js';
|
||||
import type { CommandResultDisplay } from '../commands.js';
|
||||
import { Pane } from '@anthropic/ink';
|
||||
import { PressEnterToContinue } from '../components/PressEnterToContinue.js';
|
||||
import { SandboxDoctorSection } from '../components/sandbox/SandboxDoctorSection.js';
|
||||
import { ValidationErrorsList } from '../components/ValidationErrorsList.js';
|
||||
import { useSettingsErrors } from '../hooks/notifs/useSettingsErrors.js';
|
||||
import { useExitOnCtrlCDWithKeybindings } from '../hooks/useExitOnCtrlCDWithKeybindings.js';
|
||||
import { Box, Text } from '@anthropic/ink';
|
||||
import { useKeybindings } from '../keybindings/useKeybinding.js';
|
||||
import { useAppState } from '../state/AppState.js';
|
||||
import { getPluginErrorMessage } from '../types/plugin.js';
|
||||
import { getGcsDistTags, getNpmDistTags, type NpmDistTags } from '../utils/autoUpdater.js';
|
||||
import { type ContextWarnings, checkContextWarnings } from '../utils/doctorContextWarnings.js';
|
||||
import { type DiagnosticInfo, getDoctorDiagnostic } from '../utils/doctorDiagnostic.js';
|
||||
import { validateBoundedIntEnvVar } from '../utils/envValidation.js';
|
||||
import { pathExists } from '../utils/file.js';
|
||||
import {
|
||||
cleanupStaleLocks,
|
||||
getAllLockInfo,
|
||||
isPidBasedLockingEnabled,
|
||||
type LockInfo,
|
||||
} from '../utils/nativeInstaller/pidLock.js'
|
||||
import { getInitialSettings } from '../utils/settings/settings.js'
|
||||
import {
|
||||
BASH_MAX_OUTPUT_DEFAULT,
|
||||
BASH_MAX_OUTPUT_UPPER_LIMIT,
|
||||
} from '../utils/shell/outputLimits.js'
|
||||
import {
|
||||
TASK_MAX_OUTPUT_DEFAULT,
|
||||
TASK_MAX_OUTPUT_UPPER_LIMIT,
|
||||
} from '../utils/task/outputFormatting.js'
|
||||
import { getXDGStateHome } from '../utils/xdg.js'
|
||||
} from '../utils/nativeInstaller/pidLock.js';
|
||||
import { getInitialSettings } from '../utils/settings/settings.js';
|
||||
import { BASH_MAX_OUTPUT_DEFAULT, BASH_MAX_OUTPUT_UPPER_LIMIT } from '../utils/shell/outputLimits.js';
|
||||
import { TASK_MAX_OUTPUT_DEFAULT, TASK_MAX_OUTPUT_UPPER_LIMIT } from '../utils/task/outputFormatting.js';
|
||||
import { getXDGStateHome } from '../utils/xdg.js';
|
||||
|
||||
type Props = {
|
||||
onDone: (
|
||||
result?: string,
|
||||
options?: { display?: CommandResultDisplay },
|
||||
) => void
|
||||
}
|
||||
onDone: (result?: string, options?: { display?: CommandResultDisplay }) => void;
|
||||
};
|
||||
|
||||
type AgentInfo = {
|
||||
activeAgents: Array<{
|
||||
agentType: string
|
||||
source: SettingSource | 'built-in' | 'plugin'
|
||||
}>
|
||||
userAgentsDir: string
|
||||
projectAgentsDir: string
|
||||
userDirExists: boolean
|
||||
projectDirExists: boolean
|
||||
failedFiles?: Array<{ path: string; error: string }>
|
||||
}
|
||||
agentType: string;
|
||||
source: SettingSource | 'built-in' | 'plugin';
|
||||
}>;
|
||||
userAgentsDir: string;
|
||||
projectAgentsDir: string;
|
||||
userDirExists: boolean;
|
||||
projectDirExists: boolean;
|
||||
failedFiles?: Array<{ path: string; error: string }>;
|
||||
};
|
||||
|
||||
type VersionLockInfo = {
|
||||
enabled: boolean
|
||||
locks: LockInfo[]
|
||||
locksDir: string
|
||||
staleLocksCleaned: number
|
||||
}
|
||||
enabled: boolean;
|
||||
locks: LockInfo[];
|
||||
locksDir: string;
|
||||
staleLocksCleaned: number;
|
||||
};
|
||||
|
||||
function DistTagsDisplay({
|
||||
promise,
|
||||
}: {
|
||||
promise: Promise<NpmDistTags>
|
||||
}): React.ReactNode {
|
||||
const distTags = use(promise)
|
||||
function DistTagsDisplay({ promise }: { promise: Promise<NpmDistTags> }): React.ReactNode {
|
||||
const distTags = use(promise);
|
||||
if (!distTags.latest) {
|
||||
return <Text dimColor>└ Failed to fetch versions</Text>
|
||||
return <Text dimColor>└ Failed to fetch versions</Text>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{distTags.stable && <Text>└ Stable version: {distTags.stable}</Text>}
|
||||
<Text>└ Latest version: {distTags.latest}</Text>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
const agentDefinitions = useAppState(s => s.agentDefinitions)
|
||||
const mcpTools = useAppState(s => s.mcp.tools)
|
||||
const toolPermissionContext = useAppState(s => s.toolPermissionContext)
|
||||
const pluginsErrors = useAppState(s => s.plugins.errors)
|
||||
useExitOnCtrlCDWithKeybindings()
|
||||
const agentDefinitions = useAppState(s => s.agentDefinitions);
|
||||
const mcpTools = useAppState(s => s.mcp.tools);
|
||||
const toolPermissionContext = useAppState(s => s.toolPermissionContext);
|
||||
const pluginsErrors = useAppState(s => s.plugins.errors);
|
||||
useExitOnCtrlCDWithKeybindings();
|
||||
|
||||
const tools = useMemo(() => {
|
||||
return mcpTools || []
|
||||
}, [mcpTools])
|
||||
return mcpTools || [];
|
||||
}, [mcpTools]);
|
||||
|
||||
const [diagnostic, setDiagnostic] = useState<DiagnosticInfo | null>(null)
|
||||
const [agentInfo, setAgentInfo] = useState<AgentInfo | null>(null)
|
||||
const [contextWarnings, setContextWarnings] =
|
||||
useState<ContextWarnings | null>(null)
|
||||
const [versionLockInfo, setVersionLockInfo] =
|
||||
useState<VersionLockInfo | null>(null)
|
||||
const validationErrors = useSettingsErrors()
|
||||
const [diagnostic, setDiagnostic] = useState<DiagnosticInfo | null>(null);
|
||||
const [agentInfo, setAgentInfo] = useState<AgentInfo | null>(null);
|
||||
const [contextWarnings, setContextWarnings] = useState<ContextWarnings | null>(null);
|
||||
const [versionLockInfo, setVersionLockInfo] = useState<VersionLockInfo | null>(null);
|
||||
const validationErrors = useSettingsErrors();
|
||||
|
||||
// Create promise once for dist-tags fetch (depends on diagnostic)
|
||||
const distTagsPromise = useMemo(
|
||||
() =>
|
||||
getDoctorDiagnostic().then(diag => {
|
||||
const fetchDistTags =
|
||||
diag.installationType === 'native' ? getGcsDistTags : getNpmDistTags
|
||||
return fetchDistTags().catch(() => ({ latest: null, stable: null }))
|
||||
const fetchDistTags = diag.installationType === 'native' ? getGcsDistTags : getNpmDistTags;
|
||||
return fetchDistTags().catch(() => ({ latest: null, stable: null }));
|
||||
}),
|
||||
[],
|
||||
)
|
||||
const autoUpdatesChannel =
|
||||
getInitialSettings()?.autoUpdatesChannel ?? 'latest'
|
||||
);
|
||||
const autoUpdatesChannel = getInitialSettings()?.autoUpdatesChannel ?? 'latest';
|
||||
|
||||
const errorsExcludingMcp = validationErrors.filter(
|
||||
error => error.mcpErrorMetadata === undefined,
|
||||
)
|
||||
const errorsExcludingMcp = validationErrors.filter(error => error.mcpErrorMetadata === undefined);
|
||||
|
||||
const envValidationErrors = useMemo(() => {
|
||||
const envVars = [
|
||||
@@ -153,34 +117,29 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
// Check for values against the latest supported model
|
||||
...getModelMaxOutputTokens('claude-opus-4-7'),
|
||||
},
|
||||
]
|
||||
];
|
||||
return envVars
|
||||
.map(v => {
|
||||
const value = process.env[v.name]
|
||||
const result = validateBoundedIntEnvVar(
|
||||
v.name,
|
||||
value,
|
||||
v.default,
|
||||
v.upperLimit,
|
||||
)
|
||||
return { name: v.name, ...result }
|
||||
const value = process.env[v.name];
|
||||
const result = validateBoundedIntEnvVar(v.name, value, v.default, v.upperLimit);
|
||||
return { name: v.name, ...result };
|
||||
})
|
||||
.filter(v => v.status !== 'valid')
|
||||
}, [])
|
||||
.filter(v => v.status !== 'valid');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void getDoctorDiagnostic().then(setDiagnostic)
|
||||
void getDoctorDiagnostic().then(setDiagnostic);
|
||||
|
||||
void (async () => {
|
||||
const userAgentsDir = join(getClaudeConfigHomeDir(), 'agents')
|
||||
const projectAgentsDir = join(getOriginalCwd(), '.claude', 'agents')
|
||||
const userAgentsDir = join(getClaudeConfigHomeDir(), 'agents');
|
||||
const projectAgentsDir = join(getOriginalCwd(), '.claude', 'agents');
|
||||
|
||||
const { activeAgents, allAgents, failedFiles } = agentDefinitions
|
||||
const { activeAgents, allAgents, failedFiles } = agentDefinitions;
|
||||
|
||||
const [userDirExists, projectDirExists] = await Promise.all([
|
||||
pathExists(userAgentsDir),
|
||||
pathExists(projectAgentsDir),
|
||||
])
|
||||
]);
|
||||
|
||||
const agentInfoData = {
|
||||
activeAgents: activeAgents.map(a => ({
|
||||
@@ -192,8 +151,8 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
userDirExists,
|
||||
projectDirExists,
|
||||
failedFiles,
|
||||
}
|
||||
setAgentInfo(agentInfoData)
|
||||
};
|
||||
setAgentInfo(agentInfoData);
|
||||
|
||||
const warnings = await checkContextWarnings(
|
||||
tools,
|
||||
@@ -203,34 +162,34 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
failedFiles,
|
||||
},
|
||||
async () => toolPermissionContext,
|
||||
)
|
||||
setContextWarnings(warnings)
|
||||
);
|
||||
setContextWarnings(warnings);
|
||||
|
||||
// Fetch version lock info if PID-based locking is enabled
|
||||
if (isPidBasedLockingEnabled()) {
|
||||
const locksDir = join(getXDGStateHome(), 'claude', 'locks')
|
||||
const staleLocksCleaned = cleanupStaleLocks(locksDir)
|
||||
const locks = getAllLockInfo(locksDir)
|
||||
const locksDir = join(getXDGStateHome(), 'claude', 'locks');
|
||||
const staleLocksCleaned = cleanupStaleLocks(locksDir);
|
||||
const locks = getAllLockInfo(locksDir);
|
||||
setVersionLockInfo({
|
||||
enabled: true,
|
||||
locks,
|
||||
locksDir,
|
||||
staleLocksCleaned,
|
||||
})
|
||||
});
|
||||
} else {
|
||||
setVersionLockInfo({
|
||||
enabled: false,
|
||||
locks: [],
|
||||
locksDir: '',
|
||||
staleLocksCleaned: 0,
|
||||
})
|
||||
});
|
||||
}
|
||||
})()
|
||||
}, [toolPermissionContext, tools, agentDefinitions])
|
||||
})();
|
||||
}, [toolPermissionContext, tools, agentDefinitions]);
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
onDone('Claude Code diagnostics dismissed', { display: 'system' })
|
||||
}, [onDone])
|
||||
onDone('Claude Code diagnostics dismissed', { display: 'system' });
|
||||
}, [onDone]);
|
||||
|
||||
// Handle dismiss via keybindings (Enter, Escape, or Ctrl+C)
|
||||
useKeybindings(
|
||||
@@ -239,7 +198,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
'confirm:no': handleDismiss,
|
||||
},
|
||||
{ context: 'Confirmation' },
|
||||
)
|
||||
);
|
||||
|
||||
// Loading state
|
||||
if (!diagnostic) {
|
||||
@@ -247,7 +206,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
<Pane>
|
||||
<Text dimColor>Checking installation status…</Text>
|
||||
</Pane>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Format the diagnostic output according to spec
|
||||
@@ -256,12 +215,9 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Diagnostics</Text>
|
||||
<Text>
|
||||
└ Currently running: {diagnostic.installationType} (
|
||||
{diagnostic.version})
|
||||
└ Currently running: {diagnostic.installationType} ({diagnostic.version})
|
||||
</Text>
|
||||
{diagnostic.packageManager && (
|
||||
<Text>└ Package manager: {diagnostic.packageManager}</Text>
|
||||
)}
|
||||
{diagnostic.packageManager && <Text>└ Package manager: {diagnostic.packageManager}</Text>}
|
||||
<Text>└ Path: {diagnostic.installationPath}</Text>
|
||||
<Text>└ Invoked: {diagnostic.invokedBinary}</Text>
|
||||
<Text>└ Config install method: {diagnostic.configInstallMethod}</Text>
|
||||
@@ -279,9 +235,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
{diagnostic.recommendation && (
|
||||
<>
|
||||
<Text></Text>
|
||||
<Text color="warning">
|
||||
Recommendation: {diagnostic.recommendation.split('\n')[0]}
|
||||
</Text>
|
||||
<Text color="warning">Recommendation: {diagnostic.recommendation.split('\n')[0]}</Text>
|
||||
<Text dimColor>{diagnostic.recommendation.split('\n')[1]}</Text>
|
||||
</>
|
||||
)}
|
||||
@@ -324,17 +278,9 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
{/* Updates section */}
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Updates</Text>
|
||||
<Text>
|
||||
└ Auto-updates:{' '}
|
||||
{diagnostic.packageManager
|
||||
? 'Managed by package manager'
|
||||
: diagnostic.autoUpdates}
|
||||
</Text>
|
||||
<Text>└ Auto-updates: {diagnostic.packageManager ? 'Managed by package manager' : diagnostic.autoUpdates}</Text>
|
||||
{diagnostic.hasUpdatePermissions !== null && (
|
||||
<Text>
|
||||
└ Update permissions:{' '}
|
||||
{diagnostic.hasUpdatePermissions ? 'Yes' : 'No (requires sudo)'}
|
||||
</Text>
|
||||
<Text>└ Update permissions: {diagnostic.hasUpdatePermissions ? 'Yes' : 'No (requires sudo)'}</Text>
|
||||
)}
|
||||
<Text>└ Auto-update channel: {autoUpdatesChannel}</Text>
|
||||
<Suspense fallback={null}>
|
||||
@@ -355,11 +301,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
{envValidationErrors.map((validation, i) => (
|
||||
<Text key={i}>
|
||||
└ {validation.name}:{' '}
|
||||
<Text
|
||||
color={validation.status === 'capped' ? 'warning' : 'error'}
|
||||
>
|
||||
{validation.message}
|
||||
</Text>
|
||||
<Text color={validation.status === 'capped' ? 'warning' : 'error'}>{validation.message}</Text>
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
@@ -370,9 +312,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Version Locks</Text>
|
||||
{versionLockInfo.staleLocksCleaned > 0 && (
|
||||
<Text dimColor>
|
||||
└ Cleaned {versionLockInfo.staleLocksCleaned} stale lock(s)
|
||||
</Text>
|
||||
<Text dimColor>└ Cleaned {versionLockInfo.staleLocksCleaned} stale lock(s)</Text>
|
||||
)}
|
||||
{versionLockInfo.locks.length === 0 ? (
|
||||
<Text dimColor>└ No active version locks</Text>
|
||||
@@ -380,11 +320,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
versionLockInfo.locks.map((lock, i) => (
|
||||
<Text key={i}>
|
||||
└ {lock.version}: PID {lock.pid}{' '}
|
||||
{lock.isProcessRunning ? (
|
||||
<Text>(running)</Text>
|
||||
) : (
|
||||
<Text color="warning">(stale)</Text>
|
||||
)}
|
||||
{lock.isProcessRunning ? <Text>(running)</Text> : <Text color="warning">(stale)</Text>}
|
||||
</Text>
|
||||
))
|
||||
)}
|
||||
@@ -396,9 +332,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
<Text bold color="error">
|
||||
Agent Parse Errors
|
||||
</Text>
|
||||
<Text color="error">
|
||||
└ Failed to parse {agentInfo.failedFiles.length} agent file(s):
|
||||
</Text>
|
||||
<Text color="error">└ Failed to parse {agentInfo.failedFiles.length} agent file(s):</Text>
|
||||
{agentInfo.failedFiles.map((file, i) => (
|
||||
<Text key={i} dimColor>
|
||||
{' '}└ {file.path}: {file.error}
|
||||
@@ -413,14 +347,11 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
<Text bold color="error">
|
||||
Plugin Errors
|
||||
</Text>
|
||||
<Text color="error">
|
||||
└ {pluginsErrors.length} plugin error(s) detected:
|
||||
</Text>
|
||||
<Text color="error">└ {pluginsErrors.length} plugin error(s) detected:</Text>
|
||||
{pluginsErrors.map((error, i) => (
|
||||
<Text key={i} dimColor>
|
||||
{' '}└ {error.source || 'unknown'}
|
||||
{'plugin' in error && error.plugin ? ` [${error.plugin}]` : ''}:{' '}
|
||||
{getPluginErrorMessage(error)}
|
||||
{'plugin' in error && error.plugin ? ` [${error.plugin}]` : ''}: {getPluginErrorMessage(error)}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
@@ -435,8 +366,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
<Text>
|
||||
└{' '}
|
||||
<Text color="warning">
|
||||
{figures.warning}{' '}
|
||||
{contextWarnings.unreachableRulesWarning.message}
|
||||
{figures.warning} {contextWarnings.unreachableRulesWarning.message}
|
||||
</Text>
|
||||
</Text>
|
||||
{contextWarnings.unreachableRulesWarning.details.map((detail, i) => (
|
||||
@@ -449,9 +379,7 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
|
||||
{/* Context Usage Warnings */}
|
||||
{contextWarnings &&
|
||||
(contextWarnings.claudeMdWarning ||
|
||||
contextWarnings.agentWarning ||
|
||||
contextWarnings.mcpWarning) && (
|
||||
(contextWarnings.claudeMdWarning || contextWarnings.agentWarning || contextWarnings.mcpWarning) && (
|
||||
<Box flexDirection="column">
|
||||
<Text bold>Context Usage Warnings</Text>
|
||||
|
||||
@@ -512,5 +440,5 @@ export function Doctor({ onDone }: Props): React.ReactNode {
|
||||
<PressEnterToContinue />
|
||||
</Box>
|
||||
</Pane>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,40 @@
|
||||
import { feature } from 'bun:bundle'
|
||||
import { dirname } from 'path'
|
||||
import React from 'react'
|
||||
import { useTerminalSize } from 'src/hooks/useTerminalSize.js'
|
||||
import { getOriginalCwd, switchSession } from '../bootstrap/state.js'
|
||||
import type { Command } from '../commands.js'
|
||||
import { LogSelector } from '../components/LogSelector.js'
|
||||
import { Spinner } from '../components/Spinner.js'
|
||||
import { restoreCostStateForSession } from '../cost-tracker.js'
|
||||
import { setClipboard } from '@anthropic/ink'
|
||||
import { Box, Text } from '@anthropic/ink'
|
||||
import { useKeybinding } from '../keybindings/useKeybinding.js'
|
||||
import { feature } from 'bun:bundle';
|
||||
import { dirname } from 'path';
|
||||
import React from 'react';
|
||||
import { useTerminalSize } from 'src/hooks/useTerminalSize.js';
|
||||
import { getOriginalCwd, switchSession } from '../bootstrap/state.js';
|
||||
import type { Command } from '../commands.js';
|
||||
import { LogSelector } from '../components/LogSelector.js';
|
||||
import { Spinner } from '../components/Spinner.js';
|
||||
import { restoreCostStateForSession } from '../cost-tracker.js';
|
||||
import { setClipboard } from '@anthropic/ink';
|
||||
import { Box, Text } from '@anthropic/ink';
|
||||
import { useKeybinding } from '../keybindings/useKeybinding.js';
|
||||
import {
|
||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
logEvent,
|
||||
} from '../services/analytics/index.js'
|
||||
import type {
|
||||
MCPServerConnection,
|
||||
ScopedMcpServerConfig,
|
||||
} from '../services/mcp/types.js'
|
||||
import { useAppState, useSetAppState } from '../state/AppState.js'
|
||||
import type { Tool } from '../Tool.js'
|
||||
import type { AgentColorName } from '@claude-code-best/builtin-tools/tools/AgentTool/agentColorManager.js'
|
||||
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
|
||||
import { asSessionId } from '../types/ids.js'
|
||||
import type { LogOption } from '../types/logs.js'
|
||||
import type { Message } from '../types/message.js'
|
||||
import { agenticSessionSearch } from '../utils/agenticSessionSearch.js'
|
||||
import { renameRecordingForSession } from '../utils/asciicast.js'
|
||||
import { updateSessionName } from '../utils/concurrentSessions.js'
|
||||
import { loadConversationForResume } from '../utils/conversationRecovery.js'
|
||||
import { checkCrossProjectResume } from '../utils/crossProjectResume.js'
|
||||
import type { FileHistorySnapshot } from '../utils/fileHistory.js'
|
||||
import { logError } from '../utils/log.js'
|
||||
import { createSystemMessage } from '../utils/messages.js'
|
||||
} from '../services/analytics/index.js';
|
||||
import type { MCPServerConnection, ScopedMcpServerConfig } from '../services/mcp/types.js';
|
||||
import { useAppState, useSetAppState } from '../state/AppState.js';
|
||||
import type { Tool } from '../Tool.js';
|
||||
import type { AgentColorName } from '@claude-code-best/builtin-tools/tools/AgentTool/agentColorManager.js';
|
||||
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js';
|
||||
import { asSessionId } from '../types/ids.js';
|
||||
import type { LogOption } from '../types/logs.js';
|
||||
import type { Message } from '../types/message.js';
|
||||
import { agenticSessionSearch } from '../utils/agenticSessionSearch.js';
|
||||
import { renameRecordingForSession } from '../utils/asciicast.js';
|
||||
import { updateSessionName } from '../utils/concurrentSessions.js';
|
||||
import { loadConversationForResume } from '../utils/conversationRecovery.js';
|
||||
import { checkCrossProjectResume } from '../utils/crossProjectResume.js';
|
||||
import type { FileHistorySnapshot } from '../utils/fileHistory.js';
|
||||
import { logError } from '../utils/log.js';
|
||||
import { createSystemMessage } from '../utils/messages.js';
|
||||
import {
|
||||
computeStandaloneAgentContext,
|
||||
restoreAgentFromSession,
|
||||
restoreWorktreeForResume,
|
||||
} from '../utils/sessionRestore.js'
|
||||
} from '../utils/sessionRestore.js';
|
||||
import {
|
||||
adoptResumedSessionFile,
|
||||
enrichLogs,
|
||||
@@ -48,43 +45,43 @@ import {
|
||||
resetSessionFilePointer,
|
||||
restoreSessionMetadata,
|
||||
type SessionLogResult,
|
||||
} from '../utils/sessionStorage.js'
|
||||
import type { ThinkingConfig } from '../utils/thinking.js'
|
||||
import type { ContentReplacementRecord } from '../utils/toolResultStorage.js'
|
||||
import { REPL } from './REPL.js'
|
||||
} from '../utils/sessionStorage.js';
|
||||
import type { ThinkingConfig } from '../utils/thinking.js';
|
||||
import type { ContentReplacementRecord } from '../utils/toolResultStorage.js';
|
||||
import { REPL } from './REPL.js';
|
||||
|
||||
function parsePrIdentifier(value: string): number | null {
|
||||
const directNumber = parseInt(value, 10)
|
||||
const directNumber = parseInt(value, 10);
|
||||
if (!isNaN(directNumber) && directNumber > 0) {
|
||||
return directNumber
|
||||
return directNumber;
|
||||
}
|
||||
const urlMatch = value.match(/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)/)
|
||||
const urlMatch = value.match(/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)/);
|
||||
if (urlMatch?.[1]) {
|
||||
return parseInt(urlMatch[1], 10)
|
||||
return parseInt(urlMatch[1], 10);
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
commands: Command[]
|
||||
worktreePaths: string[]
|
||||
initialTools: Tool[]
|
||||
mcpClients?: MCPServerConnection[]
|
||||
dynamicMcpConfig?: Record<string, ScopedMcpServerConfig>
|
||||
debug: boolean
|
||||
mainThreadAgentDefinition?: AgentDefinition
|
||||
autoConnectIdeFlag?: boolean
|
||||
strictMcpConfig?: boolean
|
||||
systemPrompt?: string
|
||||
appendSystemPrompt?: string
|
||||
initialSearchQuery?: string
|
||||
disableSlashCommands?: boolean
|
||||
forkSession?: boolean
|
||||
taskListId?: string
|
||||
filterByPr?: boolean | number | string
|
||||
thinkingConfig: ThinkingConfig
|
||||
onTurnComplete?: (messages: Message[]) => void | Promise<void>
|
||||
}
|
||||
commands: Command[];
|
||||
worktreePaths: string[];
|
||||
initialTools: Tool[];
|
||||
mcpClients?: MCPServerConnection[];
|
||||
dynamicMcpConfig?: Record<string, ScopedMcpServerConfig>;
|
||||
debug: boolean;
|
||||
mainThreadAgentDefinition?: AgentDefinition;
|
||||
autoConnectIdeFlag?: boolean;
|
||||
strictMcpConfig?: boolean;
|
||||
systemPrompt?: string;
|
||||
appendSystemPrompt?: string;
|
||||
initialSearchQuery?: string;
|
||||
disableSlashCommands?: boolean;
|
||||
forkSession?: boolean;
|
||||
taskListId?: string;
|
||||
filterByPr?: boolean | number | string;
|
||||
thinkingConfig: ThinkingConfig;
|
||||
onTurnComplete?: (messages: Message[]) => void | Promise<void>;
|
||||
};
|
||||
|
||||
export function ResumeConversation({
|
||||
commands,
|
||||
@@ -106,155 +103,147 @@ export function ResumeConversation({
|
||||
thinkingConfig,
|
||||
onTurnComplete,
|
||||
}: Props): React.ReactNode {
|
||||
const { rows } = useTerminalSize()
|
||||
const agentDefinitions = useAppState(s => s.agentDefinitions)
|
||||
const setAppState = useSetAppState()
|
||||
const [logs, setLogs] = React.useState<LogOption[]>([])
|
||||
const [loading, setLoading] = React.useState(true)
|
||||
const [resuming, setResuming] = React.useState(false)
|
||||
const [showAllProjects, setShowAllProjects] = React.useState(false)
|
||||
const { rows } = useTerminalSize();
|
||||
const agentDefinitions = useAppState(s => s.agentDefinitions);
|
||||
const setAppState = useSetAppState();
|
||||
const [logs, setLogs] = React.useState<LogOption[]>([]);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [resuming, setResuming] = React.useState(false);
|
||||
const [showAllProjects, setShowAllProjects] = React.useState(false);
|
||||
const [resumeData, setResumeData] = React.useState<{
|
||||
messages: Message[]
|
||||
fileHistorySnapshots?: FileHistorySnapshot[]
|
||||
contentReplacements?: ContentReplacementRecord[]
|
||||
agentName?: string
|
||||
agentColor?: AgentColorName
|
||||
mainThreadAgentDefinition?: AgentDefinition
|
||||
} | null>(null)
|
||||
const [crossProjectCommand, setCrossProjectCommand] = React.useState<
|
||||
string | null
|
||||
>(null)
|
||||
const sessionLogResultRef = React.useRef<SessionLogResult | null>(null)
|
||||
messages: Message[];
|
||||
fileHistorySnapshots?: FileHistorySnapshot[];
|
||||
contentReplacements?: ContentReplacementRecord[];
|
||||
agentName?: string;
|
||||
agentColor?: AgentColorName;
|
||||
mainThreadAgentDefinition?: AgentDefinition;
|
||||
} | null>(null);
|
||||
const [crossProjectCommand, setCrossProjectCommand] = React.useState<string | null>(null);
|
||||
const sessionLogResultRef = React.useRef<SessionLogResult | null>(null);
|
||||
// Mirror of logs.length so loadMoreLogs can compute value indices outside
|
||||
// the setLogs updater (keeping it pure per React's contract).
|
||||
const logCountRef = React.useRef(0)
|
||||
const logCountRef = React.useRef(0);
|
||||
|
||||
const filteredLogs = React.useMemo(() => {
|
||||
let result = logs.filter(l => !l.isSidechain)
|
||||
let result = logs.filter(l => !l.isSidechain);
|
||||
if (filterByPr !== undefined) {
|
||||
if (filterByPr === true) {
|
||||
result = result.filter(l => l.prNumber !== undefined)
|
||||
result = result.filter(l => l.prNumber !== undefined);
|
||||
} else if (typeof filterByPr === 'number') {
|
||||
result = result.filter(l => l.prNumber === filterByPr)
|
||||
result = result.filter(l => l.prNumber === filterByPr);
|
||||
} else if (typeof filterByPr === 'string') {
|
||||
const prNumber = parsePrIdentifier(filterByPr)
|
||||
const prNumber = parsePrIdentifier(filterByPr);
|
||||
if (prNumber !== null) {
|
||||
result = result.filter(l => l.prNumber === prNumber)
|
||||
result = result.filter(l => l.prNumber === prNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}, [logs, filterByPr])
|
||||
const isResumeWithRenameEnabled = isCustomTitleEnabled()
|
||||
return result;
|
||||
}, [logs, filterByPr]);
|
||||
const isResumeWithRenameEnabled = isCustomTitleEnabled();
|
||||
|
||||
React.useEffect(() => {
|
||||
loadSameRepoMessageLogsProgressive(worktreePaths)
|
||||
.then(result => {
|
||||
sessionLogResultRef.current = result
|
||||
logCountRef.current = result.logs.length
|
||||
setLogs(result.logs)
|
||||
setLoading(false)
|
||||
sessionLogResultRef.current = result;
|
||||
logCountRef.current = result.logs.length;
|
||||
setLogs(result.logs);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(error => {
|
||||
logError(error)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [worktreePaths])
|
||||
logError(error);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [worktreePaths]);
|
||||
|
||||
const loadMoreLogs = React.useCallback((count: number) => {
|
||||
const ref = sessionLogResultRef.current
|
||||
if (!ref || ref.nextIndex >= ref.allStatLogs.length) return
|
||||
const ref = sessionLogResultRef.current;
|
||||
if (!ref || ref.nextIndex >= ref.allStatLogs.length) return;
|
||||
|
||||
void enrichLogs(ref.allStatLogs, ref.nextIndex, count).then(result => {
|
||||
ref.nextIndex = result.nextIndex
|
||||
ref.nextIndex = result.nextIndex;
|
||||
if (result.logs.length > 0) {
|
||||
// enrichLogs returns fresh unshared objects — safe to mutate in place.
|
||||
// Offset comes from logCountRef so the setLogs updater stays pure.
|
||||
const offset = logCountRef.current
|
||||
const offset = logCountRef.current;
|
||||
result.logs.forEach((log, i) => {
|
||||
log.value = offset + i
|
||||
})
|
||||
setLogs(prev => prev.concat(result.logs))
|
||||
logCountRef.current += result.logs.length
|
||||
log.value = offset + i;
|
||||
});
|
||||
setLogs(prev => prev.concat(result.logs));
|
||||
logCountRef.current += result.logs.length;
|
||||
} else if (ref.nextIndex < ref.allStatLogs.length) {
|
||||
loadMoreLogs(count)
|
||||
loadMoreLogs(count);
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadLogs = React.useCallback(
|
||||
(allProjects: boolean) => {
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
const promise = allProjects
|
||||
? loadAllProjectsMessageLogsProgressive()
|
||||
: loadSameRepoMessageLogsProgressive(worktreePaths)
|
||||
: loadSameRepoMessageLogsProgressive(worktreePaths);
|
||||
promise
|
||||
.then(result => {
|
||||
sessionLogResultRef.current = result
|
||||
logCountRef.current = result.logs.length
|
||||
setLogs(result.logs)
|
||||
sessionLogResultRef.current = result;
|
||||
logCountRef.current = result.logs.length;
|
||||
setLogs(result.logs);
|
||||
})
|
||||
.catch(error => {
|
||||
logError(error)
|
||||
logError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
setLoading(false);
|
||||
});
|
||||
},
|
||||
[worktreePaths],
|
||||
)
|
||||
);
|
||||
|
||||
const handleToggleAllProjects = React.useCallback(() => {
|
||||
const newValue = !showAllProjects
|
||||
setShowAllProjects(newValue)
|
||||
loadLogs(newValue)
|
||||
}, [showAllProjects, loadLogs])
|
||||
const newValue = !showAllProjects;
|
||||
setShowAllProjects(newValue);
|
||||
loadLogs(newValue);
|
||||
}, [showAllProjects, loadLogs]);
|
||||
|
||||
function onCancel() {
|
||||
// eslint-disable-next-line custom-rules/no-process-exit
|
||||
process.exit(1)
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function onSelect(log: LogOption) {
|
||||
setResuming(true)
|
||||
const resumeStart = performance.now()
|
||||
setResuming(true);
|
||||
const resumeStart = performance.now();
|
||||
|
||||
const crossProjectCheck = checkCrossProjectResume(
|
||||
log,
|
||||
showAllProjects,
|
||||
worktreePaths,
|
||||
)
|
||||
const crossProjectCheck = checkCrossProjectResume(log, showAllProjects, worktreePaths);
|
||||
if (crossProjectCheck.isCrossProject) {
|
||||
if (!crossProjectCheck.isSameRepoWorktree) {
|
||||
const cmd = (crossProjectCheck as { command: string }).command
|
||||
const raw = await setClipboard(cmd)
|
||||
if (raw) process.stdout.write(raw)
|
||||
setCrossProjectCommand(cmd)
|
||||
return
|
||||
const cmd = (crossProjectCheck as { command: string }).command;
|
||||
const raw = await setClipboard(cmd);
|
||||
if (raw) process.stdout.write(raw);
|
||||
setCrossProjectCommand(cmd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await loadConversationForResume(log, undefined)
|
||||
const result = await loadConversationForResume(log, undefined);
|
||||
if (!result) {
|
||||
throw new Error('Failed to load conversation')
|
||||
throw new Error('Failed to load conversation');
|
||||
}
|
||||
|
||||
if (feature('COORDINATOR_MODE')) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const coordinatorModule =
|
||||
require('../coordinator/coordinatorMode.js') as typeof import('../coordinator/coordinatorMode.js')
|
||||
require('../coordinator/coordinatorMode.js') as typeof import('../coordinator/coordinatorMode.js');
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
const warning = coordinatorModule.matchSessionMode(result.mode)
|
||||
const warning = coordinatorModule.matchSessionMode(result.mode);
|
||||
if (warning) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const { getAgentDefinitionsWithOverrides, getActiveAgentsFromList } =
|
||||
require('@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js') as typeof import('@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js')
|
||||
require('@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js') as typeof import('@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js');
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
getAgentDefinitionsWithOverrides.cache.clear?.()
|
||||
const freshAgentDefs = await getAgentDefinitionsWithOverrides(
|
||||
getOriginalCwd(),
|
||||
)
|
||||
getAgentDefinitionsWithOverrides.cache.clear?.();
|
||||
const freshAgentDefs = await getAgentDefinitionsWithOverrides(getOriginalCwd());
|
||||
setAppState(prev => ({
|
||||
...prev,
|
||||
agentDefinitions: {
|
||||
@@ -262,101 +251,86 @@ export function ResumeConversation({
|
||||
allAgents: freshAgentDefs.allAgents,
|
||||
activeAgents: getActiveAgentsFromList(freshAgentDefs.allAgents),
|
||||
},
|
||||
}))
|
||||
result.messages.push(createSystemMessage(warning, 'warning'))
|
||||
}));
|
||||
result.messages.push(createSystemMessage(warning, 'warning'));
|
||||
}
|
||||
}
|
||||
|
||||
if (result.sessionId && !forkSession) {
|
||||
switchSession(
|
||||
asSessionId(result.sessionId),
|
||||
log.fullPath ? dirname(log.fullPath) : null,
|
||||
)
|
||||
await renameRecordingForSession()
|
||||
await resetSessionFilePointer()
|
||||
restoreCostStateForSession(result.sessionId)
|
||||
switchSession(asSessionId(result.sessionId), log.fullPath ? dirname(log.fullPath) : null);
|
||||
await renameRecordingForSession();
|
||||
await resetSessionFilePointer();
|
||||
restoreCostStateForSession(result.sessionId);
|
||||
} else if (forkSession && result.contentReplacements?.length) {
|
||||
await recordContentReplacement(result.contentReplacements)
|
||||
await recordContentReplacement(result.contentReplacements);
|
||||
}
|
||||
|
||||
const { agentDefinition: resolvedAgentDef } = restoreAgentFromSession(
|
||||
result.agentSetting,
|
||||
mainThreadAgentDefinition,
|
||||
agentDefinitions,
|
||||
)
|
||||
setAppState(prev => ({ ...prev, agent: resolvedAgentDef?.agentType }))
|
||||
);
|
||||
setAppState(prev => ({ ...prev, agent: resolvedAgentDef?.agentType }));
|
||||
|
||||
if (feature('COORDINATOR_MODE')) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const { saveMode } = require('../utils/sessionStorage.js')
|
||||
const { saveMode } = require('../utils/sessionStorage.js');
|
||||
const { isCoordinatorMode } =
|
||||
require('../coordinator/coordinatorMode.js') as typeof import('../coordinator/coordinatorMode.js')
|
||||
require('../coordinator/coordinatorMode.js') as typeof import('../coordinator/coordinatorMode.js');
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
saveMode(isCoordinatorMode() ? 'coordinator' : 'normal')
|
||||
saveMode(isCoordinatorMode() ? 'coordinator' : 'normal');
|
||||
}
|
||||
|
||||
const standaloneAgentContext = computeStandaloneAgentContext(
|
||||
result.agentName,
|
||||
result.agentColor,
|
||||
)
|
||||
const standaloneAgentContext = computeStandaloneAgentContext(result.agentName, result.agentColor);
|
||||
if (standaloneAgentContext) {
|
||||
setAppState(prev => ({ ...prev, standaloneAgentContext }))
|
||||
setAppState(prev => ({ ...prev, standaloneAgentContext }));
|
||||
}
|
||||
void updateSessionName(result.agentName)
|
||||
void updateSessionName(result.agentName);
|
||||
|
||||
restoreSessionMetadata(
|
||||
forkSession ? { ...result, worktreeSession: undefined } : result,
|
||||
)
|
||||
restoreSessionMetadata(forkSession ? { ...result, worktreeSession: undefined } : result);
|
||||
|
||||
if (!forkSession) {
|
||||
restoreWorktreeForResume(result.worktreeSession)
|
||||
restoreWorktreeForResume(result.worktreeSession);
|
||||
if (result.sessionId) {
|
||||
adoptResumedSessionFile()
|
||||
adoptResumedSessionFile();
|
||||
}
|
||||
}
|
||||
|
||||
if (feature('CONTEXT_COLLAPSE')) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
;(
|
||||
(
|
||||
require('../services/contextCollapse/persist.js') as typeof import('../services/contextCollapse/persist.js')
|
||||
).restoreFromEntries(
|
||||
result.contextCollapseCommits ?? [],
|
||||
result.contextCollapseSnapshot,
|
||||
)
|
||||
).restoreFromEntries(result.contextCollapseCommits ?? [], result.contextCollapseSnapshot);
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
}
|
||||
|
||||
logEvent('tengu_session_resumed', {
|
||||
entrypoint:
|
||||
'picker' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
entrypoint: 'picker' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
success: true,
|
||||
resume_duration_ms: Math.round(performance.now() - resumeStart),
|
||||
})
|
||||
});
|
||||
|
||||
setLogs([])
|
||||
setLogs([]);
|
||||
setResumeData({
|
||||
messages: result.messages,
|
||||
fileHistorySnapshots: result.fileHistorySnapshots,
|
||||
contentReplacements: result.contentReplacements,
|
||||
agentName: result.agentName,
|
||||
agentColor: (result.agentColor === 'default'
|
||||
? undefined
|
||||
: result.agentColor) as AgentColorName | undefined,
|
||||
agentColor: (result.agentColor === 'default' ? undefined : result.agentColor) as AgentColorName | undefined,
|
||||
mainThreadAgentDefinition: resolvedAgentDef,
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
logEvent('tengu_session_resumed', {
|
||||
entrypoint:
|
||||
'picker' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
entrypoint: 'picker' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
success: false,
|
||||
})
|
||||
logError(e as Error)
|
||||
throw e
|
||||
});
|
||||
logError(e as Error);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (crossProjectCommand) {
|
||||
return <CrossProjectMessage command={crossProjectCommand} />
|
||||
return <CrossProjectMessage command={crossProjectCommand} />;
|
||||
}
|
||||
|
||||
if (resumeData) {
|
||||
@@ -382,7 +356,7 @@ export function ResumeConversation({
|
||||
thinkingConfig={thinkingConfig}
|
||||
onTurnComplete={onTurnComplete}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
@@ -391,7 +365,7 @@ export function ResumeConversation({
|
||||
<Spinner />
|
||||
<Text> Loading conversations…</Text>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (resuming) {
|
||||
@@ -400,11 +374,11 @@ export function ResumeConversation({
|
||||
<Spinner />
|
||||
<Text> Resuming conversation…</Text>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (filteredLogs.length === 0) {
|
||||
return <NoConversationsMessage />
|
||||
return <NoConversationsMessage />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -413,16 +387,14 @@ export function ResumeConversation({
|
||||
maxHeight={rows}
|
||||
onCancel={onCancel}
|
||||
onSelect={onSelect}
|
||||
onLogsChanged={
|
||||
isResumeWithRenameEnabled ? () => loadLogs(showAllProjects) : undefined
|
||||
}
|
||||
onLogsChanged={isResumeWithRenameEnabled ? () => loadLogs(showAllProjects) : undefined}
|
||||
onLoadMore={loadMoreLogs}
|
||||
initialSearchQuery={initialSearchQuery}
|
||||
showAllProjects={showAllProjects}
|
||||
onToggleAllProjects={handleToggleAllProjects}
|
||||
onAgenticSearch={agenticSessionSearch}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function NoConversationsMessage(): React.ReactNode {
|
||||
@@ -430,31 +402,27 @@ function NoConversationsMessage(): React.ReactNode {
|
||||
'app:interrupt',
|
||||
() => {
|
||||
// eslint-disable-next-line custom-rules/no-process-exit
|
||||
process.exit(1)
|
||||
process.exit(1);
|
||||
},
|
||||
{ context: 'Global' },
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>No conversations found to resume.</Text>
|
||||
<Text dimColor>Press Ctrl+C to exit and start a new conversation.</Text>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CrossProjectMessage({
|
||||
command,
|
||||
}: {
|
||||
command: string
|
||||
}): React.ReactNode {
|
||||
function CrossProjectMessage({ command }: { command: string }): React.ReactNode {
|
||||
React.useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
// eslint-disable-next-line custom-rules/no-process-exit
|
||||
process.exit(0)
|
||||
}, 100)
|
||||
return () => clearTimeout(timeout)
|
||||
}, [])
|
||||
process.exit(0);
|
||||
}, 100);
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
@@ -465,5 +433,5 @@ function CrossProjectMessage({
|
||||
</Box>
|
||||
<Text dimColor>(Command copied to clipboard)</Text>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user