fix: enable session title generation for non-firstParty providers
This commit is contained in:
@@ -97,8 +97,8 @@ import { logError } from '../utils/log.js';
|
|||||||
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
|
||||||
const useVoiceIntegration: typeof import('../hooks/useVoiceIntegration.js').useVoiceIntegration = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({
|
const useVoiceIntegration: typeof import('../hooks/useVoiceIntegration.js').useVoiceIntegration = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({
|
||||||
stripTrailing: () => 0,
|
stripTrailing: () => 0,
|
||||||
handleKeyEvent: () => {},
|
handleKeyEvent: () => { },
|
||||||
resetAnchor: () => {}
|
resetAnchor: () => { }
|
||||||
});
|
});
|
||||||
const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler : () => null;
|
const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').VoiceKeybindingHandler : () => null;
|
||||||
// Frustration detection is ant-only (dogfooding). Conditional require so external
|
// Frustration detection is ant-only (dogfooding). Conditional require so external
|
||||||
@@ -106,11 +106,11 @@ const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').V
|
|||||||
// on every messages change, plus the GrowthBook fetch).
|
// 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" === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
|
||||||
state: 'closed',
|
state: 'closed',
|
||||||
handleTranscriptSelect: () => {}
|
handleTranscriptSelect: () => { }
|
||||||
});
|
});
|
||||||
// Ant-only org warning. Conditional require so the org UUID list is
|
// Ant-only org warning. Conditional require so the org UUID list is
|
||||||
// eliminated from external builds (one UUID is on excluded-strings).
|
// 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" === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => { };
|
||||||
// Dead code elimination: conditional import for coordinator mode
|
// Dead code elimination: conditional import for coordinator mode
|
||||||
const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{
|
const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{
|
||||||
name: string;
|
name: string;
|
||||||
@@ -192,7 +192,7 @@ import { useInboxPoller } from '../hooks/useInboxPoller.js';
|
|||||||
// Dead code elimination: conditional import for loop mode
|
// Dead code elimination: conditional import for loop mode
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/index.js') : null;
|
const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/index.js') : null;
|
||||||
const PROACTIVE_NO_OP_SUBSCRIBE = (_cb: () => void) => () => {};
|
const PROACTIVE_NO_OP_SUBSCRIBE = (_cb: () => void) => () => { };
|
||||||
const PROACTIVE_FALSE = () => false;
|
const PROACTIVE_FALSE = () => false;
|
||||||
const SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
|
const SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
|
||||||
const useProactive = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/useProactive.js').useProactive : null;
|
const useProactive = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/useProactive.js').useProactive : null;
|
||||||
@@ -297,7 +297,7 @@ const EMPTY_MCP_CLIENTS: MCPServerConnection[] = [];
|
|||||||
// Stable stub for useAssistantHistory's non-KAIROS branch — avoids a new
|
// Stable stub for useAssistantHistory's non-KAIROS branch — avoids a new
|
||||||
// function identity each render, which would break composedOnScroll's memo.
|
// function identity each render, which would break composedOnScroll's memo.
|
||||||
const HISTORY_STUB = {
|
const HISTORY_STUB = {
|
||||||
maybeLoadOlder: (_: ScrollBoxHandle) => {}
|
maybeLoadOlder: (_: ScrollBoxHandle) => { }
|
||||||
};
|
};
|
||||||
// Window after a user-initiated scroll during which type-into-empty does NOT
|
// Window after a user-initiated scroll during which type-into-empty does NOT
|
||||||
// repin to bottom. Josh Rosen's workflow: Claude emits long output → scroll
|
// repin to bottom. Josh Rosen's workflow: Claude emits long output → scroll
|
||||||
@@ -448,28 +448,28 @@ function TranscriptSearchBar({
|
|||||||
const off = cursorOffset;
|
const off = cursorOffset;
|
||||||
const cursorChar = off < query.length ? query[off] : ' ';
|
const cursorChar = off < query.length ? query[off] : ' ';
|
||||||
return <Box borderTopDimColor borderBottom={false} borderLeft={false} borderRight={false} borderStyle="single" marginTop={1} paddingLeft={2} width="100%"
|
return <Box borderTopDimColor borderBottom={false} borderLeft={false} borderRight={false} borderStyle="single" marginTop={1} paddingLeft={2} width="100%"
|
||||||
// applySearchHighlight scans the whole screen buffer. The query
|
// applySearchHighlight scans the whole screen buffer. The query
|
||||||
// text rendered here IS on screen — /foo matches its own 'foo' in
|
// text rendered here IS on screen — /foo matches its own 'foo' in
|
||||||
// the bar. With no content matches that's the ONLY visible match →
|
// the bar. With no content matches that's the ONLY visible match →
|
||||||
// gets CURRENT → underlined. noSelect makes searchHighlight.ts:76
|
// gets CURRENT → underlined. noSelect makes searchHighlight.ts:76
|
||||||
// skip these cells (same exclusion as gutters). You can't text-
|
// skip these cells (same exclusion as gutters). You can't text-
|
||||||
// select the bar either; it's transient chrome, fine.
|
// select the bar either; it's transient chrome, fine.
|
||||||
noSelect>
|
noSelect>
|
||||||
<Text>/</Text>
|
<Text>/</Text>
|
||||||
<Text>{query.slice(0, off)}</Text>
|
<Text>{query.slice(0, off)}</Text>
|
||||||
<Text inverse>{cursorChar}</Text>
|
<Text inverse>{cursorChar}</Text>
|
||||||
{off < query.length && <Text>{query.slice(off + 1)}</Text>}
|
{off < query.length && <Text>{query.slice(off + 1)}</Text>}
|
||||||
<Box flexGrow={1} />
|
<Box flexGrow={1} />
|
||||||
{indexStatus === 'building' ? <Text dimColor>indexing… </Text> : indexStatus ? <Text dimColor>indexed in {indexStatus.ms}ms </Text> : count === 0 && query ? <Text color="error">no matches </Text> : count > 0 ?
|
{indexStatus === 'building' ? <Text dimColor>indexing… </Text> : indexStatus ? <Text dimColor>indexed in {indexStatus.ms}ms </Text> : count === 0 && query ? <Text color="error">no matches </Text> : count > 0 ?
|
||||||
// Engine-counted (indexOf on extractSearchText). May drift from
|
// Engine-counted (indexOf on extractSearchText). May drift from
|
||||||
// render-count for ghost/phantom messages — badge is a rough
|
// render-count for ghost/phantom messages — badge is a rough
|
||||||
// location hint. scanElement gives exact per-message positions
|
// location hint. scanElement gives exact per-message positions
|
||||||
// but counting ALL would cost ~1-3ms × matched-messages.
|
// but counting ALL would cost ~1-3ms × matched-messages.
|
||||||
<Text dimColor>
|
<Text dimColor>
|
||||||
{current}/{count}
|
{current}/{count}
|
||||||
{' '}
|
{' '}
|
||||||
</Text> : null}
|
</Text> : null}
|
||||||
</Box>;
|
</Box>;
|
||||||
}
|
}
|
||||||
const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
|
const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
|
||||||
const TITLE_STATIC_PREFIX = '✳';
|
const TITLE_STATIC_PREFIX = '✳';
|
||||||
@@ -605,8 +605,8 @@ export function REPL({
|
|||||||
const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
|
const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
|
||||||
const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);
|
const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);
|
||||||
const disableMessageActions = feature('MESSAGE_ACTIONS') ?
|
const disableMessageActions = feature('MESSAGE_ACTIONS') ?
|
||||||
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
||||||
useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_MESSAGE_ACTIONS), []) : false;
|
useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_MESSAGE_ACTIONS), []) : false;
|
||||||
|
|
||||||
// Agent definition is state so /resume can update it mid-session
|
// Agent definition is state so /resume can update it mid-session
|
||||||
const [mainThreadAgentDefinition, setMainThreadAgentDefinition] = useState(initialMainThreadAgentDefinition);
|
const [mainThreadAgentDefinition, setMainThreadAgentDefinition] = useState(initialMainThreadAgentDefinition);
|
||||||
@@ -865,11 +865,11 @@ export function REPL({
|
|||||||
|
|
||||||
// Ref for the bridge result callback — set after useReplBridge initializes,
|
// Ref for the bridge result callback — set after useReplBridge initializes,
|
||||||
// read in the onQuery finally block to notify mobile clients that a turn ended.
|
// read in the onQuery finally block to notify mobile clients that a turn ended.
|
||||||
const sendBridgeResultRef = useRef<() => void>(() => {});
|
const sendBridgeResultRef = useRef<() => void>(() => { });
|
||||||
|
|
||||||
// Ref for the synchronous restore callback — set after restoreMessageSync is
|
// Ref for the synchronous restore callback — set after restoreMessageSync is
|
||||||
// defined, read in the onQuery finally block for auto-restore on interrupt.
|
// defined, read in the onQuery finally block for auto-restore on interrupt.
|
||||||
const restoreMessageSyncRef = useRef<(m: UserMessage) => void>(() => {});
|
const restoreMessageSyncRef = useRef<(m: UserMessage) => void>(() => { });
|
||||||
|
|
||||||
// Ref to the fullscreen layout's scroll box for keyboard scrolling.
|
// Ref to the fullscreen layout's scroll box for keyboard scrolling.
|
||||||
// Null when fullscreen mode is disabled (ref never attached).
|
// Null when fullscreen mode is disabled (ref never attached).
|
||||||
@@ -1246,8 +1246,8 @@ export function REPL({
|
|||||||
const cursorNavRef = useRef<MessageActionsNav | null>(null);
|
const cursorNavRef = useRef<MessageActionsNav | null>(null);
|
||||||
// Memoized so Messages' React.memo holds.
|
// Memoized so Messages' React.memo holds.
|
||||||
const unseenDivider = useMemo(() => computeUnseenDivider(messages, dividerIndex),
|
const unseenDivider = useMemo(() => computeUnseenDivider(messages, dividerIndex),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- length change covers appends; useUnseenDivider's count-drop guard clears dividerIndex on replace/rewind
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- length change covers appends; useUnseenDivider's count-drop guard clears dividerIndex on replace/rewind
|
||||||
[dividerIndex, messages.length]);
|
[dividerIndex, messages.length]);
|
||||||
// Re-pin scroll to bottom and clear the unseen-messages baseline. Called
|
// Re-pin scroll to bottom and clear the unseen-messages baseline. Called
|
||||||
// on any user-driven return-to-live action (submit, type-into-empty,
|
// on any user-driven return-to-live action (submit, type-into-empty,
|
||||||
// overlay appear/dismiss).
|
// overlay appear/dismiss).
|
||||||
@@ -1276,13 +1276,13 @@ export function REPL({
|
|||||||
const {
|
const {
|
||||||
maybeLoadOlder
|
maybeLoadOlder
|
||||||
} = feature('KAIROS') ?
|
} = feature('KAIROS') ?
|
||||||
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
||||||
useAssistantHistory({
|
useAssistantHistory({
|
||||||
config: remoteSessionConfig,
|
config: remoteSessionConfig,
|
||||||
setMessages,
|
setMessages,
|
||||||
scrollRef,
|
scrollRef,
|
||||||
onPrepend: shiftDivider
|
onPrepend: shiftDivider
|
||||||
}) : HISTORY_STUB;
|
}) : HISTORY_STUB;
|
||||||
// Compose useUnseenDivider's callbacks with the lazy-load trigger.
|
// Compose useUnseenDivider's callbacks with the lazy-load trigger.
|
||||||
const composedOnScroll = useCallback((sticky: boolean, handle: ScrollBoxHandle) => {
|
const composedOnScroll = useCallback((sticky: boolean, handle: ScrollBoxHandle) => {
|
||||||
lastUserScrollTsRef.current = Date.now();
|
lastUserScrollTsRef.current = Date.now();
|
||||||
@@ -1593,12 +1593,12 @@ export function REPL({
|
|||||||
swarmStartTimeRef.current = null;
|
swarmStartTimeRef.current = null;
|
||||||
swarmBudgetInfoRef.current = undefined;
|
swarmBudgetInfoRef.current = undefined;
|
||||||
setMessages(prev => [...prev, createTurnDurationMessage(totalMs, deferredBudget,
|
setMessages(prev => [...prev, createTurnDurationMessage(totalMs, deferredBudget,
|
||||||
// Count only what recordTranscript will persist — ephemeral
|
// Count only what recordTranscript will persist — ephemeral
|
||||||
// progress ticks and non-ant attachments are filtered by
|
// progress ticks and non-ant attachments are filtered by
|
||||||
// isLoggableMessage and never reach disk. Using raw prev.length
|
// isLoggableMessage and never reach disk. Using raw prev.length
|
||||||
// would make checkResumeConsistency report false delta<0 for
|
// would make checkResumeConsistency report false delta<0 for
|
||||||
// every turn that ran a progress-emitting tool.
|
// every turn that ran a progress-emitting tool.
|
||||||
count(prev, isLoggableMessage))]);
|
count(prev, isLoggableMessage))]);
|
||||||
}
|
}
|
||||||
}, [hasRunningTeammates, setMessages]);
|
}, [hasRunningTeammates, setMessages]);
|
||||||
|
|
||||||
@@ -1665,19 +1665,19 @@ export function REPL({
|
|||||||
setToolJSX
|
setToolJSX
|
||||||
});
|
});
|
||||||
const showSpinner = (!toolJSX || toolJSX.showSpinner === true) && toolUseConfirmQueue.length === 0 && promptQueue.length === 0 && (
|
const showSpinner = (!toolJSX || toolJSX.showSpinner === true) && toolUseConfirmQueue.length === 0 && promptQueue.length === 0 && (
|
||||||
// Show spinner during input processing, API call, while teammates are running,
|
// Show spinner during input processing, API call, while teammates are running,
|
||||||
// or while pending task notifications are queued (prevents spinner bounce between consecutive notifications)
|
// or while pending task notifications are queued (prevents spinner bounce between consecutive notifications)
|
||||||
isLoading || userInputOnProcessing || hasRunningTeammates ||
|
isLoading || userInputOnProcessing || hasRunningTeammates ||
|
||||||
// Keep spinner visible while task notifications are queued for processing.
|
// Keep spinner visible while task notifications are queued for processing.
|
||||||
// Without this, the spinner briefly disappears between consecutive notifications
|
// Without this, the spinner briefly disappears between consecutive notifications
|
||||||
// (e.g., multiple background agents completing in rapid succession) because
|
// (e.g., multiple background agents completing in rapid succession) because
|
||||||
// isLoading goes false momentarily between processing each one.
|
// isLoading goes false momentarily between processing each one.
|
||||||
getCommandQueueLength() > 0) &&
|
getCommandQueueLength() > 0) &&
|
||||||
// Hide spinner when waiting for leader to approve permission request
|
// Hide spinner when waiting for leader to approve permission request
|
||||||
!pendingWorkerRequest && !onlySleepToolActive && (
|
!pendingWorkerRequest && !onlySleepToolActive && (
|
||||||
// Hide spinner when streaming text is visible (the text IS the feedback),
|
// Hide spinner when streaming text is visible (the text IS the feedback),
|
||||||
// but keep it when isBriefOnly suppresses the streaming text display
|
// but keep it when isBriefOnly suppresses the streaming text display
|
||||||
!visibleStreamingText || isBriefOnly);
|
!visibleStreamingText || isBriefOnly);
|
||||||
|
|
||||||
// Check if any permission or ask question prompt is currently visible
|
// Check if any permission or ask question prompt is currently visible
|
||||||
// This is used to prevent the survey from opening while prompts are active
|
// This is used to prevent the survey from opening while prompts are active
|
||||||
@@ -2323,9 +2323,9 @@ export function REPL({
|
|||||||
addNotification({
|
addNotification({
|
||||||
key: 'sandbox-unavailable',
|
key: 'sandbox-unavailable',
|
||||||
jsx: <>
|
jsx: <>
|
||||||
<Text color="warning">sandbox disabled</Text>
|
<Text color="warning">sandbox disabled</Text>
|
||||||
<Text dimColor> · /sandbox</Text>
|
<Text dimColor> · /sandbox</Text>
|
||||||
</>,
|
</>,
|
||||||
priority: 'medium'
|
priority: 'medium'
|
||||||
});
|
});
|
||||||
}, [addNotification]);
|
}, [addNotification]);
|
||||||
@@ -2676,7 +2676,7 @@ export function REPL({
|
|||||||
// useDeferredHookMessages) and attachment messages (appended by
|
// useDeferredHookMessages) and attachment messages (appended by
|
||||||
// processTextPrompt) — both pushed length past 1 on turn one, so the
|
// processTextPrompt) — both pushed length past 1 on turn one, so the
|
||||||
// title silently fell through to the "Claude Code" default.
|
// title silently fell through to the "Claude Code" default.
|
||||||
if (getAPIProvider() === 'firstParty' && !titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) {
|
if (!titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) {
|
||||||
const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta);
|
const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta);
|
||||||
const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null;
|
const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null;
|
||||||
// Skip synthetic breadcrumbs — slash-command output, prompt-skill
|
// Skip synthetic breadcrumbs — slash-command output, prompt-skill
|
||||||
@@ -2686,7 +2686,7 @@ export function REPL({
|
|||||||
if (text && !text.startsWith(`<${LOCAL_COMMAND_STDOUT_TAG}>`) && !text.startsWith(`<${COMMAND_MESSAGE_TAG}>`) && !text.startsWith(`<${COMMAND_NAME_TAG}>`) && !text.startsWith(`<${BASH_INPUT_TAG}>`)) {
|
if (text && !text.startsWith(`<${LOCAL_COMMAND_STDOUT_TAG}>`) && !text.startsWith(`<${COMMAND_MESSAGE_TAG}>`) && !text.startsWith(`<${COMMAND_NAME_TAG}>`) && !text.startsWith(`<${BASH_INPUT_TAG}>`)) {
|
||||||
haikuTitleAttemptedRef.current = true;
|
haikuTitleAttemptedRef.current = true;
|
||||||
void generateSessionTitle(text, new AbortController().signal).then(title => {
|
void generateSessionTitle(text, new AbortController().signal).then(title => {
|
||||||
if (title) setHaikuTitle(title);else haikuTitleAttemptedRef.current = false;
|
if (title) setHaikuTitle(title); else haikuTitleAttemptedRef.current = false;
|
||||||
}, () => {
|
}, () => {
|
||||||
haikuTitleAttemptedRef.current = false;
|
haikuTitleAttemptedRef.current = false;
|
||||||
});
|
});
|
||||||
@@ -2760,11 +2760,11 @@ export function REPL({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
queryCheckpoint('query_context_loading_start');
|
queryCheckpoint('query_context_loading_start');
|
||||||
const [,, defaultSystemPrompt, baseUserContext, systemContext] = await Promise.all([
|
const [, , defaultSystemPrompt, baseUserContext, systemContext] = await Promise.all([
|
||||||
// IMPORTANT: do this after setMessages() above, to avoid UI jank
|
// IMPORTANT: do this after setMessages() above, to avoid UI jank
|
||||||
checkAndDisableBypassPermissionsIfNeeded(toolPermissionContext, setAppState),
|
checkAndDisableBypassPermissionsIfNeeded(toolPermissionContext, setAppState),
|
||||||
// Gated on TRANSCRIPT_CLASSIFIER so GrowthBook kill switch runs wherever auto mode is built in
|
// Gated on TRANSCRIPT_CLASSIFIER so GrowthBook kill switch runs wherever auto mode is built in
|
||||||
feature('TRANSCRIPT_CLASSIFIER') ? checkAndDisableAutoModeIfNeeded(toolPermissionContext, setAppState, store.getState().fastMode) : undefined, getSystemPrompt(freshTools, mainLoopModelParam, Array.from(toolPermissionContext.additionalWorkingDirectories.keys()), freshMcpClients), getUserContext(), getSystemContext()]);
|
feature('TRANSCRIPT_CLASSIFIER') ? checkAndDisableAutoModeIfNeeded(toolPermissionContext, setAppState, store.getState().fastMode) : undefined, getSystemPrompt(freshTools, mainLoopModelParam, Array.from(toolPermissionContext.additionalWorkingDirectories.keys()), freshMcpClients), getUserContext(), getSystemContext()]);
|
||||||
const userContext = {
|
const userContext = {
|
||||||
...baseUserContext,
|
...baseUserContext,
|
||||||
...getCoordinatorUserContext(freshMcpClients, isScratchpadEnabled() ? getScratchpadDir() : undefined),
|
...getCoordinatorUserContext(freshMcpClients, isScratchpadEnabled() ? getScratchpadDir() : undefined),
|
||||||
@@ -3110,9 +3110,9 @@ export function REPL({
|
|||||||
if (typeof content === 'string' && !initialMsg.message.planContent) {
|
if (typeof content === 'string' && !initialMsg.message.planContent) {
|
||||||
// Route through onSubmit for proper processing including UserPromptSubmit hooks
|
// Route through onSubmit for proper processing including UserPromptSubmit hooks
|
||||||
void onSubmit(content, {
|
void onSubmit(content, {
|
||||||
setCursorOffset: () => {},
|
setCursorOffset: () => { },
|
||||||
clearBuffer: () => {},
|
clearBuffer: () => { },
|
||||||
resetHistory: () => {}
|
resetHistory: () => { }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Plan messages or complex content (images, etc.) - send directly to model
|
// Plan messages or complex content (images, etc.) - send directly to model
|
||||||
@@ -3121,10 +3121,10 @@ export function REPL({
|
|||||||
const newAbortController = createAbortController();
|
const newAbortController = createAbortController();
|
||||||
setAbortController(newAbortController);
|
setAbortController(newAbortController);
|
||||||
void onQuery([initialMsg.message], newAbortController, true,
|
void onQuery([initialMsg.message], newAbortController, true,
|
||||||
// shouldQuery
|
// shouldQuery
|
||||||
[],
|
[],
|
||||||
// additionalAllowedTools
|
// additionalAllowedTools
|
||||||
mainLoopModel);
|
mainLoopModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset ref after a delay to allow new initial messages
|
// Reset ref after a delay to allow new initial messages
|
||||||
@@ -3526,18 +3526,18 @@ export function REPL({
|
|||||||
setStashedPrompt(undefined);
|
setStashedPrompt(undefined);
|
||||||
}
|
}
|
||||||
}, [queryGuard,
|
}, [queryGuard,
|
||||||
// isLoading is read at the !isLoading checks above for input-clearing
|
// isLoading is read at the !isLoading checks above for input-clearing
|
||||||
// and submitCount gating. It's derived from isQueryActive || isExternalLoading,
|
// and submitCount gating. It's derived from isQueryActive || isExternalLoading,
|
||||||
// so including it here ensures the closure captures the fresh value.
|
// so including it here ensures the closure captures the fresh value.
|
||||||
isLoading, isExternalLoading, inputMode, commands, setInputValue, setInputMode, setPastedContents, setSubmitCount, setIDESelection, setToolJSX, getToolUseContext,
|
isLoading, isExternalLoading, inputMode, commands, setInputValue, setInputMode, setPastedContents, setSubmitCount, setIDESelection, setToolJSX, getToolUseContext,
|
||||||
// messages is read via messagesRef.current inside the callback to
|
// messages is read via messagesRef.current inside the callback to
|
||||||
// keep onSubmit stable across message updates (see L2384/L2400/L2662).
|
// keep onSubmit stable across message updates (see L2384/L2400/L2662).
|
||||||
// Without this, each setMessages call (~30× per turn) recreates
|
// Without this, each setMessages call (~30× per turn) recreates
|
||||||
// onSubmit, pinning the REPL render scope (1776B) + that render's
|
// onSubmit, pinning the REPL render scope (1776B) + that render's
|
||||||
// messages array in downstream closures (PromptInput, handleAutoRunIssue).
|
// messages array in downstream closures (PromptInput, handleAutoRunIssue).
|
||||||
// Heap analysis showed ~9 REPL scopes and ~15 messages array versions
|
// Heap analysis showed ~9 REPL scopes and ~15 messages array versions
|
||||||
// accumulating after #20174/#20175, all traced to this dep.
|
// accumulating after #20174/#20175, all traced to this dep.
|
||||||
mainLoopModel, pastedContents, ideSelection, setUserInputOnProcessing, setAbortController, addNotification, onQuery, stashedPrompt, setStashedPrompt, setAppState, onBeforeQuery, canUseTool, remoteSession, setMessages, awaitPendingHooks, repinScroll]);
|
mainLoopModel, pastedContents, ideSelection, setUserInputOnProcessing, setAbortController, addNotification, onQuery, stashedPrompt, setStashedPrompt, setAppState, onBeforeQuery, canUseTool, remoteSession, setMessages, awaitPendingHooks, repinScroll]);
|
||||||
|
|
||||||
// Callback for when user submits input while viewing a teammate's transcript
|
// Callback for when user submits input while viewing a teammate's transcript
|
||||||
const onAgentSubmit = useCallback(async (input: string, task: InProcessTeammateTaskState | LocalAgentTaskState, helpers: PromptInputHelpers) => {
|
const onAgentSubmit = useCallback(async (input: string, task: InProcessTeammateTaskState | LocalAgentTaskState, helpers: PromptInputHelpers) => {
|
||||||
@@ -3558,8 +3558,8 @@ export function REPL({
|
|||||||
addNotification({
|
addNotification({
|
||||||
key: `resume-agent-failed-${task.id}`,
|
key: `resume-agent-failed-${task.id}`,
|
||||||
jsx: <Text color="error">
|
jsx: <Text color="error">
|
||||||
Failed to resume agent: {errorMessage(err)}
|
Failed to resume agent: {errorMessage(err)}
|
||||||
</Text>,
|
</Text>,
|
||||||
priority: 'low'
|
priority: 'low'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -3577,9 +3577,9 @@ export function REPL({
|
|||||||
const command = autoRunIssueReason ? getAutoRunCommand(autoRunIssueReason) : '/issue';
|
const command = autoRunIssueReason ? getAutoRunCommand(autoRunIssueReason) : '/issue';
|
||||||
setAutoRunIssueReason(null); // Clear the state
|
setAutoRunIssueReason(null); // Clear the state
|
||||||
onSubmit(command, {
|
onSubmit(command, {
|
||||||
setCursorOffset: () => {},
|
setCursorOffset: () => { },
|
||||||
clearBuffer: () => {},
|
clearBuffer: () => { },
|
||||||
resetHistory: () => {}
|
resetHistory: () => { }
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
logForDebugging(`Auto-run ${command} failed: ${errorMessage(err)}`);
|
logForDebugging(`Auto-run ${command} failed: ${errorMessage(err)}`);
|
||||||
});
|
});
|
||||||
@@ -3592,9 +3592,9 @@ export function REPL({
|
|||||||
const handleSurveyRequestFeedback = useCallback(() => {
|
const handleSurveyRequestFeedback = useCallback(() => {
|
||||||
const command = "external" === 'ant' ? '/issue' : '/feedback';
|
const command = "external" === 'ant' ? '/issue' : '/feedback';
|
||||||
onSubmit(command, {
|
onSubmit(command, {
|
||||||
setCursorOffset: () => {},
|
setCursorOffset: () => { },
|
||||||
clearBuffer: () => {},
|
clearBuffer: () => { },
|
||||||
resetHistory: () => {}
|
resetHistory: () => { }
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
logForDebugging(`Survey feedback request failed: ${err instanceof Error ? err.message : String(err)}`);
|
logForDebugging(`Survey feedback request failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
});
|
});
|
||||||
@@ -3609,9 +3609,9 @@ export function REPL({
|
|||||||
onSubmitRef.current = onSubmit;
|
onSubmitRef.current = onSubmit;
|
||||||
const handleOpenRateLimitOptions = useCallback(() => {
|
const handleOpenRateLimitOptions = useCallback(() => {
|
||||||
void onSubmitRef.current('/rate-limit-options', {
|
void onSubmitRef.current('/rate-limit-options', {
|
||||||
setCursorOffset: () => {},
|
setCursorOffset: () => { },
|
||||||
clearBuffer: () => {},
|
clearBuffer: () => { },
|
||||||
resetHistory: () => {}
|
resetHistory: () => { }
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
const handleExit = useCallback(async () => {
|
const handleExit = useCallback(async () => {
|
||||||
@@ -3628,14 +3628,14 @@ export function REPL({
|
|||||||
}
|
}
|
||||||
const showWorktree = getCurrentWorktreeSession() !== null;
|
const showWorktree = getCurrentWorktreeSession() !== null;
|
||||||
if (showWorktree) {
|
if (showWorktree) {
|
||||||
setExitFlow(<ExitFlow showWorktree onDone={() => {}} onCancel={() => {
|
setExitFlow(<ExitFlow showWorktree onDone={() => { }} onCancel={() => {
|
||||||
setExitFlow(null);
|
setExitFlow(null);
|
||||||
setIsExiting(false);
|
setIsExiting(false);
|
||||||
}} />);
|
}} />);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const exitMod = await exit.load();
|
const exitMod = await exit.load();
|
||||||
const exitFlowResult = await exitMod.call(() => {});
|
const exitFlowResult = await exitMod.call(() => { });
|
||||||
setExitFlow(exitFlowResult);
|
setExitFlow(exitFlowResult);
|
||||||
// If call() returned without killing the process (bg session detach),
|
// If call() returned without killing the process (bg session detach),
|
||||||
// clear isExiting so the UI is usable on reattach. No-op on the normal
|
// clear isExiting so the UI is usable on reattach. No-op on the normal
|
||||||
@@ -3749,18 +3749,18 @@ export function REPL({
|
|||||||
};
|
};
|
||||||
const messageActionCaps: MessageActionCaps = {
|
const messageActionCaps: MessageActionCaps = {
|
||||||
copy: text =>
|
copy: text =>
|
||||||
// setClipboard RETURNS OSC 52 — caller must stdout.write (tmux side-effects load-buffer, but that's tmux-only).
|
// setClipboard RETURNS OSC 52 — caller must stdout.write (tmux side-effects load-buffer, but that's tmux-only).
|
||||||
void setClipboard(text).then(raw => {
|
void setClipboard(text).then(raw => {
|
||||||
if (raw) process.stdout.write(raw);
|
if (raw) process.stdout.write(raw);
|
||||||
addNotification({
|
addNotification({
|
||||||
// Same key as text-selection copy — repeated copies replace toast, don't queue.
|
// Same key as text-selection copy — repeated copies replace toast, don't queue.
|
||||||
key: 'selection-copied',
|
key: 'selection-copied',
|
||||||
text: 'copied',
|
text: 'copied',
|
||||||
color: 'success',
|
color: 'success',
|
||||||
priority: 'immediate',
|
priority: 'immediate',
|
||||||
timeoutMs: 2000
|
timeoutMs: 2000
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
edit: async msg => {
|
edit: async msg => {
|
||||||
// Same skip-confirm check as /rewind: lossless → direct, else confirm dialog.
|
// Same skip-confirm check as /rewind: lossless → direct, else confirm dialog.
|
||||||
const rawIdx = findRawIndex(msg.uuid);
|
const rawIdx = findRawIndex(msg.uuid);
|
||||||
@@ -3856,14 +3856,14 @@ export function REPL({
|
|||||||
const executeQueuedInput = useCallback(async (queuedCommands: QueuedCommand[]) => {
|
const executeQueuedInput = useCallback(async (queuedCommands: QueuedCommand[]) => {
|
||||||
await handlePromptSubmit({
|
await handlePromptSubmit({
|
||||||
helpers: {
|
helpers: {
|
||||||
setCursorOffset: () => {},
|
setCursorOffset: () => { },
|
||||||
clearBuffer: () => {},
|
clearBuffer: () => { },
|
||||||
resetHistory: () => {}
|
resetHistory: () => { }
|
||||||
},
|
},
|
||||||
queryGuard,
|
queryGuard,
|
||||||
commands,
|
commands,
|
||||||
onInputChange: () => {},
|
onInputChange: () => { },
|
||||||
setPastedContents: () => {},
|
setPastedContents: () => { },
|
||||||
setToolJSX,
|
setToolJSX,
|
||||||
getToolUseContext,
|
getToolUseContext,
|
||||||
messages,
|
messages,
|
||||||
@@ -3924,8 +3924,8 @@ export function REPL({
|
|||||||
// User hasn't interacted since response ended, check other conditions
|
// User hasn't interacted since response ended, check other conditions
|
||||||
const idleTimeSinceResponse = Date.now() - lastQueryCompletionTime;
|
const idleTimeSinceResponse = Date.now() - lastQueryCompletionTime;
|
||||||
if (!isLoading && !toolJSX &&
|
if (!isLoading && !toolJSX &&
|
||||||
// Use ref to get current dialog state, avoiding stale closure
|
// Use ref to get current dialog state, avoiding stale closure
|
||||||
focusedInputDialogRef.current === undefined && idleTimeSinceResponse >= getGlobalConfig().messageIdleNotifThresholdMs) {
|
focusedInputDialogRef.current === undefined && idleTimeSinceResponse >= getGlobalConfig().messageIdleNotifThresholdMs) {
|
||||||
void sendNotification({
|
void sendNotification({
|
||||||
message: 'Claude is waiting for your input',
|
message: 'Claude is waiting for your input',
|
||||||
notificationType: 'idle_prompt'
|
notificationType: 'idle_prompt'
|
||||||
@@ -3957,13 +3957,13 @@ export function REPL({
|
|||||||
addNotif({
|
addNotif({
|
||||||
key: 'idle-return-hint',
|
key: 'idle-return-hint',
|
||||||
jsx: mode === 'hint_v2' ? <>
|
jsx: mode === 'hint_v2' ? <>
|
||||||
<Text dimColor>new task? </Text>
|
<Text dimColor>new task? </Text>
|
||||||
<Text color="suggestion">/clear</Text>
|
<Text color="suggestion">/clear</Text>
|
||||||
<Text dimColor> to save </Text>
|
<Text dimColor> to save </Text>
|
||||||
<Text color="suggestion">{formattedTokens} tokens</Text>
|
<Text color="suggestion">{formattedTokens} tokens</Text>
|
||||||
</> : <Text color="warning">
|
</> : <Text color="warning">
|
||||||
new task? /clear to save {formattedTokens} tokens
|
new task? /clear to save {formattedTokens} tokens
|
||||||
</Text>,
|
</Text>,
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
// Persist until submit — the hint fires at T+75min idle, user may
|
// Persist until submit — the hint fires at T+75min idle, user may
|
||||||
// not return for hours. removeNotification in useEffect cleanup
|
// not return for hours. removeNotification in useEffect cleanup
|
||||||
@@ -4015,17 +4015,17 @@ export function REPL({
|
|||||||
|
|
||||||
// Voice input integration (VOICE_MODE builds only)
|
// Voice input integration (VOICE_MODE builds only)
|
||||||
const voice = feature('VOICE_MODE') ?
|
const voice = feature('VOICE_MODE') ?
|
||||||
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
||||||
useVoiceIntegration({
|
useVoiceIntegration({
|
||||||
setInputValueRaw,
|
setInputValueRaw,
|
||||||
inputValueRef,
|
inputValueRef,
|
||||||
insertTextRef
|
insertTextRef
|
||||||
}) : {
|
}) : {
|
||||||
stripTrailing: () => 0,
|
stripTrailing: () => 0,
|
||||||
handleKeyEvent: () => {},
|
handleKeyEvent: () => { },
|
||||||
resetAnchor: () => {},
|
resetAnchor: () => { },
|
||||||
interimRange: null
|
interimRange: null
|
||||||
};
|
};
|
||||||
useInboxPoller({
|
useInboxPoller({
|
||||||
enabled: isAgentSwarmsEnabled(),
|
enabled: isAgentSwarmsEnabled(),
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -4228,11 +4228,11 @@ export function REPL({
|
|||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Search needs virtual scroll (jumpRef drives VirtualMessageList). [
|
// Search needs virtual scroll (jumpRef drives VirtualMessageList). [
|
||||||
// kills it, so !dumpMode — after [ there's nothing to jump in.
|
// kills it, so !dumpMode — after [ there's nothing to jump in.
|
||||||
{
|
{
|
||||||
isActive: screen === 'transcript' && virtualScrollActive && !searchOpen && !dumpMode
|
isActive: screen === 'transcript' && virtualScrollActive && !searchOpen && !dumpMode
|
||||||
});
|
});
|
||||||
const {
|
const {
|
||||||
setQuery: setHighlight,
|
setQuery: setHighlight,
|
||||||
scanElement,
|
scanElement,
|
||||||
@@ -4323,12 +4323,12 @@ export function REPL({
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// !searchOpen: typing 'v' or '[' in the search bar is search input, not
|
// !searchOpen: typing 'v' or '[' in the search bar is search input, not
|
||||||
// a command. No !dumpMode here — v should work after [ (the [ handler
|
// a command. No !dumpMode here — v should work after [ (the [ handler
|
||||||
// guards itself inline).
|
// guards itself inline).
|
||||||
{
|
{
|
||||||
isActive: screen === 'transcript' && virtualScrollActive && !searchOpen
|
isActive: screen === 'transcript' && virtualScrollActive && !searchOpen
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fresh `less` per transcript entry. Prevents stale highlights matching
|
// Fresh `less` per transcript entry. Prevents stale highlights matching
|
||||||
// unrelated normal-mode text (overlay is alt-screen-global) and avoids
|
// unrelated normal-mode text (overlay is alt-screen-global) and avoids
|
||||||
@@ -4396,78 +4396,78 @@ export function REPL({
|
|||||||
const transcriptScrollRef = isFullscreenEnvEnabled() && !disableVirtualScroll && !dumpMode ? scrollRef : undefined;
|
const transcriptScrollRef = isFullscreenEnvEnabled() && !disableVirtualScroll && !dumpMode ? scrollRef : undefined;
|
||||||
const transcriptMessagesElement = <Messages messages={transcriptMessages} tools={tools} commands={commands} verbose={true} toolJSX={null} toolUseConfirmQueue={[]} inProgressToolUseIDs={inProgressToolUseIDs} isMessageSelectorVisible={false} conversationId={conversationId} screen={screen} agentDefinitions={agentDefinitions} streamingToolUses={transcriptStreamingToolUses} showAllInTranscript={showAllInTranscript} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} hidePastThinking={true} streamingThinking={streamingThinking} scrollRef={transcriptScrollRef} jumpRef={jumpRef} onSearchMatchesChange={onSearchMatchesChange} scanElement={scanElement} setPositions={setPositions} disableRenderCap={dumpMode} />;
|
const transcriptMessagesElement = <Messages messages={transcriptMessages} tools={tools} commands={commands} verbose={true} toolJSX={null} toolUseConfirmQueue={[]} inProgressToolUseIDs={inProgressToolUseIDs} isMessageSelectorVisible={false} conversationId={conversationId} screen={screen} agentDefinitions={agentDefinitions} streamingToolUses={transcriptStreamingToolUses} showAllInTranscript={showAllInTranscript} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} hidePastThinking={true} streamingThinking={streamingThinking} scrollRef={transcriptScrollRef} jumpRef={jumpRef} onSearchMatchesChange={onSearchMatchesChange} scanElement={scanElement} setPositions={setPositions} disableRenderCap={dumpMode} />;
|
||||||
const transcriptToolJSX = toolJSX && <Box flexDirection="column" width="100%">
|
const transcriptToolJSX = toolJSX && <Box flexDirection="column" width="100%">
|
||||||
{toolJSX.jsx}
|
{toolJSX.jsx}
|
||||||
</Box>;
|
</Box>;
|
||||||
const transcriptReturn = <KeybindingSetup>
|
const transcriptReturn = <KeybindingSetup>
|
||||||
<AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
|
<AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
|
||||||
<GlobalKeybindingHandlers {...globalKeybindingProps} />
|
<GlobalKeybindingHandlers {...globalKeybindingProps} />
|
||||||
{feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
|
{feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
|
||||||
<CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
|
<CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
|
||||||
{transcriptScrollRef ?
|
{transcriptScrollRef ?
|
||||||
// ScrollKeybindingHandler must mount before CancelRequestHandler so
|
// ScrollKeybindingHandler must mount before CancelRequestHandler so
|
||||||
// ctrl+c-with-selection copies instead of cancelling the active task.
|
// ctrl+c-with-selection copies instead of cancelling the active task.
|
||||||
// Its raw useInput handler only stops propagation when a selection
|
// Its raw useInput handler only stops propagation when a selection
|
||||||
// exists — without one, ctrl+c falls through to CancelRequestHandler.
|
// exists — without one, ctrl+c falls through to CancelRequestHandler.
|
||||||
<ScrollKeybindingHandler scrollRef={scrollRef}
|
<ScrollKeybindingHandler scrollRef={scrollRef}
|
||||||
// Yield wheel/ctrl+u/d to UltraplanChoiceDialog's own scroll
|
// Yield wheel/ctrl+u/d to UltraplanChoiceDialog's own scroll
|
||||||
// handler while the modal is showing.
|
// handler while the modal is showing.
|
||||||
isActive={focusedInputDialog !== 'ultraplan-choice'}
|
isActive={focusedInputDialog !== 'ultraplan-choice'}
|
||||||
// g/G/j/k/ctrl+u/ctrl+d would eat keystrokes the search bar
|
// g/G/j/k/ctrl+u/ctrl+d would eat keystrokes the search bar
|
||||||
// wants. Off while searching.
|
// wants. Off while searching.
|
||||||
isModal={!searchOpen}
|
isModal={!searchOpen}
|
||||||
// Manual scroll exits the search context — clear the yellow
|
// Manual scroll exits the search context — clear the yellow
|
||||||
// current-match marker. Positions are (msg, rowOffset)-keyed;
|
// current-match marker. Positions are (msg, rowOffset)-keyed;
|
||||||
// j/k changes scrollTop so rowOffset is stale → wrong row
|
// j/k changes scrollTop so rowOffset is stale → wrong row
|
||||||
// gets yellow. Next n/N re-establishes via step()→jump().
|
// gets yellow. Next n/N re-establishes via step()→jump().
|
||||||
onScroll={() => jumpRef.current?.disarmSearch()} /> : null}
|
onScroll={() => jumpRef.current?.disarmSearch()} /> : null}
|
||||||
<CancelRequestHandler {...cancelRequestProps} />
|
<CancelRequestHandler {...cancelRequestProps} />
|
||||||
{transcriptScrollRef ? <FullscreenLayout scrollRef={scrollRef} scrollable={<>
|
{transcriptScrollRef ? <FullscreenLayout scrollRef={scrollRef} scrollable={<>
|
||||||
{transcriptMessagesElement}
|
{transcriptMessagesElement}
|
||||||
{transcriptToolJSX}
|
{transcriptToolJSX}
|
||||||
<SandboxViolationExpandedView />
|
<SandboxViolationExpandedView />
|
||||||
</>} bottom={searchOpen ? <TranscriptSearchBar jumpRef={jumpRef}
|
</>} bottom={searchOpen ? <TranscriptSearchBar jumpRef={jumpRef}
|
||||||
// Seed was tried (c01578c8) — broke /hello muscle
|
// Seed was tried (c01578c8) — broke /hello muscle
|
||||||
// memory (cursor lands after 'foo', /hello → foohello).
|
// memory (cursor lands after 'foo', /hello → foohello).
|
||||||
// Cancel-restore handles the 'don't lose prior search'
|
// Cancel-restore handles the 'don't lose prior search'
|
||||||
// concern differently (onCancel re-applies searchQuery).
|
// concern differently (onCancel re-applies searchQuery).
|
||||||
initialQuery="" count={searchCount} current={searchCurrent} onClose={q => {
|
initialQuery="" count={searchCount} current={searchCurrent} onClose={q => {
|
||||||
// Enter — commit. 0-match guard: junk query shouldn't
|
// Enter — commit. 0-match guard: junk query shouldn't
|
||||||
// persist (badge hidden, n/N dead anyway).
|
// persist (badge hidden, n/N dead anyway).
|
||||||
setSearchQuery(searchCount > 0 ? q : '');
|
setSearchQuery(searchCount > 0 ? q : '');
|
||||||
setSearchOpen(false);
|
setSearchOpen(false);
|
||||||
// onCancel path: bar unmounts before its useEffect([query])
|
// onCancel path: bar unmounts before its useEffect([query])
|
||||||
// can fire with ''. Without this, searchCount stays stale
|
// can fire with ''. Without this, searchCount stays stale
|
||||||
// (n guard at :4956 passes) and VML's matches[] too
|
// (n guard at :4956 passes) and VML's matches[] too
|
||||||
// (nextMatch walks the old array). Phantom nav, no
|
// (nextMatch walks the old array). Phantom nav, no
|
||||||
// highlight. onExit (Enter, q non-empty) still commits.
|
// highlight. onExit (Enter, q non-empty) still commits.
|
||||||
if (!q) {
|
if (!q) {
|
||||||
setSearchCount(0);
|
setSearchCount(0);
|
||||||
setSearchCurrent(0);
|
setSearchCurrent(0);
|
||||||
|
jumpRef.current?.setSearchQuery('');
|
||||||
|
}
|
||||||
|
}} onCancel={() => {
|
||||||
|
// Esc/ctrl+c/ctrl+g — undo. Bar's effect last fired
|
||||||
|
// with whatever was typed. searchQuery (REPL state)
|
||||||
|
// is unchanged since / (onClose = commit, didn't run).
|
||||||
|
// Two VML calls: '' restores anchor (0-match else-
|
||||||
|
// branch), then searchQuery re-scans from anchor's
|
||||||
|
// nearest. Both synchronous — one React batch.
|
||||||
|
// setHighlight explicit: REPL's sync-effect dep is
|
||||||
|
// searchQuery (unchanged), wouldn't re-fire.
|
||||||
|
setSearchOpen(false);
|
||||||
jumpRef.current?.setSearchQuery('');
|
jumpRef.current?.setSearchQuery('');
|
||||||
}
|
jumpRef.current?.setSearchQuery(searchQuery);
|
||||||
}} onCancel={() => {
|
setHighlight(searchQuery);
|
||||||
// Esc/ctrl+c/ctrl+g — undo. Bar's effect last fired
|
}} setHighlight={setHighlight} /> : <TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={true} status={editorStatus || undefined} searchBadge={searchQuery && searchCount > 0 ? {
|
||||||
// with whatever was typed. searchQuery (REPL state)
|
current: searchCurrent,
|
||||||
// is unchanged since / (onClose = commit, didn't run).
|
count: searchCount
|
||||||
// Two VML calls: '' restores anchor (0-match else-
|
} : undefined} />} /> : <>
|
||||||
// branch), then searchQuery re-scans from anchor's
|
{transcriptMessagesElement}
|
||||||
// nearest. Both synchronous — one React batch.
|
{transcriptToolJSX}
|
||||||
// setHighlight explicit: REPL's sync-effect dep is
|
<SandboxViolationExpandedView />
|
||||||
// searchQuery (unchanged), wouldn't re-fire.
|
<TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={false} suppressShowAll={dumpMode} status={editorStatus || undefined} />
|
||||||
setSearchOpen(false);
|
</>}
|
||||||
jumpRef.current?.setSearchQuery('');
|
</KeybindingSetup>;
|
||||||
jumpRef.current?.setSearchQuery(searchQuery);
|
|
||||||
setHighlight(searchQuery);
|
|
||||||
}} setHighlight={setHighlight} /> : <TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={true} status={editorStatus || undefined} searchBadge={searchQuery && searchCount > 0 ? {
|
|
||||||
current: searchCurrent,
|
|
||||||
count: searchCount
|
|
||||||
} : undefined} />} /> : <>
|
|
||||||
{transcriptMessagesElement}
|
|
||||||
{transcriptToolJSX}
|
|
||||||
<SandboxViolationExpandedView />
|
|
||||||
<TranscriptModeFooter showAllInTranscript={showAllInTranscript} virtualScroll={false} suppressShowAll={dumpMode} status={editorStatus || undefined} />
|
|
||||||
</>}
|
|
||||||
</KeybindingSetup>;
|
|
||||||
// The virtual-scroll branch (FullscreenLayout above) needs
|
// The virtual-scroll branch (FullscreenLayout above) needs
|
||||||
// <AlternateScreen>'s <Box height={rows}> constraint — without it,
|
// <AlternateScreen>'s <Box height={rows}> constraint — without it,
|
||||||
// ScrollBox's flexGrow has no ceiling, viewport = content height,
|
// ScrollBox's flexGrow has no ceiling, viewport = content height,
|
||||||
@@ -4478,8 +4478,8 @@ export function REPL({
|
|||||||
// unwrapped — it wants native terminal scrollback.
|
// unwrapped — it wants native terminal scrollback.
|
||||||
if (transcriptScrollRef) {
|
if (transcriptScrollRef) {
|
||||||
return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
|
return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
|
||||||
{transcriptReturn}
|
{transcriptReturn}
|
||||||
</AlternateScreen>;
|
</AlternateScreen>;
|
||||||
}
|
}
|
||||||
return transcriptReturn;
|
return transcriptReturn;
|
||||||
}
|
}
|
||||||
@@ -4541,11 +4541,11 @@ export function REPL({
|
|||||||
// early return above wraps its virtual-scroll branch the same way; only
|
// early return above wraps its virtual-scroll branch the same way; only
|
||||||
// the 30-cap dump branch stays unwrapped for native terminal scrollback.
|
// the 30-cap dump branch stays unwrapped for native terminal scrollback.
|
||||||
const mainReturn = <KeybindingSetup>
|
const mainReturn = <KeybindingSetup>
|
||||||
<AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
|
<AnimatedTerminalTitle isAnimating={titleIsAnimating} title={terminalTitle} disabled={titleDisabled} noPrefix={showStatusInTerminalTab} />
|
||||||
<GlobalKeybindingHandlers {...globalKeybindingProps} />
|
<GlobalKeybindingHandlers {...globalKeybindingProps} />
|
||||||
{feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
|
{feature('VOICE_MODE') ? <VoiceKeybindingHandler voiceHandleKeyEvent={voice.handleKeyEvent} stripTrailing={voice.stripTrailing} resetAnchor={voice.resetAnchor} isActive={!toolJSX?.isLocalJSXCommand} /> : null}
|
||||||
<CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
|
<CommandKeybindingHandlers onSubmit={onSubmit} isActive={!toolJSX?.isLocalJSXCommand} />
|
||||||
{/* ScrollKeybindingHandler must mount before CancelRequestHandler so
|
{/* ScrollKeybindingHandler must mount before CancelRequestHandler so
|
||||||
ctrl+c-with-selection copies instead of cancelling the active task.
|
ctrl+c-with-selection copies instead of cancelling the active task.
|
||||||
Its raw useInput handler only stops propagation when a selection
|
Its raw useInput handler only stops propagation when a selection
|
||||||
exists — without one, ctrl+c falls through to CancelRequestHandler.
|
exists — without one, ctrl+c falls through to CancelRequestHandler.
|
||||||
@@ -4553,40 +4553,40 @@ export function REPL({
|
|||||||
the modal's inner ScrollBox is not keyboard-driven. onScroll
|
the modal's inner ScrollBox is not keyboard-driven. onScroll
|
||||||
stays suppressed while a modal is showing so scroll doesn't
|
stays suppressed while a modal is showing so scroll doesn't
|
||||||
stamp divider/pill state. */}
|
stamp divider/pill state. */}
|
||||||
<ScrollKeybindingHandler scrollRef={scrollRef} isActive={isFullscreenEnvEnabled() && (centeredModal != null || !focusedInputDialog || focusedInputDialog === 'tool-permission')} onScroll={centeredModal || toolPermissionOverlay || viewedAgentTask ? undefined : composedOnScroll} />
|
<ScrollKeybindingHandler scrollRef={scrollRef} isActive={isFullscreenEnvEnabled() && (centeredModal != null || !focusedInputDialog || focusedInputDialog === 'tool-permission')} onScroll={centeredModal || toolPermissionOverlay || viewedAgentTask ? undefined : composedOnScroll} />
|
||||||
{feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? <MessageActionsKeybindings handlers={messageActionHandlers} isActive={cursor !== null} /> : null}
|
{feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? <MessageActionsKeybindings handlers={messageActionHandlers} isActive={cursor !== null} /> : null}
|
||||||
<CancelRequestHandler {...cancelRequestProps} />
|
<CancelRequestHandler {...cancelRequestProps} />
|
||||||
<MCPConnectionManager key={remountKey} dynamicMcpConfig={dynamicMcpConfig} isStrictMcpConfig={strictMcpConfig}>
|
<MCPConnectionManager key={remountKey} dynamicMcpConfig={dynamicMcpConfig} isStrictMcpConfig={strictMcpConfig}>
|
||||||
<FullscreenLayout scrollRef={scrollRef} overlay={toolPermissionOverlay} bottomFloat={feature('BUDDY') && companionVisible && !companionNarrow ? <CompanionFloatingBubble /> : undefined} modal={centeredModal} modalScrollRef={modalScrollRef} dividerYRef={dividerYRef} hidePill={!!viewedAgentTask} hideSticky={!!viewedTeammateTask} newMessageCount={unseenDivider?.count ?? 0} onPillClick={() => {
|
<FullscreenLayout scrollRef={scrollRef} overlay={toolPermissionOverlay} bottomFloat={feature('BUDDY') && companionVisible && !companionNarrow ? <CompanionFloatingBubble /> : undefined} modal={centeredModal} modalScrollRef={modalScrollRef} dividerYRef={dividerYRef} hidePill={!!viewedAgentTask} hideSticky={!!viewedTeammateTask} newMessageCount={unseenDivider?.count ?? 0} onPillClick={() => {
|
||||||
setCursor(null);
|
setCursor(null);
|
||||||
jumpToNew(scrollRef.current);
|
jumpToNew(scrollRef.current);
|
||||||
}} scrollable={<>
|
}} scrollable={<>
|
||||||
<TeammateViewHeader />
|
<TeammateViewHeader />
|
||||||
<Messages messages={displayedMessages} tools={tools} commands={commands} verbose={verbose} toolJSX={toolJSX} toolUseConfirmQueue={toolUseConfirmQueue} inProgressToolUseIDs={viewedTeammateTask ? viewedTeammateTask.inProgressToolUseIDs ?? new Set() : inProgressToolUseIDs} isMessageSelectorVisible={isMessageSelectorVisible} conversationId={conversationId} screen={screen} streamingToolUses={streamingToolUses} showAllInTranscript={showAllInTranscript} agentDefinitions={agentDefinitions} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} streamingText={isLoading && !viewedAgentTask ? visibleStreamingText : null} isBriefOnly={viewedAgentTask ? false : isBriefOnly} unseenDivider={viewedAgentTask ? undefined : unseenDivider} scrollRef={isFullscreenEnvEnabled() ? scrollRef : undefined} trackStickyPrompt={isFullscreenEnvEnabled() ? true : undefined} cursor={cursor} setCursor={setCursor} cursorNavRef={cursorNavRef} />
|
<Messages messages={displayedMessages} tools={tools} commands={commands} verbose={verbose} toolJSX={toolJSX} toolUseConfirmQueue={toolUseConfirmQueue} inProgressToolUseIDs={viewedTeammateTask ? viewedTeammateTask.inProgressToolUseIDs ?? new Set() : inProgressToolUseIDs} isMessageSelectorVisible={isMessageSelectorVisible} conversationId={conversationId} screen={screen} streamingToolUses={streamingToolUses} showAllInTranscript={showAllInTranscript} agentDefinitions={agentDefinitions} onOpenRateLimitOptions={handleOpenRateLimitOptions} isLoading={isLoading} streamingText={isLoading && !viewedAgentTask ? visibleStreamingText : null} isBriefOnly={viewedAgentTask ? false : isBriefOnly} unseenDivider={viewedAgentTask ? undefined : unseenDivider} scrollRef={isFullscreenEnvEnabled() ? scrollRef : undefined} trackStickyPrompt={isFullscreenEnvEnabled() ? true : undefined} cursor={cursor} setCursor={setCursor} cursorNavRef={cursorNavRef} />
|
||||||
<AwsAuthStatusBox />
|
<AwsAuthStatusBox />
|
||||||
{/* Hide the processing placeholder while a modal is showing —
|
{/* Hide the processing placeholder while a modal is showing —
|
||||||
it would sit at the last visible transcript row right above
|
it would sit at the last visible transcript row right above
|
||||||
the ▔ divider, showing "❯ /config" as redundant clutter
|
the ▔ divider, showing "❯ /config" as redundant clutter
|
||||||
(the modal IS the /config UI). Outside modals it stays so
|
(the modal IS the /config UI). Outside modals it stays so
|
||||||
the user sees their input echoed while Claude processes. */}
|
the user sees their input echoed while Claude processes. */}
|
||||||
{!disabled && placeholderText && !centeredModal && <UserTextMessage param={{
|
{!disabled && placeholderText && !centeredModal && <UserTextMessage param={{
|
||||||
text: placeholderText,
|
text: placeholderText,
|
||||||
type: 'text'
|
type: 'text'
|
||||||
}} addMargin={true} verbose={verbose} />}
|
}} addMargin={true} verbose={verbose} />}
|
||||||
{toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && <Box flexDirection="column" width="100%">
|
{toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && <Box flexDirection="column" width="100%">
|
||||||
{toolJSX.jsx}
|
{toolJSX.jsx}
|
||||||
</Box>}
|
</Box>}
|
||||||
{"external" === 'ant' && <TungstenLiveMonitor />}
|
{"external" === 'ant' && <TungstenLiveMonitor />}
|
||||||
{feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && <WebBrowserPanelModule.WebBrowserPanel /> : null}
|
{feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && <WebBrowserPanelModule.WebBrowserPanel /> : null}
|
||||||
<Box flexGrow={1} />
|
<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} />}
|
{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} />}
|
||||||
{!showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && <BriefIdleStatus />}
|
{!showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && <BriefIdleStatus />}
|
||||||
{isFullscreenEnvEnabled() && <PromptInputQueuedCommands />}
|
{isFullscreenEnvEnabled() && <PromptInputQueuedCommands />}
|
||||||
</>} bottom={<Box flexDirection={feature('BUDDY') && companionNarrow ? 'column' : 'row'} width="100%" alignItems={feature('BUDDY') && companionNarrow ? undefined : 'flex-end'}>
|
</>} bottom={<Box flexDirection={feature('BUDDY') && companionNarrow ? 'column' : 'row'} width="100%" alignItems={feature('BUDDY') && companionNarrow ? undefined : 'flex-end'}>
|
||||||
{feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? <CompanionSprite /> : null}
|
{feature('BUDDY') && companionNarrow && isFullscreenEnvEnabled() && companionVisible ? <CompanionSprite /> : null}
|
||||||
<Box flexDirection="column" flexGrow={1}>
|
<Box flexDirection="column" flexGrow={1}>
|
||||||
{permissionStickyFooter}
|
{permissionStickyFooter}
|
||||||
{/* Immediate local-jsx commands (/btw, /sandbox, /assistant,
|
{/* Immediate local-jsx commands (/btw, /sandbox, /assistant,
|
||||||
/issue) render here, NOT inside scrollable. They stay mounted
|
/issue) render here, NOT inside scrollable. They stay mounted
|
||||||
while the main conversation streams behind them, so ScrollBox
|
while the main conversation streams behind them, so ScrollBox
|
||||||
relayouts on each new message would drag them around. bottom
|
relayouts on each new message would drag them around. bottom
|
||||||
@@ -4595,13 +4595,13 @@ export function REPL({
|
|||||||
stays in scrollable: the main loop is paused so no jiggle,
|
stays in scrollable: the main loop is paused so no jiggle,
|
||||||
and their tall content (DiffDetailView renders up to 400
|
and their tall content (DiffDetailView renders up to 400
|
||||||
lines with no internal scroll) needs the outer ScrollBox. */}
|
lines with no internal scroll) needs the outer ScrollBox. */}
|
||||||
{toolJSX?.isLocalJSXCommand && toolJSX.isImmediate && !toolJsxCentered && <Box flexDirection="column" width="100%">
|
{toolJSX?.isLocalJSXCommand && toolJSX.isImmediate && !toolJsxCentered && <Box flexDirection="column" width="100%">
|
||||||
{toolJSX.jsx}
|
{toolJSX.jsx}
|
||||||
</Box>}
|
</Box>}
|
||||||
{!showSpinner && !toolJSX?.isLocalJSXCommand && showExpandedTodos && tasksV2 && tasksV2.length > 0 && <Box width="100%" flexDirection="column">
|
{!showSpinner && !toolJSX?.isLocalJSXCommand && showExpandedTodos && tasksV2 && tasksV2.length > 0 && <Box width="100%" flexDirection="column">
|
||||||
<TaskListV2 tasks={tasksV2} isStandalone={true} />
|
<TaskListV2 tasks={tasksV2} isStandalone={true} />
|
||||||
</Box>}
|
</Box>}
|
||||||
{focusedInputDialog === 'sandbox-permission' && <SandboxPermissionRequest key={sandboxPermissionRequestQueue[0]!.hostPattern.host} hostPattern={sandboxPermissionRequestQueue[0]!.hostPattern} onUserResponse={(response: {
|
{focusedInputDialog === 'sandbox-permission' && <SandboxPermissionRequest key={sandboxPermissionRequestQueue[0]!.hostPattern.host} hostPattern={sandboxPermissionRequestQueue[0]!.hostPattern} onUserResponse={(response: {
|
||||||
allow: boolean;
|
allow: boolean;
|
||||||
persistToSettings: boolean;
|
persistToSettings: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -4650,7 +4650,7 @@ export function REPL({
|
|||||||
sandboxBridgeCleanupRef.current.delete(approvedHost);
|
sandboxBridgeCleanupRef.current.delete(approvedHost);
|
||||||
}
|
}
|
||||||
}} />}
|
}} />}
|
||||||
{focusedInputDialog === 'prompt' && <PromptDialog key={promptQueue[0]!.request.prompt} title={promptQueue[0]!.title} toolInputSummary={promptQueue[0]!.toolInputSummary} request={promptQueue[0]!.request} onRespond={selectedKey => {
|
{focusedInputDialog === 'prompt' && <PromptDialog key={promptQueue[0]!.request.prompt} title={promptQueue[0]!.title} toolInputSummary={promptQueue[0]!.toolInputSummary} request={promptQueue[0]!.request} onRespond={selectedKey => {
|
||||||
const item = promptQueue[0];
|
const item = promptQueue[0];
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
item.resolve({
|
item.resolve({
|
||||||
@@ -4664,12 +4664,12 @@ export function REPL({
|
|||||||
item.reject(new Error('Prompt cancelled by user'));
|
item.reject(new Error('Prompt cancelled by user'));
|
||||||
setPromptQueue(([, ...tail]) => tail);
|
setPromptQueue(([, ...tail]) => tail);
|
||||||
}} />}
|
}} />}
|
||||||
{/* Show pending indicator on worker while waiting for leader approval */}
|
{/* Show pending indicator on worker while waiting for leader approval */}
|
||||||
{pendingWorkerRequest && <WorkerPendingPermission toolName={pendingWorkerRequest.toolName} description={pendingWorkerRequest.description} />}
|
{pendingWorkerRequest && <WorkerPendingPermission toolName={pendingWorkerRequest.toolName} description={pendingWorkerRequest.description} />}
|
||||||
{/* Show pending indicator for sandbox permission on worker side */}
|
{/* Show pending indicator for sandbox permission on worker side */}
|
||||||
{pendingSandboxRequest && <WorkerPendingPermission toolName="Network Access" description={`Waiting for leader to approve network access to ${pendingSandboxRequest.host}`} />}
|
{pendingSandboxRequest && <WorkerPendingPermission toolName="Network Access" description={`Waiting for leader to approve network access to ${pendingSandboxRequest.host}`} />}
|
||||||
{/* Worker sandbox permission requests from swarm workers */}
|
{/* Worker sandbox permission requests from swarm workers */}
|
||||||
{focusedInputDialog === 'worker-sandbox-permission' && <SandboxPermissionRequest key={workerSandboxPermissions.queue[0]!.requestId} hostPattern={{
|
{focusedInputDialog === 'worker-sandbox-permission' && <SandboxPermissionRequest key={workerSandboxPermissions.queue[0]!.requestId} hostPattern={{
|
||||||
host: workerSandboxPermissions.queue[0]!.host,
|
host: workerSandboxPermissions.queue[0]!.host,
|
||||||
port: undefined
|
port: undefined
|
||||||
} as NetworkHostPattern} onUserResponse={(response: {
|
} as NetworkHostPattern} onUserResponse={(response: {
|
||||||
@@ -4713,7 +4713,7 @@ export function REPL({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}} />}
|
}} />}
|
||||||
{focusedInputDialog === 'elicitation' && <ElicitationDialog key={elicitation.queue[0]!.serverName + ':' + String(elicitation.queue[0]!.requestId)} event={elicitation.queue[0]!} onResponse={(action, content) => {
|
{focusedInputDialog === 'elicitation' && <ElicitationDialog key={elicitation.queue[0]!.serverName + ':' + String(elicitation.queue[0]!.requestId)} event={elicitation.queue[0]!} onResponse={(action, content) => {
|
||||||
const currentRequest = elicitation.queue[0];
|
const currentRequest = elicitation.queue[0];
|
||||||
if (!currentRequest) return;
|
if (!currentRequest) return;
|
||||||
// Call respond callback to resolve Promise
|
// Call respond callback to resolve Promise
|
||||||
@@ -4742,7 +4742,7 @@ export function REPL({
|
|||||||
}));
|
}));
|
||||||
currentRequest?.onWaitingDismiss?.(action);
|
currentRequest?.onWaitingDismiss?.(action);
|
||||||
}} />}
|
}} />}
|
||||||
{focusedInputDialog === 'cost' && <CostThresholdDialog onDone={() => {
|
{focusedInputDialog === 'cost' && <CostThresholdDialog onDone={() => {
|
||||||
setShowCostDialog(false);
|
setShowCostDialog(false);
|
||||||
setHaveShownCostDialog(true);
|
setHaveShownCostDialog(true);
|
||||||
saveGlobalConfig(current => ({
|
saveGlobalConfig(current => ({
|
||||||
@@ -4751,7 +4751,7 @@ export function REPL({
|
|||||||
}));
|
}));
|
||||||
logEvent('tengu_cost_threshold_acknowledged', {});
|
logEvent('tengu_cost_threshold_acknowledged', {});
|
||||||
}} />}
|
}} />}
|
||||||
{focusedInputDialog === 'idle-return' && idleReturnPending && <IdleReturnDialog idleMinutes={idleReturnPending.idleMinutes} totalInputTokens={getTotalInputTokens()} onDone={async action => {
|
{focusedInputDialog === 'idle-return' && idleReturnPending && <IdleReturnDialog idleMinutes={idleReturnPending.idleMinutes} totalInputTokens={getTotalInputTokens()} onDone={async action => {
|
||||||
const pending = idleReturnPending;
|
const pending = idleReturnPending;
|
||||||
setIdleReturnPending(null);
|
setIdleReturnPending(null);
|
||||||
logEvent('tengu_idle_return_action', {
|
logEvent('tengu_idle_return_action', {
|
||||||
@@ -4793,13 +4793,13 @@ export function REPL({
|
|||||||
}
|
}
|
||||||
skipIdleCheckRef.current = true;
|
skipIdleCheckRef.current = true;
|
||||||
void onSubmitRef.current(pending.input, {
|
void onSubmitRef.current(pending.input, {
|
||||||
setCursorOffset: () => {},
|
setCursorOffset: () => { },
|
||||||
clearBuffer: () => {},
|
clearBuffer: () => { },
|
||||||
resetHistory: () => {}
|
resetHistory: () => { }
|
||||||
});
|
});
|
||||||
}} />}
|
}} />}
|
||||||
{focusedInputDialog === 'ide-onboarding' && <IdeOnboardingDialog onDone={() => setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />}
|
{focusedInputDialog === 'ide-onboarding' && <IdeOnboardingDialog onDone={() => setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />}
|
||||||
{"external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
|
{"external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
|
||||||
setShowModelSwitchCallout(false);
|
setShowModelSwitchCallout(false);
|
||||||
if (selection === 'switch' && modelAlias) {
|
if (selection === 'switch' && modelAlias) {
|
||||||
setAppState(prev => ({
|
setAppState(prev => ({
|
||||||
@@ -4809,8 +4809,8 @@ export function REPL({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}} />}
|
}} />}
|
||||||
{"external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
|
{"external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
|
||||||
{focusedInputDialog === 'effort-callout' && <EffortCallout model={mainLoopModel} onDone={selection => {
|
{focusedInputDialog === 'effort-callout' && <EffortCallout model={mainLoopModel} onDone={selection => {
|
||||||
setShowEffortCallout(false);
|
setShowEffortCallout(false);
|
||||||
if (selection !== 'dismiss') {
|
if (selection !== 'dismiss') {
|
||||||
setAppState(prev => ({
|
setAppState(prev => ({
|
||||||
@@ -4819,7 +4819,7 @@ export function REPL({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}} />}
|
}} />}
|
||||||
{focusedInputDialog === 'remote-callout' && <RemoteCallout onDone={selection => {
|
{focusedInputDialog === 'remote-callout' && <RemoteCallout onDone={selection => {
|
||||||
setAppState(prev => {
|
setAppState(prev => {
|
||||||
if (!prev.showRemoteCallout) return prev;
|
if (!prev.showRemoteCallout) return prev;
|
||||||
return {
|
return {
|
||||||
@@ -4834,17 +4834,17 @@ export function REPL({
|
|||||||
});
|
});
|
||||||
}} />}
|
}} />}
|
||||||
|
|
||||||
{exitFlow}
|
{exitFlow}
|
||||||
|
|
||||||
{focusedInputDialog === 'plugin-hint' && hintRecommendation && <PluginHintMenu pluginName={hintRecommendation.pluginName} pluginDescription={hintRecommendation.pluginDescription} marketplaceName={hintRecommendation.marketplaceName} sourceCommand={hintRecommendation.sourceCommand} onResponse={handleHintResponse} />}
|
{focusedInputDialog === 'plugin-hint' && hintRecommendation && <PluginHintMenu pluginName={hintRecommendation.pluginName} pluginDescription={hintRecommendation.pluginDescription} marketplaceName={hintRecommendation.marketplaceName} sourceCommand={hintRecommendation.sourceCommand} onResponse={handleHintResponse} />}
|
||||||
|
|
||||||
{focusedInputDialog === 'lsp-recommendation' && lspRecommendation && <LspRecommendationMenu pluginName={lspRecommendation.pluginName} pluginDescription={lspRecommendation.pluginDescription} fileExtension={lspRecommendation.fileExtension} onResponse={handleLspResponse} />}
|
{focusedInputDialog === 'lsp-recommendation' && lspRecommendation && <LspRecommendationMenu pluginName={lspRecommendation.pluginName} pluginDescription={lspRecommendation.pluginDescription} fileExtension={lspRecommendation.fileExtension} onResponse={handleLspResponse} />}
|
||||||
|
|
||||||
{focusedInputDialog === 'desktop-upsell' && <DesktopUpsellStartup onDone={() => setShowDesktopUpsellStartup(false)} />}
|
{focusedInputDialog === 'desktop-upsell' && <DesktopUpsellStartup onDone={() => setShowDesktopUpsellStartup(false)} />}
|
||||||
|
|
||||||
{feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-choice' && ultraplanPendingChoice && <UltraplanChoiceDialog plan={ultraplanPendingChoice.plan} sessionId={ultraplanPendingChoice.sessionId} taskId={ultraplanPendingChoice.taskId} setMessages={setMessages} readFileState={readFileState.current} getAppState={() => store.getState()} setConversationId={setConversationId} /> : null}
|
{feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-choice' && ultraplanPendingChoice && <UltraplanChoiceDialog plan={ultraplanPendingChoice.plan} sessionId={ultraplanPendingChoice.sessionId} taskId={ultraplanPendingChoice.taskId} setMessages={setMessages} readFileState={readFileState.current} getAppState={() => store.getState()} setConversationId={setConversationId} /> : null}
|
||||||
|
|
||||||
{feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-launch' && ultraplanLaunchPending && <UltraplanLaunchDialog onChoice={(choice, opts) => {
|
{feature('ULTRAPLAN') ? focusedInputDialog === 'ultraplan-launch' && ultraplanLaunchPending && <UltraplanLaunchDialog onChoice={(choice, opts) => {
|
||||||
const blurb = ultraplanLaunchPending.blurb;
|
const blurb = ultraplanLaunchPending.blurb;
|
||||||
setAppState(prev => prev.ultraplanLaunchPending ? {
|
setAppState(prev => prev.ultraplanLaunchPending ? {
|
||||||
...prev,
|
...prev,
|
||||||
@@ -4884,26 +4884,26 @@ export function REPL({
|
|||||||
}).then(appendStdout).catch(logError);
|
}).then(appendStdout).catch(logError);
|
||||||
}} /> : null}
|
}} /> : null}
|
||||||
|
|
||||||
{mrRender()}
|
{mrRender()}
|
||||||
|
|
||||||
{!toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && <>
|
{!toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && <>
|
||||||
{autoRunIssueReason && <AutoRunIssueNotification onRun={handleAutoRunIssue} onCancel={handleCancelAutoRunIssue} reason={getAutoRunIssueReasonText(autoRunIssueReason)} />}
|
{autoRunIssueReason && <AutoRunIssueNotification onRun={handleAutoRunIssue} onCancel={handleCancelAutoRunIssue} reason={getAutoRunIssueReasonText(autoRunIssueReason)} />}
|
||||||
{postCompactSurvey.state !== 'closed' ? <FeedbackSurvey state={postCompactSurvey.state} lastResponse={postCompactSurvey.lastResponse} handleSelect={postCompactSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} /> : memorySurvey.state !== 'closed' ? <FeedbackSurvey state={memorySurvey.state} lastResponse={memorySurvey.lastResponse} handleSelect={memorySurvey.handleSelect} handleTranscriptSelect={memorySurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} message="How well did Claude use its memory? (optional)" /> : <FeedbackSurvey state={feedbackSurvey.state} lastResponse={feedbackSurvey.lastResponse} handleSelect={feedbackSurvey.handleSelect} handleTranscriptSelect={feedbackSurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback} />}
|
{postCompactSurvey.state !== 'closed' ? <FeedbackSurvey state={postCompactSurvey.state} lastResponse={postCompactSurvey.lastResponse} handleSelect={postCompactSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} /> : memorySurvey.state !== 'closed' ? <FeedbackSurvey state={memorySurvey.state} lastResponse={memorySurvey.lastResponse} handleSelect={memorySurvey.handleSelect} handleTranscriptSelect={memorySurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} message="How well did Claude use its memory? (optional)" /> : <FeedbackSurvey state={feedbackSurvey.state} lastResponse={feedbackSurvey.lastResponse} handleSelect={feedbackSurvey.handleSelect} handleTranscriptSelect={feedbackSurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback} />}
|
||||||
{/* Frustration-triggered transcript sharing prompt */}
|
{/* Frustration-triggered transcript sharing prompt */}
|
||||||
{frustrationDetection.state !== 'closed' && <FeedbackSurvey state={frustrationDetection.state} lastResponse={null} handleSelect={() => {}} handleTranscriptSelect={frustrationDetection.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} />}
|
{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) */}
|
{/* 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" === 'ant' && skillImprovementSurvey.suggestion && <SkillImprovementSurvey isOpen={skillImprovementSurvey.isOpen} skillName={skillImprovementSurvey.suggestion.skillName} updates={skillImprovementSurvey.suggestion.updates} handleSelect={skillImprovementSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} />}
|
||||||
{showIssueFlagBanner && <IssueFlagBanner />}
|
{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={
|
<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={
|
||||||
// Works during isLoading — edit cancels first; uuid selection survives appends.
|
// Works during isLoading — edit cancels first; uuid selection survives appends.
|
||||||
feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? enterMessageActions : undefined} mcpClients={mcpClients} pastedContents={pastedContents} setPastedContents={setPastedContents} vimMode={vimMode} setVimMode={setVimMode} showBashesDialog={showBashesDialog} setShowBashesDialog={setShowBashesDialog} onSubmit={onSubmit} onAgentSubmit={onAgentSubmit} isSearchingHistory={isSearchingHistory} setIsSearchingHistory={setIsSearchingHistory} helpOpen={isHelpOpen} setHelpOpen={setIsHelpOpen} insertTextRef={feature('VOICE_MODE') ? insertTextRef : undefined} voiceInterimRange={voice.interimRange} />
|
feature('MESSAGE_ACTIONS') && isFullscreenEnvEnabled() && !disableMessageActions ? enterMessageActions : undefined} mcpClients={mcpClients} pastedContents={pastedContents} setPastedContents={setPastedContents} vimMode={vimMode} setVimMode={setVimMode} showBashesDialog={showBashesDialog} setShowBashesDialog={setShowBashesDialog} onSubmit={onSubmit} onAgentSubmit={onAgentSubmit} isSearchingHistory={isSearchingHistory} setIsSearchingHistory={setIsSearchingHistory} helpOpen={isHelpOpen} setHelpOpen={setIsHelpOpen} insertTextRef={feature('VOICE_MODE') ? insertTextRef : undefined} voiceInterimRange={voice.interimRange} />
|
||||||
<SessionBackgroundHint onBackgroundSession={handleBackgroundSession} isLoading={isLoading} />
|
<SessionBackgroundHint onBackgroundSession={handleBackgroundSession} isLoading={isLoading} />
|
||||||
</>}
|
</>}
|
||||||
{cursor &&
|
{cursor &&
|
||||||
// inputValue is REPL state; typed text survives the round-trip.
|
// inputValue is REPL state; typed text survives the round-trip.
|
||||||
<MessageActionsBar cursor={cursor} />}
|
<MessageActionsBar cursor={cursor} />}
|
||||||
{focusedInputDialog === 'message-selector' && <MessageSelector messages={messages} preselectedMessage={messageSelectorPreselect} onPreRestore={onCancel} onRestoreCode={async (message: UserMessage) => {
|
{focusedInputDialog === 'message-selector' && <MessageSelector messages={messages} preselectedMessage={messageSelectorPreselect} onPreRestore={onCancel} onRestoreCode={async (message: UserMessage) => {
|
||||||
await fileHistoryRewind((updater: (prev: FileHistoryState) => FileHistoryState) => {
|
await fileHistoryRewind((updater: (prev: FileHistoryState) => FileHistoryState) => {
|
||||||
setAppState(prev => ({
|
setAppState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -4985,16 +4985,16 @@ export function REPL({
|
|||||||
setIsMessageSelectorVisible(false);
|
setIsMessageSelectorVisible(false);
|
||||||
setMessageSelectorPreselect(undefined);
|
setMessageSelectorPreselect(undefined);
|
||||||
}} />}
|
}} />}
|
||||||
{"external" === 'ant' && <DevBar />}
|
{"external" === 'ant' && <DevBar />}
|
||||||
</Box>
|
</Box>
|
||||||
{feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
|
{feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
|
||||||
</Box>} />
|
</Box>} />
|
||||||
</MCPConnectionManager>
|
</MCPConnectionManager>
|
||||||
</KeybindingSetup>;
|
</KeybindingSetup>;
|
||||||
if (isFullscreenEnvEnabled()) {
|
if (isFullscreenEnvEnabled()) {
|
||||||
return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
|
return <AlternateScreen mouseTracking={isMouseTrackingEnabled()}>
|
||||||
{mainReturn}
|
{mainReturn}
|
||||||
</AlternateScreen>;
|
</AlternateScreen>;
|
||||||
}
|
}
|
||||||
return mainReturn;
|
return mainReturn;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user