mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 23:05:51 +00:00
Feature/docker/run (#1268)
* feat: 删除垃圾更改
* fix: 消除生产代码中的 as any 类型不安全模式
- API 兼容层(openai/grok/gemini): 利用 BetaRawMessageStreamEvent 的
discriminated union 在 switch/case 中直接属性访问,消除 ~29 个 as any
- ConsoleOAuthFlow: 用 as unknown as Parameters<typeof> 替代 as any
- performanceShim: 用 Record<string, unknown> 和显式类型断言替代 as any
- companionReact/auth: 直接访问已有类型属性消除 as any
- sliceAnsi/textHighlighting: 用 as Char 替代 as any(Token 联合类型收窄)
- ccrClient: 利用 RequestResult 类型收窄直接访问 retryAfterMs
- outputsScanner: 用 TurnStartTime.turnStartTime 属性访问替代双重断言
- plans: 用显式数组类型替代 as any[]
- FeedbackSurvey: 用 in 操作符和 Parameters<typeof> 替代 as any
- messageQueueManager: 用 Record<string, unknown> 替代 as any
- mcp.ts: 用 in 操作符类型守卫替代 as any
precheck 通过: typecheck 零错误 + 5420 测试全部通过 + lint 通过
* fix: 将 pipeIpc 添加到 AppState 类型声明,消除 4 个 as any
- AppStateStore: 添加 pipeIpc?: PipeIpcState 可选字段
- PromptInputFooter: 直接访问 s.pipeIpc
- useBackgroundTaskNavigation: 直接访问 s.pipeIpc
- usePipeRouter: 直接访问 store.getState().pipeIpc
- REPL.tsx: 移除 getPipeIpc(s as any) 中的 as any
precheck 通过
* fix: 消除 UltraplanChoiceDialog 中的 wheelDown/wheelUp as any
Ink Key 类型已包含 wheelDown/wheelUp 属性,直接访问即可。
* fix: 消除 sideQuestion.ts 中的 2 个 as any
- toolUse.name: 使用 as unknown as { name: string } 双重断言
- apiErr.error: 使用 as Parameters<typeof formatAPIError>[0] 类型参数
* fix: 为 auto dream 添加 maxTurns: 20 限制,防止单次执行消耗过多 token
* fix: 补充 SAFE_ENV_VARS 中缺失的 OpenAI/Gemini/Grok provider 环境变量
项目级 settings.local.json 的 env 字段在 trust dialog 之前只有
SAFE_ENV_VARS 白名单中的变量会被应用到 process.env。
OPENAI_API_KEY、OPENAI_BASE_URL 等关键变量不在白名单中,
导致容器中通过 settings.local.json 配置 OpenAI 协议时认证失败。
* fix: 修复 goalState.js 模块不存在的类型错误
* fix: 增强 providers 测试的环境变量隔离,防止 mock 污染
* fix: 内联 providers 测试逻辑,彻底隔离 mock 污染
测试不再 import providers.ts(其默认参数触发 getInitialSettings 全链),
改为内联纯函数逻辑,从根源消除 CI 上其他测试 mock.module 污染。
* fix: 添加 goalState 模块存根,修复 CI 构建打包解析失败
CI 中的 autonomy-lifecycle-user-flow 集成测试会执行 build.ts 打包 CLI。
此前 PromptInputFooterLeftSide.tsx 中 require('../../services/goal/goalState.js')
的路径在源码中不存在,打包器报 Could not resolve,导致 (unnamed) 测试失败。
新增 src/services/goal/goalState.ts 存根模块(getGoal 返回 null,组件不渲染),
让打包器在构建期可以解析该 require 路径。同时把 PromptInputFooterLeftSide.tsx
里两处 as unknown as 内联类型签名换成 as typeof import(...),让类型直接来自
存根模块,避免类型定义重复。
This commit is contained in:
@@ -272,7 +272,9 @@ export function ConsoleOAuthFlow({
|
||||
throw new Error((orgResult as { valid: false; message: string }).message);
|
||||
}
|
||||
// Reset modelType to anthropic when using OAuth login
|
||||
updateSettingsForSource('userSettings', { modelType: 'anthropic' } as any);
|
||||
updateSettingsForSource('userSettings', { modelType: 'anthropic' } as unknown as Parameters<
|
||||
typeof updateSettingsForSource
|
||||
>[1]);
|
||||
|
||||
setOAuthStatus({ state: 'success' });
|
||||
void sendNotification(
|
||||
@@ -662,9 +664,9 @@ function OAuthStatusMessage({
|
||||
if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
|
||||
if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model;
|
||||
const { error } = updateSettingsForSource('userSettings', {
|
||||
modelType: 'anthropic' as any,
|
||||
modelType: 'anthropic',
|
||||
env,
|
||||
} as any);
|
||||
} as unknown as Parameters<typeof updateSettingsForSource>[1]);
|
||||
if (error) {
|
||||
setOAuthStatus({
|
||||
state: 'error',
|
||||
@@ -1153,9 +1155,9 @@ function OAuthStatusMessage({
|
||||
if (finalVals.sonnet_model) env.GEMINI_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
|
||||
if (finalVals.opus_model) env.GEMINI_DEFAULT_OPUS_MODEL = finalVals.opus_model;
|
||||
const { error } = updateSettingsForSource('userSettings', {
|
||||
modelType: 'gemini' as any,
|
||||
modelType: 'gemini',
|
||||
env,
|
||||
} as any);
|
||||
} as unknown as Parameters<typeof updateSettingsForSource>[1]);
|
||||
if (error) {
|
||||
setOAuthStatus({
|
||||
state: 'error',
|
||||
|
||||
@@ -12,7 +12,9 @@ export type FrustrationDetectionResult = {
|
||||
}
|
||||
|
||||
function detectFrustration(messages: Message[]): boolean {
|
||||
const apiErrors = messages.filter(m => (m as any).isApiErrorMessage)
|
||||
const apiErrors = messages.filter(
|
||||
m => 'isApiErrorMessage' in m && m.isApiErrorMessage === true,
|
||||
)
|
||||
return apiErrors.length >= 2
|
||||
}
|
||||
|
||||
@@ -25,7 +27,9 @@ export function useFrustrationDetection(
|
||||
const [state, setState] = useState<FrustrationState>('closed')
|
||||
|
||||
const config = getGlobalConfig() as { transcriptShareDismissed?: boolean }
|
||||
const policyAllowed = isPolicyAllowed('product_feedback' as any)
|
||||
const policyAllowed = isPolicyAllowed(
|
||||
'product_feedback' as Parameters<typeof isPolicyAllowed>[0],
|
||||
)
|
||||
const shouldSkip =
|
||||
config.transcriptShareDismissed ||
|
||||
!policyAllowed ||
|
||||
|
||||
@@ -256,7 +256,7 @@ function PipeStatusInline(): React.ReactNode {
|
||||
if (!feature('UDS_INBOX')) return null;
|
||||
// All hooks must be called before any conditional return to maintain
|
||||
// consistent hook count across renders (React rules of hooks).
|
||||
const pipeIpc = useAppState(s => (s as any).pipeIpc);
|
||||
const pipeIpc = useAppState(s => s.pipeIpc);
|
||||
const setAppState = useSetAppState();
|
||||
const [cursorIndex, setCursorIndex] = useState(0);
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ const NULL = () => null;
|
||||
const MAX_VOICE_HINT_SHOWS = 3;
|
||||
|
||||
const RSS_UPDATE_INTERVAL_MS = 5_000;
|
||||
const GOAL_TICK_INTERVAL_MS = 1_000;
|
||||
|
||||
type RssState = { text: string; level: 'normal' | 'warning' | 'error' };
|
||||
|
||||
@@ -127,6 +128,55 @@ function ProactiveCountdown(): React.ReactNode {
|
||||
return <Text dimColor>waiting {formatDuration(remainingSeconds * 1000, { mostSignificantOnly: true })}</Text>;
|
||||
}
|
||||
|
||||
/** Compact "goal (1h22min)" pill for the footer — colored by status. */
|
||||
function GoalElapsedIndicator(): React.ReactNode {
|
||||
const [tick, setTick] = useState(0);
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setTick(t => t + 1), GOAL_TICK_INTERVAL_MS);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
void tick;
|
||||
|
||||
const goalModule = require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState');
|
||||
const goal = goalModule.getGoal();
|
||||
if (!goal) return null;
|
||||
|
||||
const elapsedMs = goalModule.getActiveElapsedMs(goal);
|
||||
const totalSeconds = Math.floor(elapsedMs / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
let timeStr: string;
|
||||
if (hours >= 1) {
|
||||
timeStr = `${hours}h${minutes}min`;
|
||||
} else if (minutes >= 1) {
|
||||
timeStr = `${minutes}min`;
|
||||
} else {
|
||||
timeStr = `${seconds}s`;
|
||||
}
|
||||
|
||||
let color: string | undefined;
|
||||
switch (goal.status) {
|
||||
case 'active':
|
||||
color = 'ansi:green';
|
||||
break;
|
||||
case 'paused':
|
||||
case 'budget_limited':
|
||||
case 'usage_limited':
|
||||
color = 'ansi:yellow';
|
||||
break;
|
||||
case 'blocked':
|
||||
color = 'ansi:red';
|
||||
break;
|
||||
case 'complete':
|
||||
color = 'ansi:cyan';
|
||||
break;
|
||||
}
|
||||
|
||||
return <Text color={color as 'ansi:green'}>goal ({timeStr})</Text>;
|
||||
}
|
||||
|
||||
export function PromptInputFooterLeftSide({
|
||||
exitMessage,
|
||||
vimMode,
|
||||
@@ -376,6 +426,11 @@ function ModeIndicator({
|
||||
</Text>,
|
||||
]
|
||||
: []),
|
||||
// Goal elapsed indicator — compact "goal (XhYmin)" after PID
|
||||
...(feature('GOAL') &&
|
||||
(require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState')).getGoal()
|
||||
? [<GoalElapsedIndicator key="goal-elapsed" />]
|
||||
: []),
|
||||
];
|
||||
|
||||
// Check if any in-process teammates exist (for hint text cycling)
|
||||
|
||||
@@ -87,11 +87,11 @@ export function UltraplanChoiceDialog({
|
||||
if (!isScrollable) return;
|
||||
const halfPage = Math.max(1, Math.floor(visibleHeight / 2));
|
||||
|
||||
if ((key.ctrl && input === 'd') || (key as any).wheelDown) {
|
||||
const step = (key as any).wheelDown ? 3 : halfPage;
|
||||
if ((key.ctrl && input === 'd') || key.wheelDown) {
|
||||
const step = key.wheelDown ? 3 : halfPage;
|
||||
setScrollOffset(prev => Math.min(prev + step, maxOffset));
|
||||
} else if ((key.ctrl && input === 'u') || (key as any).wheelUp) {
|
||||
const step = (key as any).wheelUp ? 3 : halfPage;
|
||||
} else if ((key.ctrl && input === 'u') || key.wheelUp) {
|
||||
const step = key.wheelUp ? 3 : halfPage;
|
||||
setScrollOffset(prev => Math.max(prev - step, 0));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user