feat: 完成大部分操作

This commit is contained in:
claude-code-best
2026-03-31 21:40:37 +08:00
parent 3d4cb096d1
commit c4d92178b7
22 changed files with 561 additions and 98 deletions

View File

@@ -104,13 +104,13 @@ const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').V
// Frustration detection is ant-only (dogfooding). Conditional require so external
// builds eliminate the module entirely (including its two O(n) useMemos that run
// on every messages change, plus the GrowthBook fetch).
const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = "external" === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = ("external" as string) === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
state: 'closed',
handleTranscriptSelect: () => {}
});
// Ant-only org warning. Conditional require so the org UUID list is
// eliminated from external builds (one UUID is on excluded-strings).
const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = "external" === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => {};
const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = ("external" as string) === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => {};
// Dead code elimination: conditional import for coordinator mode
const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{
name: string;
@@ -218,9 +218,9 @@ import { EffortCallout, shouldShowEffortCallout } from '../components/EffortCall
import type { EffortValue } from '../utils/effort.js';
import { RemoteCallout } from '../components/RemoteCallout.js';
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
const AntModelSwitchCallout = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null;
const shouldShowAntModelSwitch = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false;
const UndercoverAutoCallout = "external" === 'ant' ? require('../components/UndercoverAutoCallout.js').UndercoverAutoCallout : null;
const AntModelSwitchCallout = ("external" as string) === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null;
const shouldShowAntModelSwitch = ("external" as string) === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false;
const UndercoverAutoCallout = ("external" as string) === 'ant' ? require('../components/UndercoverAutoCallout.js').UndercoverAutoCallout : null;
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
import { activityManager } from '../utils/activityManager.js';
import { createAbortController } from '../utils/abortController.js';
@@ -601,7 +601,7 @@ export function REPL({
// Env-var gates hoisted to mount-time — isEnvTruthy does toLowerCase+trim+
// includes, and these were on the render path (hot during PageUp spam).
const titleDisabled = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE), []);
const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
const moreRightEnabled = useMemo(() => ("external" as string) === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);
const disableMessageActions = feature('MESSAGE_ACTIONS') ?
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
@@ -733,7 +733,7 @@ export function REPL({
const [showIdeOnboarding, setShowIdeOnboarding] = useState(false);
// Dead code elimination: model switch callout state (ant-only)
const [showModelSwitchCallout, setShowModelSwitchCallout] = useState(() => {
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
return shouldShowAntModelSwitch();
}
return false;
@@ -1012,7 +1012,7 @@ export function REPL({
}, []);
const [showUndercoverCallout, setShowUndercoverCallout] = useState(false);
useEffect(() => {
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
void (async () => {
// Wait for repo classification to settle (memoized, no-op if primed).
const {
@@ -2041,10 +2041,10 @@ export function REPL({
if (allowDialogsWithAnimation && showIdeOnboarding) return 'ide-onboarding';
// Model switch callout (ant-only, eliminated from external builds)
if ("external" === 'ant' && allowDialogsWithAnimation && showModelSwitchCallout) return 'model-switch';
if (("external" as string) === 'ant' && allowDialogsWithAnimation && showModelSwitchCallout) return 'model-switch';
// Undercover auto-enable explainer (ant-only, eliminated from external builds)
if ("external" === 'ant' && allowDialogsWithAnimation && showUndercoverCallout) return 'undercover-callout';
if (("external" as string) === 'ant' && allowDialogsWithAnimation && showUndercoverCallout) return 'undercover-callout';
// Effort callout (shown once for Opus 4.6 users when effort is enabled)
if (allowDialogsWithAnimation && showEffortCallout) return 'effort-callout';
@@ -2482,7 +2482,7 @@ export function REPL({
dynamicSkillDirTriggers: new Set<string>(),
discoveredSkillNames: discoveredSkillNamesRef.current,
setResponseLength,
pushApiMetricsEntry: "external" === 'ant' ? (ttftMs: number) => {
pushApiMetricsEntry: ("external" as string) === 'ant' ? (ttftMs: number) => {
const now = Date.now();
const baseline = responseLengthRef.current;
apiMetricsRef.current.push({
@@ -2605,7 +2605,7 @@ export function REPL({
if (feature('PROACTIVE') || feature('KAIROS')) {
proactiveModule?.setContextBlocked(false);
}
} else if (newMessage.type === 'progress' && isEphemeralToolProgress(newMessage.data.type)) {
} else if ((newMessage as MessageType).type === 'progress' && isEphemeralToolProgress((newMessage as ProgressMessage<unknown>).data.type)) {
// Replace the previous ephemeral progress tick for the same tool
// call instead of appending. Sleep/Bash emit a tick per second and
// only the last one is rendered; appending blows up the messages
@@ -2618,7 +2618,7 @@ export function REPL({
// "Initializing…" because it renders the full progress trail.
setMessages(oldMessages => {
const last = oldMessages.at(-1);
if (last?.type === 'progress' && last.parentToolUseID === newMessage.parentToolUseID && last.data.type === newMessage.data.type) {
if (last?.type === 'progress' && last.parentToolUseID === (newMessage as MessageType).parentToolUseID && last.data.type === (newMessage as ProgressMessage<unknown>).data.type) {
const copy = oldMessages.slice();
copy[copy.length - 1] = newMessage;
return copy;
@@ -2804,14 +2804,14 @@ export function REPL({
if (feature('BUDDY')) {
void fireCompanionObserver(messagesRef.current, reaction => setAppState(prev => prev.companionReaction === reaction ? prev : {
...prev,
companionReaction: reaction
companionReaction: reaction as string | undefined
}));
}
queryCheckpoint('query_end');
// Capture ant-only API metrics before resetLoadingState clears the ref.
// For multi-request turns (tool use loops), compute P50 across all requests.
if ("external" === 'ant' && apiMetricsRef.current.length > 0) {
if (("external" as string) === 'ant' && apiMetricsRef.current.length > 0) {
const entries = apiMetricsRef.current;
const ttfts = entries.map(e => e.ttftMs);
// Compute per-request OTPS using only active streaming time and
@@ -2939,7 +2939,7 @@ export function REPL({
// minutes — wiping the session made the pill disappear entirely, forcing
// the user to re-invoke Tmux just to peek. Skip on abort so the panel
// stays open for inspection (matches the turn-duration guard below).
if ("external" === 'ant' && !abortController.signal.aborted) {
if (("external" as string) === 'ant' && !abortController.signal.aborted) {
setAppState(prev => {
if (prev.tungstenActiveSession === undefined) return prev;
if (prev.tungstenPanelAutoHidden === true) return prev;
@@ -3062,7 +3062,7 @@ export function REPL({
}
// Atomically: clear initial message, set permission mode and rules, and store plan for verification
const shouldStorePlanForVerification = initialMsg.message.planContent && "external" === 'ant' && isEnvTruthy(undefined);
const shouldStorePlanForVerification = initialMsg.message.planContent && ("external" as string) === 'ant' && isEnvTruthy(undefined);
setAppState(prev => {
// Build and apply permission updates (mode + allowedPrompts rules)
let updatedToolPermissionContext = initialMsg.mode ? applyPermissionUpdates(prev.toolPermissionContext, buildPermissionUpdates(initialMsg.mode, initialMsg.allowedPrompts)) : prev.toolPermissionContext;
@@ -3595,7 +3595,7 @@ export function REPL({
// Handler for when user presses 1 on survey thanks screen to share details
const handleSurveyRequestFeedback = useCallback(() => {
const command = "external" === 'ant' ? '/issue' : '/feedback';
const command = ("external" as string) === 'ant' ? '/issue' : '/feedback';
onSubmit(command, {
setCursorOffset: () => {},
clearBuffer: () => {},
@@ -4063,7 +4063,7 @@ export function REPL({
// - Workers receive permission responses via mailbox messages
// - Leaders receive permission requests via mailbox messages
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
// Tasks mode: watch for tasks and auto-process them
// eslint-disable-next-line react-hooks/rules-of-hooks
// biome-ignore lint/correctness/useHookAtTopLevel: conditional for dead code elimination in external builds
@@ -4172,7 +4172,7 @@ export function REPL({
// Fall back to default behavior
const hookType = currentHooks[0]?.data.hookEvent === 'SubagentStop' ? 'subagent stop' : 'stop';
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
const cmd = currentHooks[completedCount]?.data.command;
const label = cmd ? ` '${truncateToWidth(cmd, 40)}'` : '';
return total === 1 ? `running ${hookType} hook${label}` : `running ${hookType} hook${label}\u2026 ${completedCount}/${total}`;
@@ -4581,7 +4581,7 @@ export function REPL({
{toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && <Box flexDirection="column" width="100%">
{toolJSX.jsx}
</Box>}
{"external" === 'ant' && <TungstenLiveMonitor />}
{("external" as string) === 'ant' && <TungstenLiveMonitor />}
{feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && <WebBrowserPanelModule.WebBrowserPanel /> : null}
<Box flexGrow={1} />
{showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
@@ -4804,7 +4804,7 @@ export function REPL({
});
}} />}
{focusedInputDialog === 'ide-onboarding' && <IdeOnboardingDialog onDone={() => setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />}
{"external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
{("external" as string) === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
setShowModelSwitchCallout(false);
if (selection === 'switch' && modelAlias) {
setAppState(prev => ({
@@ -4814,7 +4814,7 @@ export function REPL({
}));
}
}} />}
{"external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
{("external" as string) === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
{focusedInputDialog === 'effort-callout' && <EffortCallout model={mainLoopModel} onDone={selection => {
setShowEffortCallout(false);
if (selection !== 'dismiss') {
@@ -4897,7 +4897,7 @@ export function REPL({
{/* Frustration-triggered transcript sharing prompt */}
{frustrationDetection.state !== 'closed' && <FeedbackSurvey state={frustrationDetection.state} lastResponse={null} handleSelect={() => {}} handleTranscriptSelect={frustrationDetection.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} />}
{/* Skill improvement survey - appears when improvements detected (ant-only) */}
{"external" === 'ant' && skillImprovementSurvey.suggestion && <SkillImprovementSurvey isOpen={skillImprovementSurvey.isOpen} skillName={skillImprovementSurvey.suggestion.skillName} updates={skillImprovementSurvey.suggestion.updates} handleSelect={skillImprovementSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} />}
{("external" as string) === 'ant' && skillImprovementSurvey.suggestion && <SkillImprovementSurvey isOpen={skillImprovementSurvey.isOpen} skillName={skillImprovementSurvey.suggestion.skillName} updates={skillImprovementSurvey.suggestion.updates} handleSelect={skillImprovementSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} />}
{showIssueFlagBanner && <IssueFlagBanner />}
{}
<PromptInput debug={debug} ideSelection={ideSelection} hasSuppressedDialogs={!!hasSuppressedDialogs} isLocalJSXCommandActive={isShowingLocalJSXCommand} getToolUseContext={getToolUseContext} toolPermissionContext={toolPermissionContext} setToolPermissionContext={setToolPermissionContext} apiKeyStatus={apiKeyStatus} commands={commands} agents={agentDefinitions.activeAgents} isLoading={isLoading} onExit={handleExit} verbose={verbose} messages={messages} onAutoUpdaterResult={setAutoUpdaterResult} autoUpdaterResult={autoUpdaterResult} input={inputValue} onInputChange={setInputValue} mode={inputMode} onModeChange={setInputMode} stashedPrompt={stashedPrompt} setStashedPrompt={setStashedPrompt} submitCount={submitCount} onShowMessageSelector={handleShowMessageSelector} onMessageActionsEnter={
@@ -4990,7 +4990,7 @@ export function REPL({
setIsMessageSelectorVisible(false);
setMessageSelectorPreselect(undefined);
}} />}
{"external" === 'ant' && <DevBar />}
{("external" as string) === 'ant' && <DevBar />}
</Box>
{feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
</Box>} />