diff --git a/packages/@ant/computer-use-mcp/src/executor.ts b/packages/@ant/computer-use-mcp/src/executor.ts index 15df0706f..1719de96c 100644 --- a/packages/@ant/computer-use-mcp/src/executor.ts +++ b/packages/@ant/computer-use-mcp/src/executor.ts @@ -49,7 +49,7 @@ export interface ResolvePrepareCaptureResult extends ScreenshotResult { export interface ComputerExecutorCapabilities { screenshotFiltering: 'native' | 'none' - platform: 'darwin' | 'win32' + platform: 'darwin' | 'win32' | 'linux' hostBundleId: string } diff --git a/packages/@ant/computer-use-mcp/src/keyBlocklist.ts b/packages/@ant/computer-use-mcp/src/keyBlocklist.ts index b530dbaa4..ead6e2483 100644 --- a/packages/@ant/computer-use-mcp/src/keyBlocklist.ts +++ b/packages/@ant/computer-use-mcp/src/keyBlocklist.ts @@ -123,7 +123,7 @@ export function normalizeKeySequence(seq: string): string { */ export function isSystemKeyCombo( seq: string, - platform: 'darwin' | 'win32', + platform: 'darwin' | 'win32' | 'linux', ): boolean { const blocklist = platform === 'darwin' ? BLOCKED_DARWIN : BLOCKED_WIN32 const { mods, keys } = partitionKeys(seq) diff --git a/src/types/message.ts b/src/types/message.ts index 567bae475..fa47dab0f 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -16,7 +16,14 @@ import type { * Individual message subtypes (UserMessage, AssistantMessage, etc.) extend * this with narrower `type` literals and additional fields. */ -export type MessageType = 'user' | 'assistant' | 'system' | 'attachment' | 'progress' | 'grouped_tool_use' | 'collapsed_read_search' +export type MessageType = + | 'user' + | 'assistant' + | 'system' + | 'attachment' + | 'progress' + | 'grouped_tool_use' + | 'collapsed_read_search' /** A single content element inside message.content arrays. */ export type ContentItem = ContentBlockParam | ContentBlock @@ -37,7 +44,14 @@ export type Message = { isCompactSummary?: boolean toolUseResult?: unknown isVisibleInTranscriptOnly?: boolean - attachment?: { type: string; toolUseID?: string; [key: string]: unknown; addedNames: string[]; addedLines: string[]; removedNames: string[] } + attachment?: { + type: string + toolUseID?: string + [key: string]: unknown + addedNames: string[] + addedLines: string[] + removedNames: string[] + } message?: { role?: string id?: string @@ -52,8 +66,12 @@ export type AssistantMessage = Message & { type: 'assistant' message: NonNullable } -export type AttachmentMessage = Message & { type: 'attachment'; attachment: T } -export type ProgressMessage = Message & { type: 'progress'; data: T } +export type AttachmentMessage = + Message & { type: 'attachment'; attachment: T } +export type ProgressMessage = Message & { + type: 'progress' + data: T +} export type SystemLocalCommandMessage = Message & { type: 'system' } export type SystemMessage = Message & { type: 'system' } export type UserMessage = Message & { @@ -78,7 +96,7 @@ export type SystemCompactBoundaryMessage = Message & { } export type TombstoneMessage = Message export type ToolUseSummaryMessage = Message -export type MessageOrigin = string +export type MessageOrigin = string | { kind: string; [key: string]: unknown } export type CompactMetadata = Record export type SystemAPIErrorMessage = Message & { type: 'system' } export type SystemFileSnapshotMessage = Message & { type: 'system' } @@ -126,7 +144,14 @@ export type RenderableMessage = | AssistantMessage | UserMessage | (Message & { type: 'system' }) - | (Message & { type: 'attachment'; attachment: { type: string; memories?: { path: string; content: string; mtimeMs: number }[]; [key: string]: unknown } }) + | (Message & { + type: 'attachment' + attachment: { + type: string + memories?: { path: string; content: string; mtimeMs: number }[] + [key: string]: unknown + } + }) | (Message & { type: 'progress' }) | GroupedToolUseMessage | CollapsedReadSearchGroup diff --git a/src/utils/computerUse/common.ts b/src/utils/computerUse/common.ts index c70eb1a34..de54a255e 100644 --- a/src/utils/computerUse/common.ts +++ b/src/utils/computerUse/common.ts @@ -3,6 +3,11 @@ import { env } from '../env.js' export const COMPUTER_USE_MCP_SERVER_NAME = 'computer-use' +type CliComputerUseCapabilities = { + screenshotFiltering: 'native' | 'none' + platform: 'darwin' | 'win32' | 'linux' +} + /** * Sentinel bundle ID for the frontmost gate. Claude Code is a terminal — it has * no window. This never matches a real `NSWorkspace.frontmostApplication`, so @@ -52,15 +57,14 @@ export function getTerminalBundleId(): string | null { * takes this shape (no `hostBundleId`, no `teachMode`). */ export const CLI_CU_CAPABILITIES = { - screenshotFiltering: (process.platform === 'darwin' - ? 'native' - : 'none') as any, - platform: (process.platform === 'win32' - ? 'win32' - : process.platform === 'linux' - ? 'linux' - : 'darwin') as any, -} + screenshotFiltering: process.platform === 'darwin' ? 'native' : 'none', + platform: + process.platform === 'win32' + ? 'win32' + : process.platform === 'linux' + ? 'linux' + : 'darwin', +} satisfies CliComputerUseCapabilities export function isComputerUseMCPServer(name: string): boolean { return normalizeNameForMCP(name) === COMPUTER_USE_MCP_SERVER_NAME diff --git a/src/utils/deepLink/protocolHandler.ts b/src/utils/deepLink/protocolHandler.ts index 511754dc7..a6c53e826 100644 --- a/src/utils/deepLink/protocolHandler.ts +++ b/src/utils/deepLink/protocolHandler.ts @@ -94,11 +94,11 @@ export async function handleUrlSchemeLaunch(): Promise { try { const { waitForUrlEvent } = await import('url-handler-napi') - const url = (waitForUrlEvent as any)(5000) + const url = await waitForUrlEvent(5000) if (!url) { return null } - return await handleDeepLinkUri(await url as string) + return await handleDeepLinkUri(url) } catch { // NAPI module not available, or handleDeepLinkUri rejected — not a URL launch return null diff --git a/src/utils/effort.ts b/src/utils/effort.ts index cb19d92c6..90c597156 100644 --- a/src/utils/effort.ts +++ b/src/utils/effort.ts @@ -78,7 +78,7 @@ export function modelSupportsMaxEffort(_model: string): boolean { } export function modelSupportsXhighEffort(_model: string): boolean { - const supported3P = get3PModelCapabilityOverride(_model, 'xhigh_effort' as any) + const supported3P = get3PModelCapabilityOverride(_model, 'xhigh_effort') if (supported3P !== undefined) { return supported3P } diff --git a/src/utils/messageQueueManager.ts b/src/utils/messageQueueManager.ts index 76f162f4a..7e520aea3 100644 --- a/src/utils/messageQueueManager.ts +++ b/src/utils/messageQueueManager.ts @@ -368,7 +368,8 @@ export function isQueuedCommandEditable(cmd: QueuedCommand): boolean { export function isQueuedCommandVisible(cmd: QueuedCommand): boolean { if ( (feature('KAIROS') || feature('KAIROS_CHANNELS')) && - (cmd as any).origin?.kind === 'channel' + typeof cmd.origin === 'object' && + cmd.origin?.kind === 'channel' ) return true return isQueuedCommandEditable(cmd) diff --git a/src/utils/model/modelSupportOverrides.ts b/src/utils/model/modelSupportOverrides.ts index 14ea0de0a..7e842a1ca 100644 --- a/src/utils/model/modelSupportOverrides.ts +++ b/src/utils/model/modelSupportOverrides.ts @@ -4,6 +4,7 @@ import { getAPIProvider } from './providers.js' export type ModelCapabilityOverride = | 'effort' | 'max_effort' + | 'xhigh_effort' | 'thinking' | 'adaptive_thinking' | 'interleaved_thinking' diff --git a/src/utils/plans.ts b/src/utils/plans.ts index 22ca6906f..94fb6a853 100644 --- a/src/utils/plans.ts +++ b/src/utils/plans.ts @@ -362,19 +362,24 @@ export async function persistFileSnapshotIfRemote(): Promise { return } try { - const snapshotFiles: SystemFileSnapshotMessage['snapshotFiles'] = [] + type SnapshotFile = { + key: string + path: string + content: string + } + const snapshotFiles: SnapshotFile[] = [] // Snapshot plan file const plan = getPlan() if (plan) { - (snapshotFiles as any[]).push({ + snapshotFiles.push({ key: 'plan', path: getPlanFilePath(), content: plan, }) } - if ((snapshotFiles as any[]).length === 0) { + if (snapshotFiles.length === 0) { return } diff --git a/src/utils/plugins/marketplaceManager.ts b/src/utils/plugins/marketplaceManager.ts index 258903216..a8e40de04 100644 --- a/src/utils/plugins/marketplaceManager.ts +++ b/src/utils/plugins/marketplaceManager.ts @@ -184,11 +184,15 @@ export function getDeclaredMarketplaces(): Record { // Lowest precedence: implicit < --add-dir < merged settings. // An explicit extraKnownMarketplaces entry for claude-plugins-official // in --add-dir or settings wins. - return { + const declared = { ...implicit, - ...getAddDirExtraMarketplaces(), - ...(getInitialSettings().extraKnownMarketplaces ?? {}), - } as any + ...(getAddDirExtraMarketplaces() as Record), + ...((getInitialSettings().extraKnownMarketplaces ?? {}) as Record< + string, + DeclaredMarketplace + >), + } satisfies Record + return declared } /** diff --git a/src/utils/searchExtraTools.ts b/src/utils/searchExtraTools.ts index d41a7c6b2..34de1cde2 100644 --- a/src/utils/searchExtraTools.ts +++ b/src/utils/searchExtraTools.ts @@ -24,7 +24,7 @@ import { isDeferredTool, SEARCH_EXTRA_TOOLS_TOOL_NAME, } from '@claude-code-best/builtin-tools/tools/SearchExtraToolsTool/prompt.js' -import type { Message } from '../types/message.js' +import type { Message, SystemCompactBoundaryMessage } from '../types/message.js' import { countToolDefinitionTokens, TOOL_TOKEN_COUNT_OVERHEAD, @@ -504,12 +504,15 @@ export function extractDiscoveredToolNames(messages: Message[]): Set { // check rather than isCompactBoundaryMessage — utils/messages.ts imports // from this file, so importing back would be circular. if (msg.type === 'system' && msg.subtype === 'compact_boundary') { - const carried = (msg as any).compactMetadata?.preCompactDiscoveredTools as - | string[] - | undefined + const boundary = msg as SystemCompactBoundaryMessage + const carried = boundary.compactMetadata.preCompactDiscoveredTools if (carried) { - for (const name of carried) discoveredTools.add(name) - carriedFromBoundary += carried.length + if (Array.isArray(carried)) { + for (const name of carried) { + if (typeof name === 'string') discoveredTools.add(name) + } + carriedFromBoundary += carried.length + } } continue } diff --git a/src/utils/sessionRestore.ts b/src/utils/sessionRestore.ts index 93cded34b..e0897399b 100644 --- a/src/utils/sessionRestore.ts +++ b/src/utils/sessionRestore.ts @@ -27,7 +27,7 @@ import type { ContextCollapseSnapshotEntry, PersistedWorktreeSession, } from '../types/logs.js' -import type { Message } from '../types/message.js' +import type { ContentItem, Message } from '../types/message.js' import { renameRecordingForSession } from './asciicast.js' import { clearMemoryFileCaches } from './claudemd.js' import { @@ -78,7 +78,9 @@ function extractTodosFromTranscript(messages: Message[]): TodoList { for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i] if (msg?.type !== 'assistant') continue - const toolUse = (msg.message!.content as any[]).find( + const content = msg.message?.content + if (!Array.isArray(content)) continue + const toolUse = (content as ContentItem[]).find( block => block.type === 'tool_use' && block.name === TODO_WRITE_TOOL_NAME, ) if (!toolUse || toolUse.type !== 'tool_use') continue diff --git a/src/utils/sideQuestion.ts b/src/utils/sideQuestion.ts index 4d2755e20..8cfa6b33c 100644 --- a/src/utils/sideQuestion.ts +++ b/src/utils/sideQuestion.ts @@ -11,6 +11,7 @@ import type { NonNullableUsage } from '../services/api/logging.js' import type { Message, SystemAPIErrorMessage } from '../types/message.js' import { type CacheSafeParams, runForkedAgent } from './forkedAgent.js' import { createUserMessage, extractTextContent } from './messages.js' +import type { APIError } from '@anthropic-ai/sdk' // Pattern to detect "/btw" at start of input (case-insensitive, word boundary) const BTW_PATTERN = /^\/btw\b/gi @@ -125,7 +126,12 @@ ${question}` function extractSideQuestionResponse(messages: Message[]): string | null { // Flatten all assistant content blocks across the per-block messages. const assistantBlocks = messages.flatMap(m => - m.type === 'assistant' ? (m.message!.content as unknown as Array<{ type: string; [key: string]: unknown }>) : [], + m.type === 'assistant' + ? (m.message!.content as unknown as Array<{ + type: string + [key: string]: unknown + }>) + : [], ) if (assistantBlocks.length > 0) { @@ -136,7 +142,8 @@ function extractSideQuestionResponse(messages: Message[]): string | null { // No text — check if the model tried to call a tool despite instructions. const toolUse = assistantBlocks.find(b => b.type === 'tool_use') if (toolUse) { - const toolName = 'name' in toolUse ? (toolUse as any).name : 'a tool' + const toolName = + typeof toolUse.name === 'string' ? toolUse.name : 'a tool' return `(The model tried to call ${toolName} instead of answering directly. Try rephrasing or ask in the main conversation.)` } } @@ -148,7 +155,11 @@ function extractSideQuestionResponse(messages: Message[]): string | null { m.type === 'system' && 'subtype' in m && m.subtype === 'api_error', ) if (apiErr) { - return `(API error: ${formatAPIError(apiErr.error as any)})` + const error = + apiErr.error instanceof Error + ? apiErr.error + : new Error(String(apiErr.error)) + return `(API error: ${formatAPIError(error as APIError)})` } return null diff --git a/src/utils/sliceAnsi.ts b/src/utils/sliceAnsi.ts index 565a5f8cd..50443bb3e 100644 --- a/src/utils/sliceAnsi.ts +++ b/src/utils/sliceAnsi.ts @@ -1,7 +1,9 @@ import { type AnsiCode, + type Char, ansiCodesToString, reduceAnsiCodes, + type Token, tokenize, undoAnsiCodes, } from '@alcalzone/ansi-tokenize' @@ -17,6 +19,10 @@ function filterStartCodes(codes: AnsiCode[]): AnsiCode[] { return codes.filter(c => !isEndCode(c)) } +function isCharToken(token: Token): token is Char { + return token.type === 'char' +} + /** * Slice a string containing ANSI escape codes. * @@ -43,7 +49,13 @@ export default function sliceAnsi( // pass start/end in display cells (via stringWidth), so position must // track the same units. const width = - token.type === 'ansi' ? 0 : token.type === 'char' ? (token.fullWidth ? 2 : stringWidth(token.value)) : 0 + token.type === 'ansi' + ? 0 + : token.type === 'char' + ? token.fullWidth + ? 2 + : stringWidth(token.value) + : 0 // Break AFTER trailing zero-width marks — a combining mark attaches to // the preceding base char, so "भा" (भ + ा, 1 display cell) sliced at @@ -77,7 +89,7 @@ export default function sliceAnsi( } if (include) { - result += (token as any).value + result += isCharToken(token) ? token.value : token.code } position += width diff --git a/src/utils/textHighlighting.ts b/src/utils/textHighlighting.ts index d0d24f7b5..2c941a458 100644 --- a/src/utils/textHighlighting.ts +++ b/src/utils/textHighlighting.ts @@ -8,6 +8,12 @@ import { } from '@alcalzone/ansi-tokenize' import type { Theme } from './theme.js' +type TextToken = Extract + +function isTextToken(token: Token): token is TextToken { + return token.type === 'char' +} + export type TextHighlight = { start: number end: number @@ -126,19 +132,23 @@ class HighlightSegmenter { this.codes.push(token) this.stringPos += token.code.length this.tokenIdx++ - } else { + } else if (isTextToken(token)) { + const tokenValue = token.value const charsNeeded = targetVisiblePos - this.visiblePos - const charsAvailable = (token as any).value.length - this.charIdx + const charsAvailable = tokenValue.length - this.charIdx const charsToTake = Math.min(charsNeeded, charsAvailable) this.stringPos += charsToTake this.visiblePos += charsToTake this.charIdx += charsToTake - if (this.charIdx >= (token as any).value.length) { + if (this.charIdx >= tokenValue.length) { this.tokenIdx++ this.charIdx = 0 } + } else { + this.stringPos += token.code.length + this.tokenIdx++ } } diff --git a/src/utils/worktree.ts b/src/utils/worktree.ts index dc4b3db3a..6f5f2507b 100644 --- a/src/utils/worktree.ts +++ b/src/utils/worktree.ts @@ -1290,10 +1290,10 @@ export async function execIntoTmuxWorktree(args: string[]): Promise<{ worktreeName, prNumber !== null ? { prNumber } : undefined, ) - if (!result.existed) { + if (result.existed === false) { // biome-ignore lint/suspicious/noConsole: intentional console output console.log( - `Created worktree: ${worktreeDir} (based on ${(result as any).baseBranch})`, + `Created worktree: ${worktreeDir} (based on ${result.baseBranch})`, ) await performPostCreationSetup(repoRoot, worktreeDir) }