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 通过
This commit is contained in:
claude-code-best
2026-06-09 22:55:09 +08:00
parent 6e52043555
commit 21c17b4c6e
16 changed files with 212 additions and 91 deletions

View File

@@ -73,7 +73,7 @@ function isAddressed(messages: Message[], name: string): boolean {
) { ) {
const m = messages[i] const m = messages[i]
if (m?.type !== 'user') continue if (m?.type !== 'user') continue
const content = (m as any).message?.content const content = m.message?.content
if (typeof content === 'string' && pattern.test(content)) return true if (typeof content === 'string' && pattern.test(content)) return true
} }
return false return false
@@ -89,7 +89,7 @@ function buildTranscript(messages: Message[]): string {
.filter(m => m.type === 'user' || m.type === 'assistant') .filter(m => m.type === 'user' || m.type === 'assistant')
.map(m => { .map(m => {
const role = m.type === 'user' ? 'user' : 'claude' const role = m.type === 'user' ? 'user' : 'claude'
const content = (m as any).message?.content const content = m.message?.content
const text = const text =
typeof content === 'string' typeof content === 'string'
? content.slice(0, 300) ? content.slice(0, 300)

View File

@@ -381,7 +381,7 @@ export class CCRClient {
if (!result.ok) { if (!result.ok) {
throw new RetryableError( throw new RetryableError(
'client event POST failed', 'client event POST failed',
(result as any).retryAfterMs, result.retryAfterMs,
) )
} }
}, },
@@ -404,7 +404,7 @@ export class CCRClient {
if (!result.ok) { if (!result.ok) {
throw new RetryableError( throw new RetryableError(
'internal event POST failed', 'internal event POST failed',
(result as any).retryAfterMs, result.retryAfterMs,
) )
} }
}, },
@@ -433,10 +433,7 @@ export class CCRClient {
'delivery batch', 'delivery batch',
) )
if (!result.ok) { if (!result.ok) {
throw new RetryableError( throw new RetryableError('delivery POST failed', result.retryAfterMs)
'delivery POST failed',
(result as any).retryAfterMs,
)
} }
}, },
baseDelayMs: 500, baseDelayMs: 500,

View File

