Merge pull request #105 from rajrasane/fix/third-party-provider-compatibility

fix: Improve session title handling and Docker compatibility
This commit is contained in:
Kevin Codex
2026-04-02 13:50:18 +08:00
committed by GitHub
5 changed files with 376 additions and 372 deletions

View File

@@ -289,7 +289,7 @@ function checkOllamaProcessorMode(): CheckResult {
if (result.status !== 0) {
const detail = (result.stderr || result.stdout || 'Unable to run ollama ps').trim()
return fail('Ollama processor mode', detail)
return pass('Ollama processor mode', `Native CLI check failed (${detail}). Assuming valid Docker/remote backend since HTTP ping passed.`)
}
const output = (result.stdout || '').trim()

View File

@@ -97,8 +97,8 @@ import { logError } from '../utils/log.js';
/* 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 : () => ({
stripTrailing: () => 0,
handleKeyEvent: () => {},
resetAnchor: () => {}
handleKeyEvent: () => { },
resetAnchor: () => { }
});
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
@@ -106,11 +106,11 @@ const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').V
// 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 : () => ({
state: 'closed',
handleTranscriptSelect: () => {}
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" === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => { };
// Dead code elimination: conditional import for coordinator mode
const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{
name: string;
@@ -192,7 +192,7 @@ import { useInboxPoller } from '../hooks/useInboxPoller.js';
// Dead code elimination: conditional import for loop mode
/* eslint-disable @typescript-eslint/no-require-imports */
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 SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
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
// function identity each render, which would break composedOnScroll's memo.
const HISTORY_STUB = {
maybeLoadOlder: (_: ScrollBoxHandle) => {}
maybeLoadOlder: (_: ScrollBoxHandle) => { }
};
// 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
@@ -865,11 +865,11 @@ export function REPL({
// Ref for the bridge result callback — set after useReplBridge initializes,
// 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
// 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.
// Null when fullscreen mode is disabled (ref never attached).
@@ -1127,7 +1127,7 @@ export function REPL({
// session from mid-conversation context.
const haikuTitleAttemptedRef = useRef((initialMessages?.length ?? 0) > 0);
const agentTitle = mainThreadAgentDefinition?.agentType;
const terminalTitle = sessionTitle ?? agentTitle ?? haikuTitle ?? 'Claude Code';
const terminalTitle = sessionTitle ?? agentTitle ?? haikuTitle ?? 'Open Claude';
const isWaitingForApproval = toolUseConfirmQueue.length > 0 || promptQueue.length > 0 || pendingWorkerRequest || pendingSandboxRequest;
// Local-jsx commands (like /plugin, /config) show user-facing dialogs that
// wait for input. Require jsx != null — if the flag is stuck true but jsx
@@ -2676,7 +2676,7 @@ export function REPL({
// useDeferredHookMessages) and attachment messages (appended by
// processTextPrompt) — both pushed length past 1 on turn one, so the
// 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 text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null;
// 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}>`)) {
haikuTitleAttemptedRef.current = true;
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;
});
@@ -2760,7 +2760,7 @@ export function REPL({
});
}
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
checkAndDisableBypassPermissionsIfNeeded(toolPermissionContext, setAppState),
// Gated on TRANSCRIPT_CLASSIFIER so GrowthBook kill switch runs wherever auto mode is built in
@@ -3110,9 +3110,9 @@ export function REPL({
if (typeof content === 'string' && !initialMsg.message.planContent) {
// Route through onSubmit for proper processing including UserPromptSubmit hooks
void onSubmit(content, {
setCursorOffset: () => {},
clearBuffer: () => {},
resetHistory: () => {}
setCursorOffset: () => { },
clearBuffer: () => { },
resetHistory: () => { }
});
} else {
// Plan messages or complex content (images, etc.) - send directly to model
@@ -3577,9 +3577,9 @@ export function REPL({
const command = autoRunIssueReason ? getAutoRunCommand(autoRunIssueReason) : '/issue';
setAutoRunIssueReason(null); // Clear the state
onSubmit(command, {
setCursorOffset: () => {},
clearBuffer: () => {},
resetHistory: () => {}
setCursorOffset: () => { },
clearBuffer: () => { },
resetHistory: () => { }
}).catch(err => {
logForDebugging(`Auto-run ${command} failed: ${errorMessage(err)}`);
});
@@ -3592,9 +3592,9 @@ export function REPL({
const handleSurveyRequestFeedback = useCallback(() => {
const command = "external" === 'ant' ? '/issue' : '/feedback';
onSubmit(command, {
setCursorOffset: () => {},
clearBuffer: () => {},
resetHistory: () => {}
setCursorOffset: () => { },
clearBuffer: () => { },
resetHistory: () => { }
}).catch(err => {
logForDebugging(`Survey feedback request failed: ${err instanceof Error ? err.message : String(err)}`);
});
@@ -3609,9 +3609,9 @@ export function REPL({
onSubmitRef.current = onSubmit;
const handleOpenRateLimitOptions = useCallback(() => {
void onSubmitRef.current('/rate-limit-options', {
setCursorOffset: () => {},
clearBuffer: () => {},
resetHistory: () => {}
setCursorOffset: () => { },
clearBuffer: () => { },
resetHistory: () => { }
});
}, []);
const handleExit = useCallback(async () => {
@@ -3628,14 +3628,14 @@ export function REPL({
}
const showWorktree = getCurrentWorktreeSession() !== null;
if (showWorktree) {
setExitFlow(<ExitFlow showWorktree onDone={() => {}} onCancel={() => {
setExitFlow(<ExitFlow showWorktree onDone={() => { }} onCancel={() => {
setExitFlow(null);
setIsExiting(false);
}} />);
return;
}
const exitMod = await exit.load();
const exitFlowResult = await exitMod.call(() => {});
const exitFlowResult = await exitMod.call(() => { });
setExitFlow(exitFlowResult);
// If call() returned without killing the process (bg session detach),
// clear isExiting so the UI is usable on reattach. No-op on the normal
@@ -3856,14 +3856,14 @@ export function REPL({
const executeQueuedInput = useCallback(async (queuedCommands: QueuedCommand[]) => {
await handlePromptSubmit({
helpers: {
setCursorOffset: () => {},
clearBuffer: () => {},
resetHistory: () => {}
setCursorOffset: () => { },
clearBuffer: () => { },
resetHistory: () => { }
},
queryGuard,
commands,
onInputChange: () => {},
setPastedContents: () => {},
onInputChange: () => { },
setPastedContents: () => { },
setToolJSX,
getToolUseContext,
messages,
@@ -4022,8 +4022,8 @@ export function REPL({
insertTextRef
}) : {
stripTrailing: () => 0,
handleKeyEvent: () => {},
resetAnchor: () => {},
handleKeyEvent: () => { },
resetAnchor: () => { },
interimRange: null
};
useInboxPoller({
@@ -4793,9 +4793,9 @@ export function REPL({
}
skipIdleCheckRef.current = true;
void onSubmitRef.current(pending.input, {
setCursorOffset: () => {},
clearBuffer: () => {},
resetHistory: () => {}
setCursorOffset: () => { },
clearBuffer: () => { },
resetHistory: () => { }
});
}} />}
{focusedInputDialog === 'ide-onboarding' && <IdeOnboardingDialog onDone={() => setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />}
@@ -4890,11 +4890,11 @@ export function REPL({
{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} />}
{/* 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) */}
{"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 />}
{}
{ }
<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.
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} />

View File

@@ -985,7 +985,7 @@ export const connectToServer = memoize(
const client = new Client(
{
name: 'claude-code',
title: 'Claude Code',
title: 'Open Claude',
version: MACRO.VERSION ?? 'unknown',
description: "Anthropic's agentic coding tool",
websiteUrl: PRODUCT_URL,
@@ -1054,9 +1054,9 @@ export const connectToServer = memoize(
`Connection timeout triggered after ${elapsed}ms (limit: ${getConnectionTimeoutMs()}ms)`,
)
if (inProcessServer) {
inProcessServer.close().catch(() => {})
inProcessServer.close().catch(() => { })
}
transport.close().catch(() => {})
transport.close().catch(() => { })
reject(
new TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS(
`MCP server "${name}" connection timed out after ${getConnectionTimeoutMs()}ms`,
@@ -1145,9 +1145,9 @@ export const connectToServer = memoize(
})
}
if (inProcessServer) {
inProcessServer.close().catch(() => {})
inProcessServer.close().catch(() => { })
}
transport.close().catch(() => {})
transport.close().catch(() => { })
if (stderrOutput) {
logMCPError(name, `Server stderr: ${stderrOutput}`)
}
@@ -1627,7 +1627,7 @@ export const connectToServer = memoize(
logMCPError(name, `Connection failed: ${errorMessage(error)}`)
if (inProcessServer) {
inProcessServer.close().catch(() => {})
inProcessServer.close().catch(() => { })
}
return {
name,
@@ -3280,7 +3280,7 @@ export async function setupSdkMcpClients(
const client = new Client(
{
name: 'claude-code',
title: 'Claude Code',
title: 'Open Claude',
version: MACRO.VERSION ?? 'unknown',
description: "Anthropic's agentic coding tool",
websiteUrl: PRODUCT_URL,

View File

@@ -35,7 +35,7 @@ export async function sendNotification(
})
}
const DEFAULT_TITLE = 'Claude Code'
const DEFAULT_TITLE = 'Open Claude'
async function sendToChannel(
channel: string,

View File

@@ -124,6 +124,10 @@ export async function generateSessionTitle(
level: 'error',
})
logEvent('tengu_session_title_generated', { success: false })
return null
// Fallback: When using 3P providers without a compatible schema,
// default to the application name.
return 'Open Claude'
}
}