mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 06:45:50 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,59 +1,67 @@
|
||||
import * as React from 'react';
|
||||
import { getOauthProfileFromApiKey } from 'src/services/oauth/getOauthProfile.js';
|
||||
import { isClaudeAISubscriber } from 'src/utils/auth.js';
|
||||
import { Text } from '../../ink.js';
|
||||
import { logEvent } from '../../services/analytics/index.js';
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
||||
import { useStartupNotification } from './useStartupNotification.js';
|
||||
const MAX_SHOW_COUNT = 3;
|
||||
import * as React from 'react'
|
||||
import { getOauthProfileFromApiKey } from 'src/services/oauth/getOauthProfile.js'
|
||||
import { isClaudeAISubscriber } from 'src/utils/auth.js'
|
||||
import { Text } from '../../ink.js'
|
||||
import { logEvent } from '../../services/analytics/index.js'
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
|
||||
import { useStartupNotification } from './useStartupNotification.js'
|
||||
|
||||
const MAX_SHOW_COUNT = 3
|
||||
|
||||
/**
|
||||
* Hook to check if the user has a subscription on Console but isn't logged into it.
|
||||
*/
|
||||
export function useCanSwitchToExistingSubscription() {
|
||||
useStartupNotification(_temp2);
|
||||
export function useCanSwitchToExistingSubscription(): void {
|
||||
useStartupNotification(async () => {
|
||||
if ((getGlobalConfig().subscriptionNoticeCount ?? 0) >= MAX_SHOW_COUNT) {
|
||||
return null
|
||||
}
|
||||
const subscriptionType = await getExistingClaudeSubscription()
|
||||
if (subscriptionType === null) return null
|
||||
|
||||
saveGlobalConfig(current => ({
|
||||
...current,
|
||||
subscriptionNoticeCount: (current.subscriptionNoticeCount ?? 0) + 1,
|
||||
}))
|
||||
logEvent('tengu_switch_to_subscription_notice_shown', {})
|
||||
|
||||
return {
|
||||
key: 'switch-to-subscription',
|
||||
jsx: (
|
||||
<Text color="suggestion">
|
||||
Use your existing Claude {subscriptionType} plan with Claude Code
|
||||
<Text color="text" dimColor>
|
||||
{' '}
|
||||
· /login to activate
|
||||
</Text>
|
||||
</Text>
|
||||
),
|
||||
priority: 'low',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has a subscription but is not currently logged into it.
|
||||
* This helps inform users they should run /login to access their subscription.
|
||||
*/
|
||||
async function _temp2() {
|
||||
if ((getGlobalConfig().subscriptionNoticeCount ?? 0) >= MAX_SHOW_COUNT) {
|
||||
return null;
|
||||
}
|
||||
const subscriptionType = await getExistingClaudeSubscription();
|
||||
if (subscriptionType === null) {
|
||||
return null;
|
||||
}
|
||||
saveGlobalConfig(_temp);
|
||||
logEvent("tengu_switch_to_subscription_notice_shown", {});
|
||||
return {
|
||||
key: "switch-to-subscription",
|
||||
jsx: <Text color="suggestion">Use your existing Claude {subscriptionType} plan with Claude Code<Text color="text" dimColor={true}>{" "}· /login to activate</Text></Text>,
|
||||
priority: "low"
|
||||
};
|
||||
}
|
||||
function _temp(current) {
|
||||
return {
|
||||
...current,
|
||||
subscriptionNoticeCount: (current.subscriptionNoticeCount ?? 0) + 1
|
||||
};
|
||||
}
|
||||
async function getExistingClaudeSubscription(): Promise<'Max' | 'Pro' | null> {
|
||||
// If already using subscription auth, there is nothing to switch to
|
||||
if (isClaudeAISubscriber()) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
const profile = await getOauthProfileFromApiKey();
|
||||
const profile = await getOauthProfileFromApiKey()
|
||||
if (!profile) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
if (profile.account.has_claude_max) {
|
||||
return 'Max';
|
||||
return 'Max'
|
||||
}
|
||||
|
||||
if (profile.account.has_claude_pro) {
|
||||
return 'Pro';
|
||||
return 'Pro'
|
||||
}
|
||||
return null;
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,43 +1,30 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useNotifications } from 'src/context/notifications.js';
|
||||
import { getModelDeprecationWarning } from 'src/utils/model/deprecation.js';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
export function useDeprecationWarningNotification(model) {
|
||||
const $ = _c(4);
|
||||
const {
|
||||
addNotification
|
||||
} = useNotifications();
|
||||
const lastWarningRef = useRef(null);
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== addNotification || $[1] !== model) {
|
||||
t0 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
const deprecationWarning = getModelDeprecationWarning(model);
|
||||
if (deprecationWarning && deprecationWarning !== lastWarningRef.current) {
|
||||
lastWarningRef.current = deprecationWarning;
|
||||
addNotification({
|
||||
key: "model-deprecation-warning",
|
||||
text: deprecationWarning,
|
||||
color: "warning",
|
||||
priority: "high"
|
||||
});
|
||||
}
|
||||
if (!deprecationWarning) {
|
||||
lastWarningRef.current = null;
|
||||
}
|
||||
};
|
||||
t1 = [model, addNotification];
|
||||
$[0] = addNotification;
|
||||
$[1] = model;
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
t1 = $[3];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useNotifications } from 'src/context/notifications.js'
|
||||
import { getModelDeprecationWarning } from 'src/utils/model/deprecation.js'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
|
||||
export function useDeprecationWarningNotification(model: string): void {
|
||||
const { addNotification } = useNotifications()
|
||||
const lastWarningRef = useRef<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
const deprecationWarning = getModelDeprecationWarning(model)
|
||||
|
||||
// Show warning if model is deprecated and we haven't shown this exact warning yet
|
||||
if (deprecationWarning && deprecationWarning !== lastWarningRef.current) {
|
||||
lastWarningRef.current = deprecationWarning
|
||||
addNotification({
|
||||
key: 'model-deprecation-warning',
|
||||
text: deprecationWarning,
|
||||
color: 'warning',
|
||||
priority: 'high',
|
||||
})
|
||||
}
|
||||
|
||||
// Reset tracking if model changes to non-deprecated
|
||||
if (!deprecationWarning) {
|
||||
lastWarningRef.current = null
|
||||
}
|
||||
}, [model, addNotification])
|
||||
}
|
||||
|
||||
@@ -1,161 +1,111 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useEffect } from 'react';
|
||||
import { useNotifications } from 'src/context/notifications.js';
|
||||
import { useAppState, useSetAppState } from 'src/state/AppState.js';
|
||||
import { type CooldownReason, isFastModeEnabled, onCooldownExpired, onCooldownTriggered, onFastModeOverageRejection, onOrgFastModeChanged } from 'src/utils/fastMode.js';
|
||||
import { formatDuration } from 'src/utils/format.js';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
const COOLDOWN_STARTED_KEY = 'fast-mode-cooldown-started';
|
||||
const COOLDOWN_EXPIRED_KEY = 'fast-mode-cooldown-expired';
|
||||
const ORG_CHANGED_KEY = 'fast-mode-org-changed';
|
||||
const OVERAGE_REJECTED_KEY = 'fast-mode-overage-rejected';
|
||||
export function useFastModeNotification() {
|
||||
const $ = _c(13);
|
||||
const {
|
||||
addNotification
|
||||
} = useNotifications();
|
||||
const isFastMode = useAppState(_temp);
|
||||
const setAppState = useSetAppState();
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== addNotification || $[1] !== isFastMode || $[2] !== setAppState) {
|
||||
t0 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (!isFastModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
return onOrgFastModeChanged(orgEnabled => {
|
||||
if (orgEnabled) {
|
||||
addNotification({
|
||||
key: ORG_CHANGED_KEY,
|
||||
color: "fastMode",
|
||||
priority: "immediate",
|
||||
text: "Fast mode is now available \xB7 /fast to turn on"
|
||||
});
|
||||
} else {
|
||||
if (isFastMode) {
|
||||
setAppState(_temp2);
|
||||
addNotification({
|
||||
key: ORG_CHANGED_KEY,
|
||||
color: "warning",
|
||||
priority: "immediate",
|
||||
text: "Fast mode has been disabled by your organization"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
t1 = [addNotification, isFastMode, setAppState];
|
||||
$[0] = addNotification;
|
||||
$[1] = isFastMode;
|
||||
$[2] = setAppState;
|
||||
$[3] = t0;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
t1 = $[4];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[5] !== addNotification || $[6] !== setAppState) {
|
||||
t2 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (!isFastModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
return onFastModeOverageRejection(message => {
|
||||
setAppState(_temp3);
|
||||
import { useEffect } from 'react'
|
||||
import { useNotifications } from 'src/context/notifications.js'
|
||||
import { useAppState, useSetAppState } from 'src/state/AppState.js'
|
||||
import {
|
||||
type CooldownReason,
|
||||
isFastModeEnabled,
|
||||
onCooldownExpired,
|
||||
onCooldownTriggered,
|
||||
onFastModeOverageRejection,
|
||||
onOrgFastModeChanged,
|
||||
} from 'src/utils/fastMode.js'
|
||||
import { formatDuration } from 'src/utils/format.js'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
|
||||
const COOLDOWN_STARTED_KEY = 'fast-mode-cooldown-started'
|
||||
const COOLDOWN_EXPIRED_KEY = 'fast-mode-cooldown-expired'
|
||||
const ORG_CHANGED_KEY = 'fast-mode-org-changed'
|
||||
const OVERAGE_REJECTED_KEY = 'fast-mode-overage-rejected'
|
||||
|
||||
export function useFastModeNotification(): void {
|
||||
const { addNotification } = useNotifications()
|
||||
const isFastMode = useAppState(s => s.fastMode)
|
||||
const setAppState = useSetAppState()
|
||||
|
||||
// Notify when org fast mode status changes
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (!isFastModeEnabled()) {
|
||||
return
|
||||
}
|
||||
|
||||
return onOrgFastModeChanged(orgEnabled => {
|
||||
if (orgEnabled) {
|
||||
addNotification({
|
||||
key: OVERAGE_REJECTED_KEY,
|
||||
color: "warning",
|
||||
priority: "immediate",
|
||||
text: message
|
||||
});
|
||||
});
|
||||
};
|
||||
t3 = [addNotification, setAppState];
|
||||
$[5] = addNotification;
|
||||
$[6] = setAppState;
|
||||
$[7] = t2;
|
||||
$[8] = t3;
|
||||
} else {
|
||||
t2 = $[7];
|
||||
t3 = $[8];
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
let t4;
|
||||
let t5;
|
||||
if ($[9] !== addNotification || $[10] !== isFastMode) {
|
||||
t4 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (!isFastMode) {
|
||||
return;
|
||||
}
|
||||
const unsubTriggered = onCooldownTriggered((resetAt, reason) => {
|
||||
const resetIn = formatDuration(resetAt - Date.now(), {
|
||||
hideTrailingZeros: true
|
||||
});
|
||||
const message_0 = getCooldownMessage(reason, resetIn);
|
||||
key: ORG_CHANGED_KEY,
|
||||
color: 'fastMode',
|
||||
priority: 'immediate',
|
||||
text: 'Fast mode is now available · /fast to turn on',
|
||||
})
|
||||
} else if (isFastMode) {
|
||||
// Org disabled fast mode — permanently turn off fast mode
|
||||
setAppState(prev => ({ ...prev, fastMode: false }))
|
||||
addNotification({
|
||||
key: COOLDOWN_STARTED_KEY,
|
||||
invalidates: [COOLDOWN_EXPIRED_KEY],
|
||||
text: message_0,
|
||||
color: "warning",
|
||||
priority: "immediate"
|
||||
});
|
||||
});
|
||||
const unsubExpired = onCooldownExpired(() => {
|
||||
addNotification({
|
||||
key: COOLDOWN_EXPIRED_KEY,
|
||||
invalidates: [COOLDOWN_STARTED_KEY],
|
||||
color: "fastMode",
|
||||
text: "Fast limit reset \xB7 now using fast mode",
|
||||
priority: "immediate"
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
unsubTriggered();
|
||||
unsubExpired();
|
||||
};
|
||||
};
|
||||
t5 = [addNotification, isFastMode];
|
||||
$[9] = addNotification;
|
||||
$[10] = isFastMode;
|
||||
$[11] = t4;
|
||||
$[12] = t5;
|
||||
} else {
|
||||
t4 = $[11];
|
||||
t5 = $[12];
|
||||
}
|
||||
useEffect(t4, t5);
|
||||
}
|
||||
function _temp3(prev_0) {
|
||||
return {
|
||||
...prev_0,
|
||||
fastMode: false
|
||||
};
|
||||
}
|
||||
function _temp2(prev) {
|
||||
return {
|
||||
...prev,
|
||||
fastMode: false
|
||||
};
|
||||
}
|
||||
function _temp(s) {
|
||||
return s.fastMode;
|
||||
key: ORG_CHANGED_KEY,
|
||||
color: 'warning',
|
||||
priority: 'immediate',
|
||||
text: 'Fast mode has been disabled by your organization',
|
||||
})
|
||||
}
|
||||
})
|
||||
}, [addNotification, isFastMode, setAppState])
|
||||
|
||||
// Notify when fast mode is rejected due to overage/extra usage issues
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (!isFastModeEnabled()) return
|
||||
|
||||
return onFastModeOverageRejection(message => {
|
||||
setAppState(prev => ({ ...prev, fastMode: false }))
|
||||
addNotification({
|
||||
key: OVERAGE_REJECTED_KEY,
|
||||
color: 'warning',
|
||||
priority: 'immediate',
|
||||
text: message,
|
||||
})
|
||||
})
|
||||
}, [addNotification, setAppState])
|
||||
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (!isFastMode) {
|
||||
return
|
||||
}
|
||||
|
||||
const unsubTriggered = onCooldownTriggered((resetAt, reason) => {
|
||||
const resetIn = formatDuration(resetAt - Date.now(), {
|
||||
hideTrailingZeros: true,
|
||||
})
|
||||
const message = getCooldownMessage(reason, resetIn)
|
||||
addNotification({
|
||||
key: COOLDOWN_STARTED_KEY,
|
||||
invalidates: [COOLDOWN_EXPIRED_KEY],
|
||||
text: message,
|
||||
color: 'warning',
|
||||
priority: 'immediate',
|
||||
})
|
||||
})
|
||||
const unsubExpired = onCooldownExpired(() => {
|
||||
addNotification({
|
||||
key: COOLDOWN_EXPIRED_KEY,
|
||||
invalidates: [COOLDOWN_STARTED_KEY],
|
||||
color: 'fastMode',
|
||||
text: `Fast limit reset · now using fast mode`,
|
||||
priority: 'immediate',
|
||||
})
|
||||
})
|
||||
return () => {
|
||||
unsubTriggered()
|
||||
unsubExpired()
|
||||
}
|
||||
}, [addNotification, isFastMode])
|
||||
}
|
||||
|
||||
function getCooldownMessage(reason: CooldownReason, resetIn: string): string {
|
||||
switch (reason) {
|
||||
case 'overloaded':
|
||||
return `Fast mode overloaded and is temporarily unavailable · resets in ${resetIn}`;
|
||||
return `Fast mode overloaded and is temporarily unavailable · resets in ${resetIn}`
|
||||
case 'rate_limit':
|
||||
return `Fast limit reached and temporarily disabled · resets in ${resetIn}`;
|
||||
return `Fast limit reached and temporarily disabled · resets in ${resetIn}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,185 +1,159 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useNotifications } from 'src/context/notifications.js';
|
||||
import { Text } from 'src/ink.js';
|
||||
import type { MCPServerConnection } from 'src/services/mcp/types.js';
|
||||
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js';
|
||||
import { detectIDEs, type IDEExtensionInstallationStatus, isJetBrainsIde, isSupportedTerminal } from 'src/utils/ide.js';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
import { useIdeConnectionStatus } from '../useIdeConnectionStatus.js';
|
||||
import type { IDESelection } from '../useIdeSelection.js';
|
||||
const MAX_IDE_HINT_SHOW_COUNT = 5;
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useNotifications } from 'src/context/notifications.js'
|
||||
import { Text } from 'src/ink.js'
|
||||
import type { MCPServerConnection } from 'src/services/mcp/types.js'
|
||||
import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
|
||||
import {
|
||||
detectIDEs,
|
||||
type IDEExtensionInstallationStatus,
|
||||
isJetBrainsIde,
|
||||
isSupportedTerminal,
|
||||
} from 'src/utils/ide.js'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
import { useIdeConnectionStatus } from '../useIdeConnectionStatus.js'
|
||||
import type { IDESelection } from '../useIdeSelection.js'
|
||||
|
||||
const MAX_IDE_HINT_SHOW_COUNT = 5
|
||||
|
||||
type Props = {
|
||||
ideInstallationStatus: IDEExtensionInstallationStatus | null;
|
||||
ideSelection: IDESelection | undefined;
|
||||
mcpClients: MCPServerConnection[];
|
||||
};
|
||||
export function useIDEStatusIndicator(t0) {
|
||||
const $ = _c(26);
|
||||
const {
|
||||
ideSelection,
|
||||
mcpClients,
|
||||
ideInstallationStatus
|
||||
} = t0;
|
||||
const {
|
||||
addNotification,
|
||||
removeNotification
|
||||
} = useNotifications();
|
||||
const {
|
||||
status: ideStatus,
|
||||
ideName
|
||||
} = useIdeConnectionStatus(mcpClients);
|
||||
const hasShownHintRef = useRef(false);
|
||||
let t1;
|
||||
if ($[0] !== ideInstallationStatus) {
|
||||
t1 = ideInstallationStatus ? isJetBrainsIde(ideInstallationStatus?.ideType) : false;
|
||||
$[0] = ideInstallationStatus;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const isJetBrains = t1;
|
||||
const showIDEInstallErrorOrJetBrainsInfo = ideInstallationStatus?.error || isJetBrains;
|
||||
const shouldShowIdeSelection = ideStatus === "connected" && (ideSelection?.filePath || ideSelection?.text && ideSelection.lineCount > 0);
|
||||
const shouldShowConnected = ideStatus === "connected" && !shouldShowIdeSelection;
|
||||
const showIDEInstallError = showIDEInstallErrorOrJetBrainsInfo && !isJetBrains && !shouldShowConnected && !shouldShowIdeSelection;
|
||||
const showJetBrainsInfo = showIDEInstallErrorOrJetBrainsInfo && isJetBrains && !shouldShowConnected && !shouldShowIdeSelection;
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[2] !== addNotification || $[3] !== ideStatus || $[4] !== removeNotification || $[5] !== showJetBrainsInfo) {
|
||||
t2 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (isSupportedTerminal() || ideStatus !== null || showJetBrainsInfo) {
|
||||
removeNotification("ide-status-hint");
|
||||
return;
|
||||
}
|
||||
if (hasShownHintRef.current || (getGlobalConfig().ideHintShownCount ?? 0) >= MAX_IDE_HINT_SHOW_COUNT) {
|
||||
return;
|
||||
}
|
||||
const timeoutId = setTimeout(_temp2, 3000, hasShownHintRef, addNotification);
|
||||
return () => clearTimeout(timeoutId);
|
||||
};
|
||||
t3 = [addNotification, removeNotification, ideStatus, showJetBrainsInfo];
|
||||
$[2] = addNotification;
|
||||
$[3] = ideStatus;
|
||||
$[4] = removeNotification;
|
||||
$[5] = showJetBrainsInfo;
|
||||
$[6] = t2;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
t3 = $[7];
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
let t4;
|
||||
let t5;
|
||||
if ($[8] !== addNotification || $[9] !== ideName || $[10] !== ideStatus || $[11] !== removeNotification || $[12] !== showIDEInstallError || $[13] !== showJetBrainsInfo) {
|
||||
t4 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (showIDEInstallError || showJetBrainsInfo || ideStatus !== "disconnected" || !ideName) {
|
||||
removeNotification("ide-status-disconnected");
|
||||
return;
|
||||
}
|
||||
addNotification({
|
||||
key: "ide-status-disconnected",
|
||||
text: `${ideName} disconnected`,
|
||||
color: "error",
|
||||
priority: "medium"
|
||||
});
|
||||
};
|
||||
t5 = [addNotification, removeNotification, ideStatus, ideName, showIDEInstallError, showJetBrainsInfo];
|
||||
$[8] = addNotification;
|
||||
$[9] = ideName;
|
||||
$[10] = ideStatus;
|
||||
$[11] = removeNotification;
|
||||
$[12] = showIDEInstallError;
|
||||
$[13] = showJetBrainsInfo;
|
||||
$[14] = t4;
|
||||
$[15] = t5;
|
||||
} else {
|
||||
t4 = $[14];
|
||||
t5 = $[15];
|
||||
}
|
||||
useEffect(t4, t5);
|
||||
let t6;
|
||||
let t7;
|
||||
if ($[16] !== addNotification || $[17] !== removeNotification || $[18] !== showJetBrainsInfo) {
|
||||
t6 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (!showJetBrainsInfo) {
|
||||
removeNotification("ide-status-jetbrains-disconnected");
|
||||
return;
|
||||
}
|
||||
addNotification({
|
||||
key: "ide-status-jetbrains-disconnected",
|
||||
text: "IDE plugin not connected \xB7 /status for info",
|
||||
priority: "medium"
|
||||
});
|
||||
};
|
||||
t7 = [addNotification, removeNotification, showJetBrainsInfo];
|
||||
$[16] = addNotification;
|
||||
$[17] = removeNotification;
|
||||
$[18] = showJetBrainsInfo;
|
||||
$[19] = t6;
|
||||
$[20] = t7;
|
||||
} else {
|
||||
t6 = $[19];
|
||||
t7 = $[20];
|
||||
}
|
||||
useEffect(t6, t7);
|
||||
let t8;
|
||||
let t9;
|
||||
if ($[21] !== addNotification || $[22] !== removeNotification || $[23] !== showIDEInstallError) {
|
||||
t8 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (!showIDEInstallError) {
|
||||
removeNotification("ide-status-install-error");
|
||||
return;
|
||||
}
|
||||
addNotification({
|
||||
key: "ide-status-install-error",
|
||||
text: "IDE extension install failed (see /status for info)",
|
||||
color: "error",
|
||||
priority: "medium"
|
||||
});
|
||||
};
|
||||
t9 = [addNotification, removeNotification, showIDEInstallError];
|
||||
$[21] = addNotification;
|
||||
$[22] = removeNotification;
|
||||
$[23] = showIDEInstallError;
|
||||
$[24] = t8;
|
||||
$[25] = t9;
|
||||
} else {
|
||||
t8 = $[24];
|
||||
t9 = $[25];
|
||||
}
|
||||
useEffect(t8, t9);
|
||||
ideInstallationStatus: IDEExtensionInstallationStatus | null
|
||||
ideSelection: IDESelection | undefined
|
||||
mcpClients: MCPServerConnection[]
|
||||
}
|
||||
function _temp2(hasShownHintRef_0, addNotification_0) {
|
||||
detectIDEs(true).then(infos => {
|
||||
const ideName_0 = infos[0]?.name;
|
||||
if (ideName_0 && !hasShownHintRef_0.current) {
|
||||
hasShownHintRef_0.current = true;
|
||||
saveGlobalConfig(_temp);
|
||||
addNotification_0({
|
||||
key: "ide-status-hint",
|
||||
jsx: <Text dimColor={true}>/ide for <Text color="ide">{ideName_0}</Text></Text>,
|
||||
priority: "low"
|
||||
});
|
||||
|
||||
export function useIDEStatusIndicator({
|
||||
ideSelection,
|
||||
mcpClients,
|
||||
ideInstallationStatus,
|
||||
}: Props): void {
|
||||
const { addNotification, removeNotification } = useNotifications()
|
||||
const { status: ideStatus, ideName } = useIdeConnectionStatus(mcpClients)
|
||||
const hasShownHintRef = useRef(false)
|
||||
|
||||
const isJetBrains = ideInstallationStatus
|
||||
? isJetBrainsIde(ideInstallationStatus?.ideType)
|
||||
: false
|
||||
const showIDEInstallErrorOrJetBrainsInfo =
|
||||
ideInstallationStatus?.error || isJetBrains
|
||||
|
||||
const shouldShowIdeSelection =
|
||||
ideStatus === 'connected' &&
|
||||
(ideSelection?.filePath ||
|
||||
(ideSelection?.text && ideSelection.lineCount > 0))
|
||||
|
||||
// Only show the connected if not showing context
|
||||
const shouldShowConnected =
|
||||
ideStatus === 'connected' && !shouldShowIdeSelection
|
||||
|
||||
const showIDEInstallError =
|
||||
showIDEInstallErrorOrJetBrainsInfo &&
|
||||
!isJetBrains &&
|
||||
!shouldShowConnected &&
|
||||
!shouldShowIdeSelection
|
||||
|
||||
const showJetBrainsInfo =
|
||||
showIDEInstallErrorOrJetBrainsInfo &&
|
||||
isJetBrains &&
|
||||
!shouldShowConnected &&
|
||||
!shouldShowIdeSelection
|
||||
|
||||
// Show the /ide command hint if running from an external terminal and found running IDE(s)
|
||||
// Delay showing hint to avoid brief flash during auto-connect startup
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (isSupportedTerminal() || ideStatus !== null || showJetBrainsInfo) {
|
||||
removeNotification('ide-status-hint')
|
||||
return
|
||||
}
|
||||
});
|
||||
}
|
||||
function _temp(current) {
|
||||
return {
|
||||
...current,
|
||||
ideHintShownCount: (current.ideHintShownCount ?? 0) + 1
|
||||
};
|
||||
// Wait a bit to let auto-connect happen first, avoiding brief hint flash
|
||||
if (
|
||||
hasShownHintRef.current ||
|
||||
(getGlobalConfig().ideHintShownCount ?? 0) >= MAX_IDE_HINT_SHOW_COUNT
|
||||
) {
|
||||
return
|
||||
}
|
||||
const timeoutId = setTimeout(
|
||||
(hasShownHintRef, addNotification) => {
|
||||
void detectIDEs(true).then(infos => {
|
||||
const ideName = infos[0]?.name
|
||||
if (ideName && !hasShownHintRef.current) {
|
||||
hasShownHintRef.current = true
|
||||
saveGlobalConfig(current => ({
|
||||
...current,
|
||||
ideHintShownCount: (current.ideHintShownCount ?? 0) + 1,
|
||||
}))
|
||||
addNotification({
|
||||
key: 'ide-status-hint',
|
||||
jsx: (
|
||||
<Text dimColor>
|
||||
/ide for <Text color="ide">{ideName}</Text>
|
||||
</Text>
|
||||
),
|
||||
priority: 'low',
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
3000,
|
||||
hasShownHintRef,
|
||||
addNotification,
|
||||
)
|
||||
return () => clearTimeout(timeoutId)
|
||||
}, [addNotification, removeNotification, ideStatus, showJetBrainsInfo])
|
||||
|
||||
// Show IDE disconnected/failed notification when status is disconnected
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (
|
||||
showIDEInstallError ||
|
||||
showJetBrainsInfo ||
|
||||
ideStatus !== 'disconnected' ||
|
||||
!ideName
|
||||
) {
|
||||
removeNotification('ide-status-disconnected')
|
||||
return
|
||||
}
|
||||
addNotification({
|
||||
key: 'ide-status-disconnected',
|
||||
text: `${ideName} disconnected`,
|
||||
color: 'error',
|
||||
priority: 'medium',
|
||||
})
|
||||
}, [
|
||||
addNotification,
|
||||
removeNotification,
|
||||
ideStatus,
|
||||
ideName,
|
||||
showIDEInstallError,
|
||||
showJetBrainsInfo,
|
||||
])
|
||||
|
||||
// Show JetBrains plugin not connected hint
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (!showJetBrainsInfo) {
|
||||
removeNotification('ide-status-jetbrains-disconnected')
|
||||
return
|
||||
}
|
||||
addNotification({
|
||||
key: 'ide-status-jetbrains-disconnected',
|
||||
text: 'IDE plugin not connected · /status for info',
|
||||
priority: 'medium',
|
||||
})
|
||||
}, [addNotification, removeNotification, showJetBrainsInfo])
|
||||
|
||||
// Show IDE install error
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (!showIDEInstallError) {
|
||||
removeNotification('ide-status-install-error')
|
||||
return
|
||||
}
|
||||
addNotification({
|
||||
key: 'ide-status-install-error',
|
||||
text: 'IDE extension install failed (see /status for info)',
|
||||
color: 'error',
|
||||
priority: 'medium',
|
||||
})
|
||||
}, [addNotification, removeNotification, showIDEInstallError])
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import { checkInstall } from 'src/utils/nativeInstaller/index.js';
|
||||
import { useStartupNotification } from './useStartupNotification.js';
|
||||
export function useInstallMessages() {
|
||||
useStartupNotification(_temp2);
|
||||
}
|
||||
async function _temp2() {
|
||||
const messages = await checkInstall();
|
||||
return messages.map(_temp);
|
||||
}
|
||||
function _temp(message, index) {
|
||||
let priority = "low";
|
||||
if (message.type === "error" || message.userActionRequired) {
|
||||
priority = "high";
|
||||
} else {
|
||||
if (message.type === "path" || message.type === "alias") {
|
||||
priority = "medium";
|
||||
}
|
||||
}
|
||||
return {
|
||||
key: `install-message-${index}-${message.type}`,
|
||||
text: message.message,
|
||||
priority,
|
||||
color: message.type === "error" ? "error" : "warning"
|
||||
};
|
||||
import { checkInstall } from 'src/utils/nativeInstaller/index.js'
|
||||
import { useStartupNotification } from './useStartupNotification.js'
|
||||
|
||||
export function useInstallMessages(): void {
|
||||
useStartupNotification(async () => {
|
||||
const messages = await checkInstall()
|
||||
return messages.map((message, index) => {
|
||||
let priority: 'low' | 'medium' | 'high' | 'immediate' = 'low'
|
||||
if (message.type === 'error' || message.userActionRequired) {
|
||||
priority = 'high'
|
||||
} else if (message.type === 'path' || message.type === 'alias') {
|
||||
priority = 'medium'
|
||||
}
|
||||
return {
|
||||
key: `install-message-${index}-${message.type}`,
|
||||
text: message.message,
|
||||
priority,
|
||||
color: message.type === 'error' ? 'error' : 'warning',
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useInterval } from 'usehooks-ts';
|
||||
import { getIsRemoteMode, getIsScrollDraining } from '../../bootstrap/state.js';
|
||||
import { useNotifications } from '../../context/notifications.js';
|
||||
import { Text } from '../../ink.js';
|
||||
import { getInitializationStatus, getLspServerManager } from '../../services/lsp/manager.js';
|
||||
import { useSetAppState } from '../../state/AppState.js';
|
||||
import { logForDebugging } from '../../utils/debug.js';
|
||||
import { isEnvTruthy } from '../../utils/envUtils.js';
|
||||
const LSP_POLL_INTERVAL_MS = 5000;
|
||||
import * as React from 'react'
|
||||
import { useInterval } from 'usehooks-ts'
|
||||
import { getIsRemoteMode, getIsScrollDraining } from '../../bootstrap/state.js'
|
||||
import { useNotifications } from '../../context/notifications.js'
|
||||
import { Text } from '../../ink.js'
|
||||
import {
|
||||
getInitializationStatus,
|
||||
getLspServerManager,
|
||||
} from '../../services/lsp/manager.js'
|
||||
import { useSetAppState } from '../../state/AppState.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { isEnvTruthy } from '../../utils/envUtils.js'
|
||||
|
||||
const LSP_POLL_INTERVAL_MS = 5000
|
||||
|
||||
/**
|
||||
* Hook that polls LSP status and shows a notification when:
|
||||
@@ -19,124 +22,120 @@ const LSP_POLL_INTERVAL_MS = 5000;
|
||||
*
|
||||
* Only active when ENABLE_LSP_TOOL is set.
|
||||
*/
|
||||
export function useLspInitializationNotification() {
|
||||
const $ = _c(10);
|
||||
const {
|
||||
addNotification
|
||||
} = useNotifications();
|
||||
const setAppState = useSetAppState();
|
||||
const [shouldPoll, setShouldPoll] = React.useState(_temp);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = new Set();
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const notifiedErrorsRef = React.useRef(t0);
|
||||
let t1;
|
||||
if ($[1] !== addNotification || $[2] !== setAppState) {
|
||||
t1 = (source, errorMessage) => {
|
||||
const errorKey = `${source}:${errorMessage}`;
|
||||
export function useLspInitializationNotification(): void {
|
||||
const { addNotification } = useNotifications()
|
||||
const setAppState = useSetAppState()
|
||||
// Lazy initializer — eager form re-evaluates isEnvTruthy on every REPL
|
||||
// render (the arg expression runs even though useState ignores it after
|
||||
// mount). Showed up as 7.2s isEnvTruthy self-time during PageUp spam
|
||||
// after #24498 swapped cheap !!process.env.X for isEnvTruthy().
|
||||
const [shouldPoll, setShouldPoll] = React.useState(() =>
|
||||
isEnvTruthy("true"),
|
||||
)
|
||||
// Track which errors we've already notified about to avoid duplicates
|
||||
const notifiedErrorsRef = React.useRef<Set<string>>(new Set())
|
||||
|
||||
const addError = React.useCallback(
|
||||
(source: string, errorMessage: string) => {
|
||||
const errorKey = `${source}:${errorMessage}`
|
||||
if (notifiedErrorsRef.current.has(errorKey)) {
|
||||
return;
|
||||
return // Already notified
|
||||
}
|
||||
notifiedErrorsRef.current.add(errorKey);
|
||||
logForDebugging(`LSP error: ${source} - ${errorMessage}`);
|
||||
notifiedErrorsRef.current.add(errorKey)
|
||||
|
||||
logForDebugging(`LSP error: ${source} - ${errorMessage}`)
|
||||
|
||||
// Add error to appState.plugins.errors
|
||||
setAppState(prev => {
|
||||
const existingKeys = new Set(prev.plugins.errors.map(_temp2));
|
||||
const stateErrorKey = `generic-error:${source}:${errorMessage}`;
|
||||
// Check if this error already exists to avoid duplicates
|
||||
const existingKeys = new Set(
|
||||
prev.plugins.errors.map(e => {
|
||||
if (e.type === 'generic-error') {
|
||||
return `generic-error:${e.source}:${e.error}`
|
||||
}
|
||||
return `${e.type}:${e.source}`
|
||||
}),
|
||||
)
|
||||
|
||||
const stateErrorKey = `generic-error:${source}:${errorMessage}`
|
||||
if (existingKeys.has(stateErrorKey)) {
|
||||
return prev;
|
||||
return prev
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
plugins: {
|
||||
...prev.plugins,
|
||||
errors: [...prev.plugins.errors, {
|
||||
type: "generic-error" as const,
|
||||
source,
|
||||
error: errorMessage
|
||||
}]
|
||||
}
|
||||
};
|
||||
});
|
||||
const displayName = source.startsWith("plugin:") ? source.split(":")[1] ?? source : source;
|
||||
errors: [
|
||||
...prev.plugins.errors,
|
||||
{
|
||||
type: 'generic-error' as const,
|
||||
source,
|
||||
error: errorMessage,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Show notification - extract plugin name from source like "plugin:typescript-lsp:typescript"
|
||||
const displayName = source.startsWith('plugin:')
|
||||
? (source.split(':')[1] ?? source)
|
||||
: source
|
||||
|
||||
addNotification({
|
||||
key: `lsp-error-${source}`,
|
||||
jsx: <><Text color="error">LSP for {displayName} failed</Text><Text dimColor={true}> · /plugin for details</Text></>,
|
||||
priority: "medium",
|
||||
timeoutMs: 8000
|
||||
});
|
||||
};
|
||||
$[1] = addNotification;
|
||||
$[2] = setAppState;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const addError = t1;
|
||||
let t2;
|
||||
if ($[4] !== addError) {
|
||||
t2 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (getIsScrollDraining()) {
|
||||
return;
|
||||
}
|
||||
const status = getInitializationStatus();
|
||||
if (status.status === "failed") {
|
||||
addError("lsp-manager", status.error.message);
|
||||
setShouldPoll(false);
|
||||
return;
|
||||
}
|
||||
if (status.status === "pending" || status.status === "not-started") {
|
||||
return;
|
||||
}
|
||||
const manager = getLspServerManager();
|
||||
if (manager) {
|
||||
const servers = manager.getAllServers();
|
||||
for (const [serverName, server] of servers) {
|
||||
if (server.state === "error" && server.lastError) {
|
||||
addError(serverName, server.lastError.message);
|
||||
}
|
||||
jsx: (
|
||||
<>
|
||||
<Text color="error">LSP for {displayName} failed</Text>
|
||||
<Text dimColor> · /plugin for details</Text>
|
||||
</>
|
||||
),
|
||||
priority: 'medium',
|
||||
timeoutMs: 8000,
|
||||
})
|
||||
},
|
||||
[addNotification, setAppState],
|
||||
)
|
||||
|
||||
const poll = React.useCallback(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
// Skip during scroll drain — iterating all LSP servers + setAppState
|
||||
// competes for the event loop with scroll frames. Next interval picks up.
|
||||
if (getIsScrollDraining()) return
|
||||
|
||||
const status = getInitializationStatus()
|
||||
|
||||
// Check manager initialization status
|
||||
if (status.status === 'failed') {
|
||||
addError('lsp-manager', status.error.message)
|
||||
setShouldPoll(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (status.status === 'pending' || status.status === 'not-started') {
|
||||
// Still initializing, continue polling
|
||||
return
|
||||
}
|
||||
|
||||
// Manager initialized successfully - check for server errors
|
||||
const manager = getLspServerManager()
|
||||
if (manager) {
|
||||
const servers = manager.getAllServers()
|
||||
for (const [serverName, server] of servers) {
|
||||
if (server.state === 'error' && server.lastError) {
|
||||
addError(serverName, server.lastError.message)
|
||||
}
|
||||
}
|
||||
};
|
||||
$[4] = addError;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
const poll = t2;
|
||||
useInterval(poll, shouldPoll ? LSP_POLL_INTERVAL_MS : null);
|
||||
let t3;
|
||||
let t4;
|
||||
if ($[6] !== poll || $[7] !== shouldPoll) {
|
||||
t3 = () => {
|
||||
if (getIsRemoteMode() || !shouldPoll) {
|
||||
return;
|
||||
}
|
||||
poll();
|
||||
};
|
||||
t4 = [poll, shouldPoll];
|
||||
$[6] = poll;
|
||||
$[7] = shouldPoll;
|
||||
$[8] = t3;
|
||||
$[9] = t4;
|
||||
} else {
|
||||
t3 = $[8];
|
||||
t4 = $[9];
|
||||
}
|
||||
React.useEffect(t3, t4);
|
||||
}
|
||||
function _temp2(e) {
|
||||
if (e.type === "generic-error") {
|
||||
return `generic-error:${e.source}:${e.error}`;
|
||||
}
|
||||
return `${e.type}:${e.source}`;
|
||||
}
|
||||
function _temp() {
|
||||
return isEnvTruthy("true");
|
||||
}
|
||||
// Continue polling to detect future server errors
|
||||
}, [addError])
|
||||
|
||||
useInterval(poll, shouldPoll ? LSP_POLL_INTERVAL_MS : null)
|
||||
|
||||
// Initial poll on mount
|
||||
React.useEffect(() => {
|
||||
if (getIsRemoteMode() || !shouldPoll) return
|
||||
poll()
|
||||
}, [poll, shouldPoll])
|
||||
}
|
||||
|
||||
@@ -1,87 +1,126 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useNotifications } from 'src/context/notifications.js';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
import { Text } from '../../ink.js';
|
||||
import { hasClaudeAiMcpEverConnected } from '../../services/mcp/claudeai.js';
|
||||
import type { MCPServerConnection } from '../../services/mcp/types.js';
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { useNotifications } from 'src/context/notifications.js'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
import { Text } from '../../ink.js'
|
||||
import { hasClaudeAiMcpEverConnected } from '../../services/mcp/claudeai.js'
|
||||
import type { MCPServerConnection } from '../../services/mcp/types.js'
|
||||
|
||||
type Props = {
|
||||
mcpClients?: MCPServerConnection[];
|
||||
};
|
||||
const EMPTY_MCP_CLIENTS: MCPServerConnection[] = [];
|
||||
export function useMcpConnectivityStatus(t0) {
|
||||
const $ = _c(4);
|
||||
const {
|
||||
mcpClients: t1
|
||||
} = t0;
|
||||
const mcpClients = t1 === undefined ? EMPTY_MCP_CLIENTS : t1;
|
||||
const {
|
||||
addNotification
|
||||
} = useNotifications();
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[0] !== addNotification || $[1] !== mcpClients) {
|
||||
t2 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
const failedLocalClients = mcpClients.filter(_temp);
|
||||
const failedClaudeAiClients = mcpClients.filter(_temp2);
|
||||
const needsAuthLocalServers = mcpClients.filter(_temp3);
|
||||
const needsAuthClaudeAiServers = mcpClients.filter(_temp4);
|
||||
if (failedLocalClients.length === 0 && failedClaudeAiClients.length === 0 && needsAuthLocalServers.length === 0 && needsAuthClaudeAiServers.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (failedLocalClients.length > 0) {
|
||||
addNotification({
|
||||
key: "mcp-failed",
|
||||
jsx: <><Text color="error">{failedLocalClients.length} MCP{" "}{failedLocalClients.length === 1 ? "server" : "servers"} failed</Text><Text dimColor={true}> · /mcp</Text></>,
|
||||
priority: "medium"
|
||||
});
|
||||
}
|
||||
if (failedClaudeAiClients.length > 0) {
|
||||
addNotification({
|
||||
key: "mcp-claudeai-failed",
|
||||
jsx: <><Text color="error">{failedClaudeAiClients.length} claude.ai{" "}{failedClaudeAiClients.length === 1 ? "connector" : "connectors"}{" "}unavailable</Text><Text dimColor={true}> · /mcp</Text></>,
|
||||
priority: "medium"
|
||||
});
|
||||
}
|
||||
if (needsAuthLocalServers.length > 0) {
|
||||
addNotification({
|
||||
key: "mcp-needs-auth",
|
||||
jsx: <><Text color="warning">{needsAuthLocalServers.length} MCP{" "}{needsAuthLocalServers.length === 1 ? "server needs" : "servers need"}{" "}auth</Text><Text dimColor={true}> · /mcp</Text></>,
|
||||
priority: "medium"
|
||||
});
|
||||
}
|
||||
if (needsAuthClaudeAiServers.length > 0) {
|
||||
addNotification({
|
||||
key: "mcp-claudeai-needs-auth",
|
||||
jsx: <><Text color="warning">{needsAuthClaudeAiServers.length} claude.ai{" "}{needsAuthClaudeAiServers.length === 1 ? "connector needs" : "connectors need"}{" "}auth</Text><Text dimColor={true}> · /mcp</Text></>,
|
||||
priority: "medium"
|
||||
});
|
||||
}
|
||||
};
|
||||
t3 = [addNotification, mcpClients];
|
||||
$[0] = addNotification;
|
||||
$[1] = mcpClients;
|
||||
$[2] = t2;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
t3 = $[3];
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
mcpClients?: MCPServerConnection[]
|
||||
}
|
||||
function _temp4(client_2) {
|
||||
return client_2.type === "needs-auth" && client_2.config.type === "claudeai-proxy" && hasClaudeAiMcpEverConnected(client_2.name);
|
||||
}
|
||||
function _temp3(client_1) {
|
||||
return client_1.type === "needs-auth" && client_1.config.type !== "claudeai-proxy";
|
||||
}
|
||||
function _temp2(client_0) {
|
||||
return client_0.type === "failed" && client_0.config.type === "claudeai-proxy" && hasClaudeAiMcpEverConnected(client_0.name);
|
||||
}
|
||||
function _temp(client) {
|
||||
return client.type === "failed" && client.config.type !== "sse-ide" && client.config.type !== "ws-ide" && client.config.type !== "claudeai-proxy";
|
||||
|
||||
const EMPTY_MCP_CLIENTS: MCPServerConnection[] = []
|
||||
|
||||
export function useMcpConnectivityStatus({
|
||||
mcpClients = EMPTY_MCP_CLIENTS,
|
||||
}: Props): void {
|
||||
const { addNotification } = useNotifications()
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
const failedLocalClients = mcpClients.filter(
|
||||
client =>
|
||||
client.type === 'failed' &&
|
||||
client.config.type !== 'sse-ide' &&
|
||||
client.config.type !== 'ws-ide' &&
|
||||
client.config.type !== 'claudeai-proxy',
|
||||
)
|
||||
// claude.ai failures get a separate notification: they almost always indicate
|
||||
// a toolbox-service outage (shared auth backend), not a local config issue.
|
||||
// Only flag connectors that have previously connected successfully — an
|
||||
// org-configured connector that's been needs-auth since it appeared is one
|
||||
// the user has ignored and shouldn't nag about; one that was working
|
||||
// yesterday and is now failed is a state change worth surfacing.
|
||||
const failedClaudeAiClients = mcpClients.filter(
|
||||
client =>
|
||||
client.type === 'failed' &&
|
||||
client.config.type === 'claudeai-proxy' &&
|
||||
hasClaudeAiMcpEverConnected(client.name),
|
||||
)
|
||||
const needsAuthLocalServers = mcpClients.filter(
|
||||
client =>
|
||||
client.type === 'needs-auth' && client.config.type !== 'claudeai-proxy',
|
||||
)
|
||||
const needsAuthClaudeAiServers = mcpClients.filter(
|
||||
client =>
|
||||
client.type === 'needs-auth' &&
|
||||
client.config.type === 'claudeai-proxy' &&
|
||||
hasClaudeAiMcpEverConnected(client.name),
|
||||
)
|
||||
if (
|
||||
failedLocalClients.length === 0 &&
|
||||
failedClaudeAiClients.length === 0 &&
|
||||
needsAuthLocalServers.length === 0 &&
|
||||
needsAuthClaudeAiServers.length === 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (failedLocalClients.length > 0) {
|
||||
addNotification({
|
||||
key: 'mcp-failed',
|
||||
jsx: (
|
||||
<>
|
||||
<Text color="error">
|
||||
{failedLocalClients.length} MCP{' '}
|
||||
{failedLocalClients.length === 1 ? 'server' : 'servers'} failed
|
||||
</Text>
|
||||
<Text dimColor> · /mcp</Text>
|
||||
</>
|
||||
),
|
||||
priority: 'medium',
|
||||
})
|
||||
}
|
||||
if (failedClaudeAiClients.length > 0) {
|
||||
addNotification({
|
||||
key: 'mcp-claudeai-failed',
|
||||
jsx: (
|
||||
<>
|
||||
<Text color="error">
|
||||
{failedClaudeAiClients.length} claude.ai{' '}
|
||||
{failedClaudeAiClients.length === 1 ? 'connector' : 'connectors'}{' '}
|
||||
unavailable
|
||||
</Text>
|
||||
<Text dimColor> · /mcp</Text>
|
||||
</>
|
||||
),
|
||||
priority: 'medium',
|
||||
})
|
||||
}
|
||||
if (needsAuthLocalServers.length > 0) {
|
||||
addNotification({
|
||||
key: 'mcp-needs-auth',
|
||||
jsx: (
|
||||
<>
|
||||
<Text color="warning">
|
||||
{needsAuthLocalServers.length} MCP{' '}
|
||||
{needsAuthLocalServers.length === 1
|
||||
? 'server needs'
|
||||
: 'servers need'}{' '}
|
||||
auth
|
||||
</Text>
|
||||
<Text dimColor> · /mcp</Text>
|
||||
</>
|
||||
),
|
||||
priority: 'medium',
|
||||
})
|
||||
}
|
||||
if (needsAuthClaudeAiServers.length > 0) {
|
||||
addNotification({
|
||||
key: 'mcp-claudeai-needs-auth',
|
||||
jsx: (
|
||||
<>
|
||||
<Text color="warning">
|
||||
{needsAuthClaudeAiServers.length} claude.ai{' '}
|
||||
{needsAuthClaudeAiServers.length === 1
|
||||
? 'connector needs'
|
||||
: 'connectors need'}{' '}
|
||||
auth
|
||||
</Text>
|
||||
<Text dimColor> · /mcp</Text>
|
||||
</>
|
||||
),
|
||||
priority: 'medium',
|
||||
})
|
||||
}
|
||||
}, [addNotification, mcpClients])
|
||||
}
|
||||
|
||||
@@ -1,51 +1,53 @@
|
||||
import type { Notification } from 'src/context/notifications.js';
|
||||
import { type GlobalConfig, getGlobalConfig } from 'src/utils/config.js';
|
||||
import { useStartupNotification } from './useStartupNotification.js';
|
||||
import type { Notification } from 'src/context/notifications.js'
|
||||
import { type GlobalConfig, getGlobalConfig } from 'src/utils/config.js'
|
||||
import { useStartupNotification } from './useStartupNotification.js'
|
||||
|
||||
// Shows a one-time notification right after a model migration writes its
|
||||
// timestamp to config. Each entry reads its own timestamp field(s) and emits
|
||||
// a notification if the write happened within the last 3s (i.e. this launch).
|
||||
// Future model migrations: add an entry to MIGRATIONS below.
|
||||
const MIGRATIONS: ((c: GlobalConfig) => Notification | undefined)[] = [
|
||||
// Sonnet 4.5 → 4.6 (pro/max/team premium)
|
||||
c => {
|
||||
if (!recent(c.sonnet45To46MigrationTimestamp)) return;
|
||||
return {
|
||||
key: 'sonnet-46-update',
|
||||
text: 'Model updated to Sonnet 4.6',
|
||||
color: 'suggestion',
|
||||
priority: 'high',
|
||||
timeoutMs: 3000
|
||||
};
|
||||
},
|
||||
// Opus Pro → default, or pinned 4.0/4.1 → opus alias. Both land on the
|
||||
// current Opus default (4.6 for 1P).
|
||||
c => {
|
||||
const isLegacyRemap = Boolean(c.legacyOpusMigrationTimestamp);
|
||||
const ts = c.legacyOpusMigrationTimestamp ?? c.opusProMigrationTimestamp;
|
||||
if (!recent(ts)) return;
|
||||
return {
|
||||
key: 'opus-pro-update',
|
||||
text: isLegacyRemap ? 'Model updated to Opus 4.6 · Set CLAUDE_CODE_DISABLE_LEGACY_MODEL_REMAP=1 to opt out' : 'Model updated to Opus 4.6',
|
||||
color: 'suggestion',
|
||||
priority: 'high',
|
||||
timeoutMs: isLegacyRemap ? 8000 : 3000
|
||||
};
|
||||
}];
|
||||
export function useModelMigrationNotifications() {
|
||||
useStartupNotification(_temp);
|
||||
}
|
||||
function _temp() {
|
||||
const config = getGlobalConfig();
|
||||
const notifs = [];
|
||||
for (const migration of MIGRATIONS) {
|
||||
const notif = migration(config);
|
||||
if (notif) {
|
||||
notifs.push(notif);
|
||||
// Sonnet 4.5 → 4.6 (pro/max/team premium)
|
||||
c => {
|
||||
if (!recent(c.sonnet45To46MigrationTimestamp)) return
|
||||
return {
|
||||
key: 'sonnet-46-update',
|
||||
text: 'Model updated to Sonnet 4.6',
|
||||
color: 'suggestion',
|
||||
priority: 'high',
|
||||
timeoutMs: 3000,
|
||||
}
|
||||
}
|
||||
return notifs.length > 0 ? notifs : null;
|
||||
},
|
||||
// Opus Pro → default, or pinned 4.0/4.1 → opus alias. Both land on the
|
||||
// current Opus default (4.6 for 1P).
|
||||
c => {
|
||||
const isLegacyRemap = Boolean(c.legacyOpusMigrationTimestamp)
|
||||
const ts = c.legacyOpusMigrationTimestamp ?? c.opusProMigrationTimestamp
|
||||
if (!recent(ts)) return
|
||||
return {
|
||||
key: 'opus-pro-update',
|
||||
text: isLegacyRemap
|
||||
? 'Model updated to Opus 4.6 · Set CLAUDE_CODE_DISABLE_LEGACY_MODEL_REMAP=1 to opt out'
|
||||
: 'Model updated to Opus 4.6',
|
||||
color: 'suggestion',
|
||||
priority: 'high',
|
||||
timeoutMs: isLegacyRemap ? 8000 : 3000,
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
export function useModelMigrationNotifications(): void {
|
||||
useStartupNotification(() => {
|
||||
const config = getGlobalConfig()
|
||||
const notifs: Notification[] = []
|
||||
for (const migration of MIGRATIONS) {
|
||||
const notif = migration(config)
|
||||
if (notif) notifs.push(notif)
|
||||
}
|
||||
return notifs.length > 0 ? notifs : null
|
||||
})
|
||||
}
|
||||
|
||||
function recent(ts: number | undefined): boolean {
|
||||
return ts !== undefined && Date.now() - ts < 3000;
|
||||
return ts !== undefined && Date.now() - ts < 3000
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import { isInBundledMode } from 'src/utils/bundledMode.js';
|
||||
import { getCurrentInstallationType } from 'src/utils/doctorDiagnostic.js';
|
||||
import { isEnvTruthy } from 'src/utils/envUtils.js';
|
||||
import { useStartupNotification } from './useStartupNotification.js';
|
||||
const NPM_DEPRECATION_MESSAGE = '';
|
||||
// const NPM_DEPRECATION_MESSAGE = 'Claude Code has switched from npm to native installer. Run `claude install` or see https://docs.anthropic.com/en/docs/claude-code/getting-started for more options.';
|
||||
export function useNpmDeprecationNotification() {
|
||||
useStartupNotification(_temp);
|
||||
}
|
||||
async function _temp() {
|
||||
if (isInBundledMode() || isEnvTruthy(process.env.DISABLE_INSTALLATION_CHECKS)) {
|
||||
return null;
|
||||
}
|
||||
const installationType = await getCurrentInstallationType();
|
||||
if (installationType === "development") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timeoutMs: 15000,
|
||||
key: "npm-deprecation-warning",
|
||||
text: NPM_DEPRECATION_MESSAGE,
|
||||
color: "warning",
|
||||
priority: "high"
|
||||
};
|
||||
import { isInBundledMode } from 'src/utils/bundledMode.js'
|
||||
import { getCurrentInstallationType } from 'src/utils/doctorDiagnostic.js'
|
||||
import { isEnvTruthy } from 'src/utils/envUtils.js'
|
||||
import { useStartupNotification } from './useStartupNotification.js'
|
||||
|
||||
const NPM_DEPRECATION_MESSAGE =
|
||||
'Claude Code has switched from npm to native installer. Run `claude install` or see https://docs.anthropic.com/en/docs/claude-code/getting-started for more options.'
|
||||
|
||||
export function useNpmDeprecationNotification(): void {
|
||||
useStartupNotification(async () => {
|
||||
if (
|
||||
isInBundledMode() ||
|
||||
isEnvTruthy(process.env.DISABLE_INSTALLATION_CHECKS)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const installationType = await getCurrentInstallationType()
|
||||
if (installationType === 'development') return null
|
||||
return {
|
||||
timeoutMs: 15000,
|
||||
key: 'npm-deprecation-warning',
|
||||
text: NPM_DEPRECATION_MESSAGE,
|
||||
color: 'warning',
|
||||
priority: 'high',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,82 +1,67 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
import { useNotifications } from '../../context/notifications.js';
|
||||
import { Text } from '../../ink.js';
|
||||
import { logForDebugging } from '../../utils/debug.js';
|
||||
import { onPluginsAutoUpdated } from '../../utils/plugins/pluginAutoupdate.js';
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
import { useNotifications } from '../../context/notifications.js'
|
||||
import { Text } from '../../ink.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { onPluginsAutoUpdated } from '../../utils/plugins/pluginAutoupdate.js'
|
||||
|
||||
/**
|
||||
* Hook that displays a notification when plugins have been auto-updated.
|
||||
* The notification tells the user to run /reload-plugins to apply the updates.
|
||||
*/
|
||||
export function usePluginAutoupdateNotification() {
|
||||
const $ = _c(7);
|
||||
const {
|
||||
addNotification
|
||||
} = useNotifications();
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = [];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const [updatedPlugins, setUpdatedPlugins] = useState(t0);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
const unsubscribe = onPluginsAutoUpdated(plugins => {
|
||||
logForDebugging(`Plugin autoupdate notification: ${plugins.length} plugin(s) updated`);
|
||||
setUpdatedPlugins(plugins);
|
||||
});
|
||||
return unsubscribe;
|
||||
};
|
||||
t2 = [];
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
let t4;
|
||||
if ($[3] !== addNotification || $[4] !== updatedPlugins) {
|
||||
t3 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (updatedPlugins.length === 0) {
|
||||
return;
|
||||
}
|
||||
const pluginNames = updatedPlugins.map(_temp);
|
||||
const displayNames = pluginNames.length <= 2 ? pluginNames.join(" and ") : `${pluginNames.length} plugins`;
|
||||
addNotification({
|
||||
key: "plugin-autoupdate-restart",
|
||||
jsx: <><Text color="success">{pluginNames.length === 1 ? "Plugin" : "Plugins"} updated:{" "}{displayNames}</Text><Text dimColor={true}> · Run /reload-plugins to apply</Text></>,
|
||||
priority: "low",
|
||||
timeoutMs: 10000
|
||||
});
|
||||
logForDebugging(`Showing plugin autoupdate notification for: ${pluginNames.join(", ")}`);
|
||||
};
|
||||
t4 = [updatedPlugins, addNotification];
|
||||
$[3] = addNotification;
|
||||
$[4] = updatedPlugins;
|
||||
$[5] = t3;
|
||||
$[6] = t4;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
t4 = $[6];
|
||||
}
|
||||
useEffect(t3, t4);
|
||||
}
|
||||
function _temp(id) {
|
||||
const atIndex = id.indexOf("@");
|
||||
return atIndex > 0 ? id.substring(0, atIndex) : id;
|
||||
export function usePluginAutoupdateNotification(): void {
|
||||
const { addNotification } = useNotifications()
|
||||
const [updatedPlugins, setUpdatedPlugins] = useState<string[]>([])
|
||||
|
||||
// Register for autoupdate notifications
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
const unsubscribe = onPluginsAutoUpdated(plugins => {
|
||||
logForDebugging(
|
||||
`Plugin autoupdate notification: ${plugins.length} plugin(s) updated`,
|
||||
)
|
||||
setUpdatedPlugins(plugins)
|
||||
})
|
||||
|
||||
return unsubscribe
|
||||
}, [])
|
||||
|
||||
// Show notification when plugins are updated
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (updatedPlugins.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract plugin names from plugin IDs (format: "name@marketplace")
|
||||
const pluginNames = updatedPlugins.map(id => {
|
||||
const atIndex = id.indexOf('@')
|
||||
return atIndex > 0 ? id.substring(0, atIndex) : id
|
||||
})
|
||||
|
||||
const displayNames =
|
||||
pluginNames.length <= 2
|
||||
? pluginNames.join(' and ')
|
||||
: `${pluginNames.length} plugins`
|
||||
|
||||
addNotification({
|
||||
key: 'plugin-autoupdate-restart',
|
||||
jsx: (
|
||||
<>
|
||||
<Text color="success">
|
||||
{pluginNames.length === 1 ? 'Plugin' : 'Plugins'} updated:{' '}
|
||||
{displayNames}
|
||||
</Text>
|
||||
<Text dimColor> · Run /reload-plugins to apply</Text>
|
||||
</>
|
||||
),
|
||||
priority: 'low',
|
||||
timeoutMs: 10000,
|
||||
})
|
||||
|
||||
logForDebugging(
|
||||
`Showing plugin autoupdate notification for: ${pluginNames.join(', ')}`,
|
||||
)
|
||||
}, [updatedPlugins, addNotification])
|
||||
}
|
||||
|
||||
@@ -1,127 +1,80 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
import { useNotifications } from '../../context/notifications.js';
|
||||
import { Text } from '../../ink.js';
|
||||
import { useAppState } from '../../state/AppState.js';
|
||||
import { logForDebugging } from '../../utils/debug.js';
|
||||
import { plural } from '../../utils/stringUtils.js';
|
||||
export function usePluginInstallationStatus() {
|
||||
const $ = _c(20);
|
||||
const {
|
||||
addNotification
|
||||
} = useNotifications();
|
||||
const installationStatus = useAppState(_temp);
|
||||
let t0;
|
||||
bb0: {
|
||||
if (!installationStatus) {
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = {
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
import { useNotifications } from '../../context/notifications.js'
|
||||
import { Text } from '../../ink.js'
|
||||
import { useAppState } from '../../state/AppState.js'
|
||||
import { logForDebugging } from '../../utils/debug.js'
|
||||
import { plural } from '../../utils/stringUtils.js'
|
||||
|
||||
export function usePluginInstallationStatus(): void {
|
||||
const { addNotification } = useNotifications()
|
||||
const installationStatus = useAppState(s => s.plugins.installationStatus)
|
||||
|
||||
// Memoize the failed counts to prevent unnecessary effect triggers
|
||||
const { totalFailed, failedMarketplacesCount, failedPluginsCount } =
|
||||
useMemo(() => {
|
||||
if (!installationStatus) {
|
||||
return {
|
||||
totalFailed: 0,
|
||||
failedMarketplacesCount: 0,
|
||||
failedPluginsCount: 0
|
||||
};
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
failedPluginsCount: 0,
|
||||
}
|
||||
}
|
||||
t0 = t1;
|
||||
break bb0;
|
||||
}
|
||||
let t1;
|
||||
if ($[1] !== installationStatus.marketplaces) {
|
||||
t1 = installationStatus.marketplaces.filter(_temp2);
|
||||
$[1] = installationStatus.marketplaces;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
const failedMarketplaces = t1;
|
||||
let t2;
|
||||
if ($[3] !== installationStatus.plugins) {
|
||||
t2 = installationStatus.plugins.filter(_temp3);
|
||||
$[3] = installationStatus.plugins;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
const failedPlugins = t2;
|
||||
const t3 = failedMarketplaces.length + failedPlugins.length;
|
||||
let t4;
|
||||
if ($[5] !== failedMarketplaces.length || $[6] !== failedPlugins.length || $[7] !== t3) {
|
||||
t4 = {
|
||||
totalFailed: t3,
|
||||
|
||||
const failedMarketplaces = installationStatus.marketplaces.filter(
|
||||
m => m.status === 'failed',
|
||||
)
|
||||
const failedPlugins = installationStatus.plugins.filter(
|
||||
p => p.status === 'failed',
|
||||
)
|
||||
|
||||
return {
|
||||
totalFailed: failedMarketplaces.length + failedPlugins.length,
|
||||
failedMarketplacesCount: failedMarketplaces.length,
|
||||
failedPluginsCount: failedPlugins.length
|
||||
};
|
||||
$[5] = failedMarketplaces.length;
|
||||
$[6] = failedPlugins.length;
|
||||
$[7] = t3;
|
||||
$[8] = t4;
|
||||
} else {
|
||||
t4 = $[8];
|
||||
failedPluginsCount: failedPlugins.length,
|
||||
}
|
||||
}, [installationStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (!installationStatus) {
|
||||
logForDebugging('No installation status to monitor')
|
||||
return
|
||||
}
|
||||
t0 = t4;
|
||||
}
|
||||
const {
|
||||
|
||||
if (totalFailed === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
logForDebugging(
|
||||
`Plugin installation status: ${failedMarketplacesCount} failed marketplaces, ${failedPluginsCount} failed plugins`,
|
||||
)
|
||||
|
||||
if (totalFailed === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Add notification for failures
|
||||
logForDebugging(
|
||||
`Adding notification for ${totalFailed} failed installations`,
|
||||
)
|
||||
addNotification({
|
||||
key: 'plugin-install-failed',
|
||||
jsx: (
|
||||
<>
|
||||
<Text color="error">
|
||||
{totalFailed} {plural(totalFailed, 'plugin')} failed to install
|
||||
</Text>
|
||||
<Text dimColor> · /plugin for details</Text>
|
||||
</>
|
||||
),
|
||||
priority: 'medium',
|
||||
})
|
||||
}, [
|
||||
addNotification,
|
||||
totalFailed,
|
||||
failedMarketplacesCount,
|
||||
failedPluginsCount
|
||||
} = t0;
|
||||
let t1;
|
||||
if ($[9] !== addNotification || $[10] !== failedMarketplacesCount || $[11] !== failedPluginsCount || $[12] !== installationStatus || $[13] !== totalFailed) {
|
||||
t1 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (!installationStatus) {
|
||||
logForDebugging("No installation status to monitor");
|
||||
return;
|
||||
}
|
||||
if (totalFailed === 0) {
|
||||
return;
|
||||
}
|
||||
logForDebugging(`Plugin installation status: ${failedMarketplacesCount} failed marketplaces, ${failedPluginsCount} failed plugins`);
|
||||
if (totalFailed === 0) {
|
||||
return;
|
||||
}
|
||||
logForDebugging(`Adding notification for ${totalFailed} failed installations`);
|
||||
addNotification({
|
||||
key: "plugin-install-failed",
|
||||
jsx: <><Text color="error">{totalFailed} {plural(totalFailed, "plugin")} failed to install</Text><Text dimColor={true}> · /plugin for details</Text></>,
|
||||
priority: "medium"
|
||||
});
|
||||
};
|
||||
$[9] = addNotification;
|
||||
$[10] = failedMarketplacesCount;
|
||||
$[11] = failedPluginsCount;
|
||||
$[12] = installationStatus;
|
||||
$[13] = totalFailed;
|
||||
$[14] = t1;
|
||||
} else {
|
||||
t1 = $[14];
|
||||
}
|
||||
let t2;
|
||||
if ($[15] !== addNotification || $[16] !== failedMarketplacesCount || $[17] !== failedPluginsCount || $[18] !== totalFailed) {
|
||||
t2 = [addNotification, totalFailed, failedMarketplacesCount, failedPluginsCount];
|
||||
$[15] = addNotification;
|
||||
$[16] = failedMarketplacesCount;
|
||||
$[17] = failedPluginsCount;
|
||||
$[18] = totalFailed;
|
||||
$[19] = t2;
|
||||
} else {
|
||||
t2 = $[19];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
}
|
||||
function _temp3(p) {
|
||||
return p.status === "failed";
|
||||
}
|
||||
function _temp2(m) {
|
||||
return m.status === "failed";
|
||||
}
|
||||
function _temp(s) {
|
||||
return s.plugins.installationStatus;
|
||||
failedPluginsCount,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -1,113 +1,80 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNotifications } from 'src/context/notifications.js';
|
||||
import { Text } from 'src/ink.js';
|
||||
import { getRateLimitWarning, getUsingOverageText } from 'src/services/claudeAiLimits.js';
|
||||
import { useClaudeAiLimits } from 'src/services/claudeAiLimitsHook.js';
|
||||
import { getSubscriptionType } from 'src/utils/auth.js';
|
||||
import { hasClaudeAiBillingAccess } from 'src/utils/billing.js';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
export function useRateLimitWarningNotification(model) {
|
||||
const $ = _c(17);
|
||||
const {
|
||||
addNotification
|
||||
} = useNotifications();
|
||||
const claudeAiLimits = useClaudeAiLimits();
|
||||
let t0;
|
||||
if ($[0] !== claudeAiLimits || $[1] !== model) {
|
||||
t0 = getRateLimitWarning(claudeAiLimits, model);
|
||||
$[0] = claudeAiLimits;
|
||||
$[1] = model;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[2];
|
||||
}
|
||||
const rateLimitWarning = t0;
|
||||
let t1;
|
||||
if ($[3] !== claudeAiLimits) {
|
||||
t1 = getUsingOverageText(claudeAiLimits);
|
||||
$[3] = claudeAiLimits;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
const usingOverageText = t1;
|
||||
const shownWarningRef = useRef(null);
|
||||
let t2;
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = getSubscriptionType();
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
const subscriptionType = t2;
|
||||
let t3;
|
||||
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = hasClaudeAiBillingAccess();
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
const hasBillingAccess = t3;
|
||||
const isTeamOrEnterprise = subscriptionType === "team" || subscriptionType === "enterprise";
|
||||
const [hasShownOverageNotification, setHasShownOverageNotification] = useState(false);
|
||||
let t4;
|
||||
let t5;
|
||||
if ($[7] !== addNotification || $[8] !== claudeAiLimits.isUsingOverage || $[9] !== hasShownOverageNotification || $[10] !== usingOverageText) {
|
||||
t4 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (claudeAiLimits.isUsingOverage && !hasShownOverageNotification && (!isTeamOrEnterprise || hasBillingAccess)) {
|
||||
addNotification({
|
||||
key: "limit-reached",
|
||||
text: usingOverageText,
|
||||
priority: "immediate"
|
||||
});
|
||||
setHasShownOverageNotification(true);
|
||||
} else {
|
||||
if (!claudeAiLimits.isUsingOverage && hasShownOverageNotification) {
|
||||
setHasShownOverageNotification(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
t5 = [claudeAiLimits.isUsingOverage, usingOverageText, hasShownOverageNotification, addNotification, hasBillingAccess, isTeamOrEnterprise];
|
||||
$[7] = addNotification;
|
||||
$[8] = claudeAiLimits.isUsingOverage;
|
||||
$[9] = hasShownOverageNotification;
|
||||
$[10] = usingOverageText;
|
||||
$[11] = t4;
|
||||
$[12] = t5;
|
||||
} else {
|
||||
t4 = $[11];
|
||||
t5 = $[12];
|
||||
}
|
||||
useEffect(t4, t5);
|
||||
let t6;
|
||||
let t7;
|
||||
if ($[13] !== addNotification || $[14] !== rateLimitWarning) {
|
||||
t6 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (rateLimitWarning && rateLimitWarning !== shownWarningRef.current) {
|
||||
shownWarningRef.current = rateLimitWarning;
|
||||
addNotification({
|
||||
key: "rate-limit-warning",
|
||||
jsx: <Text><Text color="warning">{rateLimitWarning}</Text></Text>,
|
||||
priority: "high"
|
||||
});
|
||||
}
|
||||
};
|
||||
t7 = [rateLimitWarning, addNotification];
|
||||
$[13] = addNotification;
|
||||
$[14] = rateLimitWarning;
|
||||
$[15] = t6;
|
||||
$[16] = t7;
|
||||
} else {
|
||||
t6 = $[15];
|
||||
t7 = $[16];
|
||||
}
|
||||
useEffect(t6, t7);
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useNotifications } from 'src/context/notifications.js'
|
||||
import { Text } from 'src/ink.js'
|
||||
import {
|
||||
getRateLimitWarning,
|
||||
getUsingOverageText,
|
||||
} from 'src/services/claudeAiLimits.js'
|
||||
import { useClaudeAiLimits } from 'src/services/claudeAiLimitsHook.js'
|
||||
import { getSubscriptionType } from 'src/utils/auth.js'
|
||||
import { hasClaudeAiBillingAccess } from 'src/utils/billing.js'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
|
||||
export function useRateLimitWarningNotification(model: string): void {
|
||||
const { addNotification } = useNotifications()
|
||||
const claudeAiLimits = useClaudeAiLimits()
|
||||
// claudeAiLimits reference is stable until statusListeners fire (API
|
||||
// response), so these skip the Intl formatting work on most REPL renders.
|
||||
const rateLimitWarning = useMemo(
|
||||
() => getRateLimitWarning(claudeAiLimits, model),
|
||||
[claudeAiLimits, model],
|
||||
)
|
||||
const usingOverageText = useMemo(
|
||||
() => getUsingOverageText(claudeAiLimits),
|
||||
[claudeAiLimits],
|
||||
)
|
||||
const shownWarningRef = useRef<string | null>(null)
|
||||
const subscriptionType = getSubscriptionType()
|
||||
const hasBillingAccess = hasClaudeAiBillingAccess()
|
||||
const isTeamOrEnterprise =
|
||||
subscriptionType === 'team' || subscriptionType === 'enterprise'
|
||||
|
||||
// Track overage mode transitions
|
||||
const [hasShownOverageNotification, setHasShownOverageNotification] =
|
||||
useState(false)
|
||||
|
||||
// Show immediate notification when entering overage mode
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (
|
||||
claudeAiLimits.isUsingOverage &&
|
||||
!hasShownOverageNotification &&
|
||||
(!isTeamOrEnterprise || hasBillingAccess)
|
||||
) {
|
||||
addNotification({
|
||||
key: 'limit-reached',
|
||||
text: usingOverageText,
|
||||
priority: 'immediate',
|
||||
})
|
||||
setHasShownOverageNotification(true)
|
||||
} else if (!claudeAiLimits.isUsingOverage && hasShownOverageNotification) {
|
||||
// Reset when no longer in overage mode
|
||||
setHasShownOverageNotification(false)
|
||||
}
|
||||
}, [
|
||||
claudeAiLimits.isUsingOverage,
|
||||
usingOverageText,
|
||||
hasShownOverageNotification,
|
||||
addNotification,
|
||||
hasBillingAccess,
|
||||
isTeamOrEnterprise,
|
||||
])
|
||||
|
||||
// Show warning notification for approaching limits
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (rateLimitWarning && rateLimitWarning !== shownWarningRef.current) {
|
||||
shownWarningRef.current = rateLimitWarning
|
||||
addNotification({
|
||||
key: 'rate-limit-warning',
|
||||
jsx: (
|
||||
<Text>
|
||||
<Text color="warning">{rateLimitWarning}</Text>
|
||||
</Text>
|
||||
),
|
||||
priority: 'high',
|
||||
})
|
||||
}
|
||||
}, [rateLimitWarning, addNotification])
|
||||
}
|
||||
|
||||
@@ -1,68 +1,41 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useNotifications } from 'src/context/notifications.js';
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js';
|
||||
import { getSettingsWithAllErrors } from '../../utils/settings/allErrors.js';
|
||||
import type { ValidationError } from '../../utils/settings/validation.js';
|
||||
import { useSettingsChange } from '../useSettingsChange.js';
|
||||
const SETTINGS_ERRORS_NOTIFICATION_KEY = 'settings-errors';
|
||||
export function useSettingsErrors() {
|
||||
const $ = _c(6);
|
||||
const {
|
||||
addNotification,
|
||||
removeNotification
|
||||
} = useNotifications();
|
||||
const [errors_0, setErrors] = useState(_temp);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = () => {
|
||||
const {
|
||||
errors: errors_1
|
||||
} = getSettingsWithAllErrors();
|
||||
setErrors(errors_1);
|
||||
};
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const handleSettingsChange = t0;
|
||||
useSettingsChange(handleSettingsChange);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[1] !== addNotification || $[2] !== errors_0 || $[3] !== removeNotification) {
|
||||
t1 = () => {
|
||||
if (getIsRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
if (errors_0.length > 0) {
|
||||
const message = `Found ${errors_0.length} settings ${errors_0.length === 1 ? "issue" : "issues"} · /doctor for details`;
|
||||
addNotification({
|
||||
key: SETTINGS_ERRORS_NOTIFICATION_KEY,
|
||||
text: message,
|
||||
color: "warning",
|
||||
priority: "high",
|
||||
timeoutMs: 60000
|
||||
});
|
||||
} else {
|
||||
removeNotification(SETTINGS_ERRORS_NOTIFICATION_KEY);
|
||||
}
|
||||
};
|
||||
t2 = [errors_0, addNotification, removeNotification];
|
||||
$[1] = addNotification;
|
||||
$[2] = errors_0;
|
||||
$[3] = removeNotification;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
t2 = $[5];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
return errors_0;
|
||||
}
|
||||
function _temp() {
|
||||
const {
|
||||
errors
|
||||
} = getSettingsWithAllErrors();
|
||||
return errors;
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useNotifications } from 'src/context/notifications.js'
|
||||
import { getIsRemoteMode } from '../../bootstrap/state.js'
|
||||
import { getSettingsWithAllErrors } from '../../utils/settings/allErrors.js'
|
||||
import type { ValidationError } from '../../utils/settings/validation.js'
|
||||
import { useSettingsChange } from '../useSettingsChange.js'
|
||||
|
||||
const SETTINGS_ERRORS_NOTIFICATION_KEY = 'settings-errors'
|
||||
|
||||
export function useSettingsErrors(): ValidationError[] {
|
||||
const { addNotification, removeNotification } = useNotifications()
|
||||
const [errors, setErrors] = useState<ValidationError[]>(() => {
|
||||
const { errors } = getSettingsWithAllErrors()
|
||||
return errors
|
||||
})
|
||||
|
||||
const handleSettingsChange = useCallback(() => {
|
||||
const { errors } = getSettingsWithAllErrors()
|
||||
setErrors(errors)
|
||||
}, [])
|
||||
|
||||
useSettingsChange(handleSettingsChange)
|
||||
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (errors.length > 0) {
|
||||
const message = `Found ${errors.length} settings ${errors.length === 1 ? 'issue' : 'issues'} · /doctor for details`
|
||||
addNotification({
|
||||
key: SETTINGS_ERRORS_NOTIFICATION_KEY,
|
||||
text: message,
|
||||
color: 'warning',
|
||||
priority: 'high',
|
||||
timeoutMs: 60000,
|
||||
})
|
||||
} else {
|
||||
removeNotification(SETTINGS_ERRORS_NOTIFICATION_KEY)
|
||||
}
|
||||
}, [errors, addNotification, removeNotification])
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user