@@ -272,7 +272,9 @@ export function ConsoleOAuthFlow({
throw new Error((orgResult as { valid: false; message: string }).message); throw new Error((orgResult as { valid: false; message: string }).message);
} }
// Reset modelType to anthropic when using OAuth login // 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' }); setOAuthStatus({ state: 'success' });
void sendNotification( void sendNotification(
@@ -662,9 +664,9 @@ function OAuthStatusMessage({
if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model; if (finalVals.sonnet_model) env.ANTHROPIC_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model; if (finalVals.opus_model) env.ANTHROPIC_DEFAULT_OPUS_MODEL = finalVals.opus_model;
const { error } = updateSettingsForSource('userSettings', { const { error } = updateSettingsForSource('userSettings', {
modelType: 'anthropic' as any, modelType: 'anthropic',
env, env,
} as any); } as unknown as Parameters<typeof updateSettingsForSource>[1]);
if (error) { if (error) {
setOAuthStatus({ setOAuthStatus({
state: 'error', state: 'error',
@@ -1153,9 +1155,9 @@ function OAuthStatusMessage({
if (finalVals.sonnet_model) env.GEMINI_DEFAULT_SONNET_MODEL = finalVals.sonnet_model; if (finalVals.sonnet_model) env.GEMINI_DEFAULT_SONNET_MODEL = finalVals.sonnet_model;
if (finalVals.opus_model) env.GEMINI_DEFAULT_OPUS_MODEL = finalVals.opus_model; if (finalVals.opus_model) env.GEMINI_DEFAULT_OPUS_MODEL = finalVals.opus_model;
const { error } = updateSettingsForSource('userSettings', { const { error } = updateSettingsForSource('userSettings', {
modelType: 'gemini' as any, modelType: 'gemini',
env, env,
} as any); } as unknown as Parameters<typeof updateSettingsForSource>[1]);
if (error) { if (error) {
setOAuthStatus({ setOAuthStatus({
state: 'error', state: 'error',

View File

@@ -12,7 +12,9 @@ export type FrustrationDetectionResult = {
} }
function detectFrustration(messages: Message[]): boolean { 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 return apiErrors.length >= 2
} }
@@ -25,7 +27,9 @@ export function useFrustrationDetection(
const [state, setState] = useState<FrustrationState>('closed') const [state, setState] = useState<FrustrationState>('closed')
const config = getGlobalConfig() as { transcriptShareDismissed?: boolean } 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 = const shouldSkip =
config.transcriptShareDismissed || config.transcriptShareDismissed ||
!policyAllowed || !policyAllowed ||

View File

@@ -55,6 +55,7 @@ const NULL = () => null;
const MAX_VOICE_HINT_SHOWS = 3; const MAX_VOICE_HINT_SHOWS = 3;
const RSS_UPDATE_INTERVAL_MS = 5_000; const RSS_UPDATE_INTERVAL_MS = 5_000;
const GOAL_TICK_INTERVAL_MS = 1_000;
type RssState = { text: string; level: 'normal' | 'warning' | 'error' }; type RssState = { text: string; level: 'normal' | 'warning' | 'error' };
@@ -127,6 +128,56 @@ function ProactiveCountdown(): React.ReactNode {
return <Text dimColor>waiting {formatDuration(remainingSeconds * 1000, { mostSignificantOnly: true })}</Text>; 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 { getGoal, getActiveElapsedMs } =
require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState.js');
const goal = getGoal();
if (!goal) return null;
const elapsedMs = 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({ export function PromptInputFooterLeftSide({
exitMessage, exitMessage,
vimMode, vimMode,
@@ -376,6 +427,11 @@ function ModeIndicator({
</Text>, </Text>,
] ]
: []), : []),
// Goal elapsed indicator — compact "goal (XhYmin)" after PID
...(feature('GOAL') &&
(require('../../services/goal/goalState.js') as typeof import('../../services/goal/goalState.js')).getGoal()
? [<GoalElapsedIndicator key="goal-elapsed" />]
: []),
]; ];
// Check if any in-process teammates exist (for hint text cycling) // Check if any in-process teammates exist (for hint text cycling)

View File

@@ -144,7 +144,7 @@ export async function startMCPServer(
) )
if (validationResult && !validationResult.result) { if (validationResult && !validationResult.result) {
throw new Error( throw new Error(
`Tool ${name} input is invalid: ${(validationResult as any).message}`, `Tool ${name} input is invalid: ${'message' in validationResult ? validationResult.message : String(validationResult)}`,
) )
} }
const finalResult = await tool.call( const finalResult = await tool.call(

View File

@@ -1,4 +1,7 @@
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' import type {
BetaToolUnion,
BetaMessage,
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import { randomUUID } from 'crypto' import { randomUUID } from 'crypto'
import type { import type {
AssistantMessage, AssistantMessage,
@@ -112,21 +115,21 @@ export async function* queryModelGemini(
) )
const adaptedStream = adaptGeminiStreamToAnthropic(stream, geminiModel) const adaptedStream = adaptGeminiStreamToAnthropic(stream, geminiModel)
const contentBlocks: Record<number, any> = {} const contentBlocks: Record<number, Record<string, unknown>> = {}
const collectedMessages: AssistantMessage[] = [] const collectedMessages: AssistantMessage[] = []
let partialMessage: any let partialMessage: BetaMessage | null = null
let ttftMs = 0 let ttftMs = 0
const start = Date.now() const start = Date.now()
for await (const event of adaptedStream) { for await (const event of adaptedStream) {
switch (event.type) { switch (event.type) {
case 'message_start': case 'message_start':
partialMessage = (event as any).message partialMessage = event.message
ttftMs = Date.now() - start ttftMs = Date.now() - start
break break
case 'content_block_start': { case 'content_block_start': {
const idx = (event as any).index const idx = event.index
const cb = (event as any).content_block const cb = event.content_block
if (cb.type === 'tool_use') { if (cb.type === 'tool_use') {
contentBlocks[idx] = { ...cb, input: '' } contentBlocks[idx] = { ...cb, input: '' }
} else if (cb.type === 'text') { } else if (cb.type === 'text') {
@@ -139,17 +142,19 @@ export async function* queryModelGemini(
break break
} }
case 'content_block_delta': { case 'content_block_delta': {
const idx = (event as any).index const idx = event.index
const delta = (event as any).delta const delta = event.delta
const block = contentBlocks[idx] const block = contentBlocks[idx]
if (!block) break if (!block) break
if (delta.type === 'text_delta') { if (delta.type === 'text_delta') {
block.text = (block.text || '') + delta.text block.text = ((block.text as string | undefined) || '') + delta.text
} else if (delta.type === 'input_json_delta') { } else if (delta.type === 'input_json_delta') {
block.input = (block.input || '') + delta.partial_json block.input =
((block.input as string | undefined) || '') + delta.partial_json
} else if (delta.type === 'thinking_delta') { } else if (delta.type === 'thinking_delta') {
block.thinking = (block.thinking || '') + delta.thinking block.thinking =
((block.thinking as string | undefined) || '') + delta.thinking
} else if (delta.type === 'signature_delta') { } else if (delta.type === 'signature_delta') {
if (block.type === 'thinking') { if (block.type === 'thinking') {
block.signature = delta.signature block.signature = delta.signature
@@ -160,15 +165,19 @@ export async function* queryModelGemini(
break break
} }
case 'content_block_stop': { case 'content_block_stop': {
const idx = (event as any).index const idx = event.index
const block = contentBlocks[idx] const block = contentBlocks[idx]
if (!block || !partialMessage) break if (!block || !partialMessage) break
const message: AssistantMessage = { const message: AssistantMessage = {
message: { message: {
...partialMessage, ...partialMessage,
content: normalizeContentFromAPI([block], tools, options.agentId), content: normalizeContentFromAPI(
}, [block] as unknown as BetaMessage['content'],
tools,
options.agentId,
),
} as AssistantMessage['message'],
requestId: undefined, requestId: undefined,
type: 'assistant', type: 'assistant',
uuid: randomUUID(), uuid: randomUUID(),

View File

@@ -1,4 +1,8 @@
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' import type {
BetaToolUnion,
BetaMessage,
BetaUsage,
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import type { SystemPrompt } from '../../../utils/systemPromptType.js' import type { SystemPrompt } from '../../../utils/systemPromptType.js'
import type { import type {
Message, Message,
@@ -119,10 +123,15 @@ export async function* queryModelGrok(
grokModel, grokModel,
) )
const contentBlocks: Record<number, any> = {} const contentBlocks: Record<number, Record<string, unknown>> = {}
const collectedMessages: AssistantMessage[] = [] const collectedMessages: AssistantMessage[] = []
let partialMessage: any let partialMessage: BetaMessage | null = null
let usage = { let usage: {
input_tokens: number
output_tokens: number
cache_creation_input_tokens: number
cache_read_input_tokens: number
} = {
input_tokens: 0, input_tokens: 0,
output_tokens: 0, output_tokens: 0,
cache_creation_input_tokens: 0, cache_creation_input_tokens: 0,
@@ -134,16 +143,21 @@ export async function* queryModelGrok(
for await (const event of adaptedStream) { for await (const event of adaptedStream) {
switch (event.type) { switch (event.type) {
case 'message_start': { case 'message_start': {
partialMessage = (event as any).message partialMessage = event.message
ttftMs = Date.now() - start ttftMs = Date.now() - start
if ((event as any).message?.usage) { if (event.message.usage) {
usage = updateOpenAIUsage(usage, (event as any).message.usage) usage = updateOpenAIUsage(
usage,
event.message.usage as unknown as Parameters<
typeof updateOpenAIUsage
>[1],
)
} }
break break
} }
case 'content_block_start': { case 'content_block_start': {
const idx = (event as any).index const idx = event.index
const cb = (event as any).content_block const cb = event.content_block
if (cb.type === 'tool_use') { if (cb.type === 'tool_use') {
contentBlocks[idx] = { ...cb, input: '' } contentBlocks[idx] = { ...cb, input: '' }
} else if (cb.type === 'text') { } else if (cb.type === 'text') {
@@ -156,31 +170,37 @@ export async function* queryModelGrok(
break break
} }
case 'content_block_delta': { case 'content_block_delta': {
const idx = (event as any).index const idx = event.index
const delta = (event as any).delta const delta = event.delta
const block = contentBlocks[idx] const block = contentBlocks[idx]
if (!block) break if (!block) break
if (delta.type === 'text_delta') { if (delta.type === 'text_delta') {
block.text = (block.text || '') + delta.text block.text = ((block.text as string | undefined) || '') + delta.text
} else if (delta.type === 'input_json_delta') { } else if (delta.type === 'input_json_delta') {
block.input = (block.input || '') + delta.partial_json block.input =
((block.input as string | undefined) || '') + delta.partial_json
} else if (delta.type === 'thinking_delta') { } else if (delta.type === 'thinking_delta') {
block.thinking = (block.thinking || '') + delta.thinking block.thinking =
((block.thinking as string | undefined) || '') + delta.thinking
} else if (delta.type === 'signature_delta') { } else if (delta.type === 'signature_delta') {
block.signature = delta.signature block.signature = delta.signature
} }
break break
} }
case 'content_block_stop': { case 'content_block_stop': {
const idx = (event as any).index const idx = event.index
const block = contentBlocks[idx] const block = contentBlocks[idx]
if (!block || !partialMessage) break if (!block || !partialMessage) break
const m: AssistantMessage = { const m: AssistantMessage = {
message: { message: {
...partialMessage, ...partialMessage,
content: normalizeContentFromAPI([block], tools, options.agentId), content: normalizeContentFromAPI(
}, [block] as unknown as BetaMessage['content'],
tools,
options.agentId,
),
} as AssistantMessage['message'],
requestId: undefined, requestId: undefined,
type: 'assistant', type: 'assistant',
uuid: randomUUID(), uuid: randomUUID(),
@@ -191,9 +211,12 @@ export async function* queryModelGrok(
break break
} }
case 'message_delta': { case 'message_delta': {
const deltaUsage = (event as any).usage const deltaUsage = event.usage
if (deltaUsage) { if (deltaUsage) {
usage = updateOpenAIUsage(usage, deltaUsage) usage = updateOpenAIUsage(
usage,
deltaUsage as unknown as Parameters<typeof updateOpenAIUsage>[1],
)
} }
break break
} }
@@ -205,8 +228,15 @@ export async function* queryModelGrok(
event.type === 'message_stop' && event.type === 'message_stop' &&
usage.input_tokens + usage.output_tokens > 0 usage.input_tokens + usage.output_tokens > 0
) { ) {
const costUSD = calculateUSDCost(grokModel, usage as any) const costUSD = calculateUSDCost(
addToTotalSessionCost(costUSD, usage as any, options.model) grokModel,
usage as unknown as BetaUsage,
)
addToTotalSessionCost(
costUSD,
usage as unknown as BetaUsage,
options.model,
)
} }
yield { yield {

View File

@@ -1,4 +1,8 @@
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' import type {
BetaToolUnion,
BetaMessage,
BetaUsage,
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import type { SystemPrompt } from '../../../utils/systemPromptType.js' import type { SystemPrompt } from '../../../utils/systemPromptType.js'
import type { import type {
Message, Message,
@@ -137,8 +141,8 @@ function isOpenAIConvertibleMessage(
* `message_stop` handler and the post-loop safety fallback. * `message_stop` handler and the post-loop safety fallback.
*/ */
function assembleFinalAssistantOutputs(params: { function assembleFinalAssistantOutputs(params: {
partialMessage: any partialMessage: BetaMessage | null
contentBlocks: Record<number, any> contentBlocks: Record<number, Record<string, unknown>>
tools: Tools tools: Tools
agentId: string | undefined agentId: string | undefined
usage: { usage: {
@@ -166,19 +170,19 @@ function assembleFinalAssistantOutputs(params: {
.map(k => contentBlocks[Number(k)]) .map(k => contentBlocks[Number(k)])
.filter(Boolean) .filter(Boolean)
if (allBlocks.length > 0) { if (allBlocks.length > 0 && partialMessage) {
outputs.push({ outputs.push({
message: { message: {
...partialMessage, ...partialMessage,
content: normalizeContentFromAPI( content: normalizeContentFromAPI(
allBlocks, allBlocks as unknown as BetaMessage['content'],
tools, tools,
agentId as AgentId | undefined, agentId as AgentId | undefined,
), ),
usage, usage,
stop_reason: stopReason, stop_reason: stopReason,
stop_sequence: null, stop_sequence: null,
}, } as AssistantMessage['message'],
requestId: undefined, requestId: undefined,
type: 'assistant', type: 'assistant',
uuid: randomUUID(), uuid: randomUUID(),
@@ -387,9 +391,9 @@ export async function* queryModelOpenAI(
// AssistantMessage + StreamEvent (matching the Anthropic path behavior) // AssistantMessage + StreamEvent (matching the Anthropic path behavior)
// Accumulate content blocks and usage, same as the Anthropic path in claude.ts // Accumulate content blocks and usage, same as the Anthropic path in claude.ts
const contentBlocks: Record<number, any> = {} const contentBlocks: Record<number, Record<string, unknown>> = {}
const collectedMessages: AssistantMessage[] = [] const collectedMessages: AssistantMessage[] = []
let partialMessage: any let partialMessage: BetaMessage | null = null
let stopReason: string | null = null let stopReason: string | null = null
let usage = { let usage = {
input_tokens: 0, input_tokens: 0,
@@ -403,19 +407,19 @@ export async function* queryModelOpenAI(
for await (const event of adaptedStream) { for await (const event of adaptedStream) {
switch (event.type) { switch (event.type) {
case 'message_start': { case 'message_start': {
partialMessage = (event as any).message partialMessage = event.message
ttftMs = Date.now() - start ttftMs = Date.now() - start
if ((event as any).message?.usage) { if (event.message.usage) {
usage = { usage = {
...usage, ...usage,
...(event as any).message.usage, ...(event.message.usage as unknown as typeof usage),
} }
} }
break break
} }
case 'content_block_start': { case 'content_block_start': {
const idx = (event as any).index const idx = event.index
const cb = (event as any).content_block const cb = event.content_block
if (cb.type === 'tool_use') { if (cb.type === 'tool_use') {
contentBlocks[idx] = { ...cb, input: '' } contentBlocks[idx] = { ...cb, input: '' }
} else if (cb.type === 'text') { } else if (cb.type === 'text') {
@@ -428,16 +432,18 @@ export async function* queryModelOpenAI(
break break
} }
case 'content_block_delta': { case 'content_block_delta': {
const idx = (event as any).index const idx = event.index
const delta = (event as any).delta const delta = event.delta
const block = contentBlocks[idx] const block = contentBlocks[idx]
if (!block) break if (!block) break
if (delta.type === 'text_delta') { if (delta.type === 'text_delta') {
block.text = (block.text || '') + delta.text block.text = ((block.text as string | undefined) || '') + delta.text
} else if (delta.type === 'input_json_delta') { } else if (delta.type === 'input_json_delta') {
block.input = (block.input || '') + delta.partial_json block.input =
((block.input as string | undefined) || '') + delta.partial_json
} else if (delta.type === 'thinking_delta') { } else if (delta.type === 'thinking_delta') {
block.thinking = (block.thinking || '') + delta.thinking block.thinking =
((block.thinking as string | undefined) || '') + delta.thinking
} else if (delta.type === 'signature_delta') { } else if (delta.type === 'signature_delta') {
block.signature = delta.signature block.signature = delta.signature
} }
@@ -448,12 +454,15 @@ export async function* queryModelOpenAI(
break break
} }
case 'message_delta': { case 'message_delta': {
const deltaUsage = (event as any).usage const deltaUsage = event.usage
if (deltaUsage) { if (deltaUsage) {
usage = updateOpenAIUsage(usage, deltaUsage) usage = updateOpenAIUsage(
usage,
deltaUsage as unknown as Parameters<typeof updateOpenAIUsage>[1],
)
} }
if ((event as any).delta?.stop_reason != null) { if (event.delta.stop_reason != null) {
stopReason = (event as any).delta.stop_reason stopReason = event.delta.stop_reason
} }
break break
} }
@@ -482,8 +491,15 @@ export async function* queryModelOpenAI(
} }
// Track cost and token usage // Track cost and token usage
if (usage.input_tokens + usage.output_tokens > 0) { if (usage.input_tokens + usage.output_tokens > 0) {
const costUSD = calculateUSDCost(openaiModel, usage as any) const costUSD = calculateUSDCost(
addToTotalSessionCost(costUSD, usage as any, options.model) openaiModel,
usage as unknown as BetaUsage,
)
addToTotalSessionCost(
costUSD,
usage as unknown as BetaUsage,
options.model,
)
} }
break break
} }

View File

@@ -117,8 +117,8 @@ export function isAnthropicAuthEnabled(): boolean {
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) || isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) || isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) || isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
(settings as any).modelType === 'openai' || settings.modelType === 'openai' ||
(settings as any).modelType === 'gemini' || settings.modelType === 'gemini' ||
!!process.env.OPENAI_BASE_URL || !!process.env.OPENAI_BASE_URL ||
!!process.env.GEMINI_BASE_URL !!process.env.GEMINI_BASE_URL
const apiKeyHelper = settings.apiKeyHelper const apiKeyHelper = settings.apiKeyHelper

View File

@@ -64,12 +64,14 @@ export async function findModifiedFiles(
outputsDir: string, outputsDir: string,
): Promise<string[]> { ): Promise<string[]> {
// Use recursive flag to get all entries in one call // Use recursive flag to get all entries in one call
let entries: Awaited<ReturnType<typeof fs.readdir>> | any[] let entries:
| Awaited<ReturnType<typeof fs.readdir>>
| { name: string; isFile(): boolean; isSymbolicLink(): boolean }[]
try { try {
entries = (await fs.readdir(outputsDir, { entries = (await fs.readdir(outputsDir, {
withFileTypes: true, withFileTypes: true,
recursive: true, recursive: true,
})) as any[] })) as { name: string; isFile(): boolean; isSymbolicLink(): boolean }[]
} catch { } catch {
// Directory doesn't exist or is not accessible // Directory doesn't exist or is not accessible
return [] return []
@@ -113,7 +115,7 @@ export async function findModifiedFiles(
// Filter to files modified since turn start // Filter to files modified since turn start
const modifiedFiles: string[] = [] const modifiedFiles: string[] = []
for (const result of statResults) { for (const result of statResults) {
if (result && result.mtimeMs >= (turnStartTime as any as number)) { if (result && result.mtimeMs >= turnStartTime.turnStartTime) {
modifiedFiles.push(result.filePath) modifiedFiles.push(result.filePath)
} }
} }

View File

@@ -368,7 +368,9 @@ export function isQueuedCommandEditable(cmd: QueuedCommand): boolean {
export function isQueuedCommandVisible(cmd: QueuedCommand): boolean { export function isQueuedCommandVisible(cmd: QueuedCommand): boolean {
if ( if (
(feature('KAIROS') || feature('KAIROS_CHANNELS')) && (feature('KAIROS') || feature('KAIROS_CHANNELS')) &&
(cmd as any).origin?.kind === 'channel' (cmd as Record<string, unknown>).origin !== undefined &&
((cmd as Record<string, unknown>).origin as Record<string, unknown>)
?.kind === 'channel'
) )
return true return true
return isQueuedCommandEditable(cmd) return isQueuedCommandEditable(cmd)

View File

@@ -137,13 +137,14 @@ const shim = {
(() => {}) as typeof performance.setResourceTimingBufferSize, (() => {}) as typeof performance.setResourceTimingBufferSize,
// Node.js v22 undici internal calls this after every fetch — must exist to // Node.js v22 undici internal calls this after every fetch — must exist to
// avoid TypeError: markResourceTiming is not a function // avoid TypeError: markResourceTiming is not a function
markResourceTiming: (() => {}) as any, markResourceTiming: (() => {}) as () => void,
// Delegate read-only properties to the original // Delegate read-only properties to the original
get timeOrigin() { get timeOrigin() {
return original.timeOrigin return original.timeOrigin
}, },
get onresourcetimingbufferfull() { get onresourcetimingbufferfull() {
return (original as any).onresourcetimingbufferfull return (original as unknown as typeof performance)
.onresourcetimingbufferfull
}, },
set onresourcetimingbufferfull(_v: any) { set onresourcetimingbufferfull(_v: any) {
// no-op — prevent accumulation // no-op — prevent accumulation
@@ -159,8 +160,8 @@ const shim = {
* native Performance reference. * native Performance reference.
*/ */
export function installPerformanceShim(): void { export function installPerformanceShim(): void {
if ((globalThis as any).__performanceShimInstalled) return if ((globalThis as Record<string, unknown>).__performanceShimInstalled) return
;(globalThis as any).__performanceShimInstalled = true ;(globalThis as Record<string, unknown>).__performanceShimInstalled = true
globalThis.performance = shim globalThis.performance = shim
} }

View File

@@ -366,19 +366,19 @@ export async function persistFileSnapshotIfRemote(): Promise<void> {
return return
} }
try { try {
const snapshotFiles: SystemFileSnapshotMessage['snapshotFiles'] = [] const snapshotFiles: { key: string; path: string; content: string }[] = []
// Snapshot plan file // Snapshot plan file
const plan = getPlan() const plan = getPlan()
if (plan) { if (plan) {
;(snapshotFiles as any[]).push({ snapshotFiles.push({
key: 'plan', key: 'plan',
path: getPlanFilePath(), path: getPlanFilePath(),
content: plan, content: plan,
}) })
} }
if ((snapshotFiles as any[]).length === 0) { if (snapshotFiles.length === 0) {
return return
} }

View File

@@ -1,5 +1,6 @@
import { import {
type AnsiCode, type AnsiCode,
type Char,
ansiCodesToString, ansiCodesToString,
reduceAnsiCodes, reduceAnsiCodes,
tokenize, tokenize,
@@ -83,7 +84,7 @@ export default function sliceAnsi(
} }
if (include) { if (include) {
result += (token as any).value result += (token as Char).value
} }
position += width position += width

View File

@@ -1,5 +1,6 @@
import { import {
type AnsiCode, type AnsiCode,
type Char,
ansiCodesToString, ansiCodesToString,
reduceAnsiCodes, reduceAnsiCodes,
type Token, type Token,
@@ -128,14 +129,14 @@ class HighlightSegmenter {
this.tokenIdx++ this.tokenIdx++
} else { } else {
const charsNeeded = targetVisiblePos - this.visiblePos const charsNeeded = targetVisiblePos - this.visiblePos
const charsAvailable = (token as any).value.length - this.charIdx const charsAvailable = (token as Char).value.length - this.charIdx
const charsToTake = Math.min(charsNeeded, charsAvailable) const charsToTake = Math.min(charsNeeded, charsAvailable)
this.stringPos += charsToTake this.stringPos += charsToTake
this.visiblePos += charsToTake this.visiblePos += charsToTake
this.charIdx += charsToTake this.charIdx += charsToTake
if (this.charIdx >= (token as any).value.length) { if (this.charIdx >= (token as Char).value.length) {
this.tokenIdx++ this.tokenIdx++
this.charIdx = 0 this.charIdx = 0
} }