diff --git a/bun.lock b/bun.lock index f5dfe6836..6d58ae8cc 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,9 @@ "workspaces": { "": { "name": "claude-code", + "dependencies": { + "@types/lodash-es": "^4.17.12", + }, "devDependencies": { "@alcalzone/ansi-tokenize": "^0.3.0", "@ant/claude-for-chrome-mcp": "workspace:*", @@ -51,11 +54,18 @@ "@smithy/node-http-handler": "^4.5.1", "@types/bun": "^1.3.11", "@types/cacache": "^20.0.1", + "@types/picomatch": "^4.0.3", "@types/plist": "^3.0.5", + "@types/proper-lockfile": "^4.1.4", + "@types/qrcode": "^1.5.6", "@types/react": "^19.2.14", "@types/react-reconciler": "^0.33.0", + "@types/semver": "^7.7.1", "@types/sharp": "^0.32.0", + "@types/shell-quote": "^1.7.5", + "@types/stack-utils": "^2.0.3", "@types/turndown": "^5.0.6", + "@types/ws": "^8.18.1", "ajv": "^8.18.0", "asciichart": "^1.5.25", "audio-capture-napi": "workspace:*", @@ -986,16 +996,30 @@ "@types/pg-pool": ["@types/pg-pool@2.0.7", "https://registry.npmmirror.com/@types/pg-pool/-/pg-pool-2.0.7.tgz", { "dependencies": { "@types/pg": "*" } }, "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng=="], + "@types/picomatch": ["@types/picomatch@4.0.3", "https://registry.npmmirror.com/@types/picomatch/-/picomatch-4.0.3.tgz", {}, "sha512-iG0T6+nYJ9FAPmx9SsUlnwcq1ZVRuCXcVEvWnntoPlrOpwtSTKNDC9uVAxTsC3PUvJ+99n4RpAcNgBbHX3JSnQ=="], + "@types/plist": ["@types/plist@3.0.5", "https://registry.npmmirror.com/@types/plist/-/plist-3.0.5.tgz", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], + "@types/proper-lockfile": ["@types/proper-lockfile@4.1.4", "https://registry.npmmirror.com/@types/proper-lockfile/-/proper-lockfile-4.1.4.tgz", { "dependencies": { "@types/retry": "*" } }, "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ=="], + + "@types/qrcode": ["@types/qrcode@1.5.6", "https://registry.npmmirror.com/@types/qrcode/-/qrcode-1.5.6.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], + "@types/react": ["@types/react@19.2.14", "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], "@types/react-reconciler": ["@types/react-reconciler@0.33.0", "https://registry.npmmirror.com/@types/react-reconciler/-/react-reconciler-0.33.0.tgz", { "peerDependencies": { "@types/react": "*" } }, "sha512-HZOXsKT0tGI9LlUw2LuedXsVeB88wFa536vVL0M6vE8zN63nI+sSr1ByxmPToP5K5bukaVscyeCJcF9guVNJ1g=="], + "@types/retry": ["@types/retry@0.12.5", "https://registry.npmmirror.com/@types/retry/-/retry-0.12.5.tgz", {}, "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw=="], + + "@types/semver": ["@types/semver@7.7.1", "https://registry.npmmirror.com/@types/semver/-/semver-7.7.1.tgz", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="], + "@types/sharp": ["@types/sharp@0.32.0", "https://registry.npmmirror.com/@types/sharp/-/sharp-0.32.0.tgz", { "dependencies": { "sharp": "*" } }, "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw=="], + "@types/shell-quote": ["@types/shell-quote@1.7.5", "https://registry.npmmirror.com/@types/shell-quote/-/shell-quote-1.7.5.tgz", {}, "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw=="], + + "@types/stack-utils": ["@types/stack-utils@2.0.3", "https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.3.tgz", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + "@types/tedious": ["@types/tedious@4.0.14", "https://registry.npmmirror.com/@types/tedious/-/tedious-4.0.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], "@types/turndown": ["@types/turndown@5.0.6", "https://registry.npmmirror.com/@types/turndown/-/turndown-5.0.6.tgz", {}, "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg=="], @@ -1004,6 +1028,8 @@ "@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "https://registry.npmmirror.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="], + "@types/ws": ["@types/ws@8.18.1", "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.4", "https://registry.npmmirror.com/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], diff --git a/package.json b/package.json index 7ee0b71a1..266e5c1c9 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,10 @@ "docs:dev": "npx mintlify dev", "rcs": "bun run scripts/rcs.ts" }, - "dependencies": {}, + "dependencies": { + "@types/lodash-es": "^4.17.12" + }, "devDependencies": { - "openai": "^6.33.0", "@alcalzone/ansi-tokenize": "^0.3.0", "@ant/claude-for-chrome-mcp": "workspace:*", "@ant/computer-use-input": "workspace:*", @@ -68,6 +69,7 @@ "@anthropic-ai/sandbox-runtime": "^0.0.44", "@anthropic-ai/sdk": "^0.80.0", "@anthropic-ai/vertex-sdk": "^0.14.4", + "@anthropic/ink": "workspace:*", "@aws-sdk/client-bedrock": "^3.1020.0", "@aws-sdk/client-bedrock-runtime": "^3.1020.0", "@aws-sdk/client-sts": "^3.1020.0", @@ -101,11 +103,18 @@ "@smithy/node-http-handler": "^4.5.1", "@types/bun": "^1.3.11", "@types/cacache": "^20.0.1", + "@types/picomatch": "^4.0.3", "@types/plist": "^3.0.5", + "@types/proper-lockfile": "^4.1.4", + "@types/qrcode": "^1.5.6", "@types/react": "^19.2.14", "@types/react-reconciler": "^0.33.0", + "@types/semver": "^7.7.1", "@types/sharp": "^0.32.0", + "@types/shell-quote": "^1.7.5", + "@types/stack-utils": "^2.0.3", "@types/turndown": "^5.0.6", + "@types/ws": "^8.18.1", "ajv": "^8.18.0", "asciichart": "^1.5.25", "audio-capture-napi": "workspace:*", @@ -132,7 +141,6 @@ "highlight.js": "^11.11.1", "https-proxy-agent": "^8.0.0", "ignore": "^7.0.5", - "@anthropic/ink": "workspace:*", "image-processor-napi": "workspace:*", "indent-string": "^5.0.0", "jsonc-parser": "^3.3.1", @@ -141,6 +149,7 @@ "lru-cache": "^11.2.7", "marked": "^17.0.5", "modifiers-napi": "workspace:*", + "openai": "^6.33.0", "p-map": "^7.0.4", "picomatch": "^4.0.4", "plist": "^3.1.0", diff --git a/packages/@ant/computer-use-mcp/src/toolCalls.ts b/packages/@ant/computer-use-mcp/src/toolCalls.ts index af20a3efd..eb68b0bb3 100644 --- a/packages/@ant/computer-use-mcp/src/toolCalls.ts +++ b/packages/@ant/computer-use-mcp/src/toolCalls.ts @@ -2148,7 +2148,7 @@ async function handleScreenshot( const monitorNote = await buildMonitorNote( adapter, - shot.displayId, + shot.displayId ?? 0, overrides.lastScreenshot?.displayId, overrides.onDisplayPinned !== undefined, ); @@ -2217,7 +2217,7 @@ async function handleScreenshot( const monitorNote = await buildMonitorNote( adapter, - shot.displayId, + shot.displayId ?? 0, overrides.lastScreenshot?.displayId, overrides.onDisplayPinned !== undefined, ); diff --git a/packages/@ant/ink/src/core/bidi.ts b/packages/@ant/ink/src/core/bidi.ts index bed474db8..07502e550 100644 --- a/packages/@ant/ink/src/core/bidi.ts +++ b/packages/@ant/ink/src/core/bidi.ts @@ -16,6 +16,12 @@ */ import bidiFactory from 'bidi-js' +type BidiInstance = { + getEmbeddingLevels: (text: string, defaultDirection?: string) => { paragraphLevel: number; levels: Uint8Array } + getReorderSegments: (text: string, embeddingLevels: { paragraphLevel: number; levels: Uint8Array }, start?: number, end?: number) => [number, number][] + getVisualOrder: (reorderSegments: [number, number][]) => number[] +} + type ClusteredChar = { value: string width: number @@ -23,7 +29,7 @@ type ClusteredChar = { hyperlink: string | undefined } -let bidiInstance: ReturnType | undefined +let bidiInstance: BidiInstance | undefined let needsSoftwareBidi: boolean | undefined function needsBidi(): boolean { @@ -38,7 +44,7 @@ function needsBidi(): boolean { function getBidi() { if (!bidiInstance) { - bidiInstance = bidiFactory() + bidiInstance = (bidiFactory as unknown as () => BidiInstance)() } return bidiInstance } diff --git a/packages/@ant/ink/src/core/ink.tsx b/packages/@ant/ink/src/core/ink.tsx index 12f0c92a5..f3fdb8e6f 100644 --- a/packages/@ant/ink/src/core/ink.tsx +++ b/packages/@ant/ink/src/core/ink.tsx @@ -1883,8 +1883,8 @@ export default class Ink { let reentered = false const intercept = ( chunk: Uint8Array | string, - encodingOrCb?: BufferEncoding | ((err?: Error) => void), - cb?: (err?: Error) => void, + encodingOrCb?: BufferEncoding | ((err?: Error | null) => void), + cb?: (err?: Error | null) => void, ): boolean => { const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb // Reentrancy guard: logger.debug → writeToStderr → here. Pass diff --git a/src/QueryEngine.ts b/src/QueryEngine.ts index 1166151d1..467a8de89 100644 --- a/src/QueryEngine.ts +++ b/src/QueryEngine.ts @@ -563,16 +563,16 @@ export class QueryEngine { for (const msg of messagesFromUserInput) { if ( msg.type === 'user' && - typeof msg.message.content === 'string' && - (msg.message.content.includes(`<${LOCAL_COMMAND_STDOUT_TAG}>`) || - msg.message.content.includes(`<${LOCAL_COMMAND_STDERR_TAG}>`) || + typeof msg.message!.content === 'string' && + (msg.message!.content.includes(`<${LOCAL_COMMAND_STDOUT_TAG}>`) || + msg.message!.content.includes(`<${LOCAL_COMMAND_STDERR_TAG}>`) || msg.isCompactSummary) ) { yield { type: 'user', message: { ...msg.message, - content: stripAnsi(msg.message.content), + content: stripAnsi(msg.message!.content), }, session_id: getSessionId(), parent_tool_use_id: null, @@ -1089,7 +1089,7 @@ export class QueryEngine { const edeResultType = result?.type ?? 'undefined' const edeLastContentType = result?.type === 'assistant' - ? (last(result.message.content)?.type ?? 'none') + ? (last(result.message!.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[])?.type ?? 'none') : 'n/a' // Flush buffered transcript writes before yielding result. @@ -1147,7 +1147,7 @@ export class QueryEngine { let isApiError = false if (result.type === 'assistant') { - const lastContent = last(result.message.content) + const lastContent = last(result.message!.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[]) if ( lastContent?.type === 'text' && !SYNTHETIC_MESSAGES.has(lastContent.text) diff --git a/src/bootstrap/state.ts b/src/bootstrap/state.ts index e160e16f9..c30f3ab66 100644 --- a/src/bootstrap/state.ts +++ b/src/bootstrap/state.ts @@ -1429,7 +1429,7 @@ export function registerHookCallbacks( if (!STATE.registeredHooks[eventKey]) { STATE.registeredHooks[eventKey] = [] } - STATE.registeredHooks[eventKey]!.push(...matchers) + STATE.registeredHooks[eventKey]!.push(...(matchers ?? [])) } } @@ -1451,7 +1451,7 @@ export function clearRegisteredPluginHooks(): void { const filtered: Partial> = {} for (const [event, matchers] of Object.entries(STATE.registeredHooks)) { // Keep only callback hooks (those without pluginRoot) - const callbackHooks = matchers.filter(m => !('pluginRoot' in m)) + const callbackHooks = (matchers ?? []).filter(m => !('pluginRoot' in m)) if (callbackHooks.length > 0) { filtered[event as HookEvent] = callbackHooks } diff --git a/src/bridge/bridgeMessaging.ts b/src/bridge/bridgeMessaging.ts index 389c5739b..eab387fb8 100644 --- a/src/bridge/bridgeMessaging.ts +++ b/src/bridge/bridgeMessaging.ts @@ -105,12 +105,12 @@ export function extractTitleText(m: Message): string | undefined { if (m.type !== 'user' || m.isMeta || m.toolUseResult || m.isCompactSummary) return undefined if (m.origin && (m.origin as { kind?: string }).kind !== 'human') return undefined - const content = m.message.content + const content = m.message!.content let raw: string | undefined if (typeof content === 'string') { raw = content } else { - for (const block of content) { + for (const block of content ?? []) { if (block.type === 'text') { raw = block.text break diff --git a/src/bridge/initReplBridge.ts b/src/bridge/initReplBridge.ts index 48c9f6d39..6d012b93b 100644 --- a/src/bridge/initReplBridge.ts +++ b/src/bridge/initReplBridge.ts @@ -25,6 +25,7 @@ import { waitForPolicyLimitsToLoad, } from '../services/policyLimits/index.js' import type { Message } from '../types/message.js' +import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.js' import { checkAndRefreshOAuthTokenIfNeeded, getClaudeAIOAuthTokens, @@ -289,7 +290,7 @@ export async function initReplBridge( isSyntheticMessage(msg) ) continue - const rawContent = getContentText(msg.message.content) + const rawContent = getContentText(msg.message!.content as string | ContentBlockParam[]) if (!rawContent) continue const derived = deriveTitle(rawContent) if (!derived) continue diff --git a/src/buddy/prompt.ts b/src/buddy/prompt.ts index c5782c0c2..2203702c9 100644 --- a/src/buddy/prompt.ts +++ b/src/buddy/prompt.ts @@ -22,8 +22,8 @@ export function getCompanionIntroAttachment( // Skip if already announced for this companion. for (const msg of messages ?? []) { if (msg.type !== 'attachment') continue - if (msg.attachment.type !== 'companion_intro') continue - if (msg.attachment.name === companion.name) return [] + if (msg.attachment!.type !== 'companion_intro') continue + if (msg.attachment!.name === companion.name) return [] } return [ diff --git a/src/cli/print.ts b/src/cli/print.ts index 662aa865d..affe37e60 100644 --- a/src/cli/print.ts +++ b/src/cli/print.ts @@ -1180,7 +1180,7 @@ function runHeadlessStreaming( removeInterruptedMessage(mutableMessages, turnInterruptionState.message) enqueue({ mode: 'prompt', - value: turnInterruptionState.message.message.content, + value: turnInterruptionState.message.message!.content as string | ContentBlockParam[], uuid: randomUUID(), }) } @@ -1231,13 +1231,13 @@ function runHeadlessStreaming( output.enqueue({ type: 'user', content: crumb.message.content, - message: crumb.message, + message: crumb.message as unknown, session_id: getSessionId(), parent_tool_use_id: null, uuid: crumb.uuid, timestamp: crumb.timestamp, isReplay: true, - } as SDKUserMessageReplay as StdoutMessage) + } as unknown as StdoutMessage) } } } @@ -1969,12 +1969,12 @@ function runHeadlessStreaming( output.enqueue({ type: 'user', content: c.value, - message: { role: 'user', content: c.value }, + message: { role: 'user', content: c.value } as unknown, session_id: getSessionId(), parent_tool_use_id: null, uuid: c.uuid as string, isReplay: true, - } as SDKUserMessageReplay as StdoutMessage) + } as unknown as StdoutMessage) } } } @@ -4090,13 +4090,13 @@ function runHeadlessStreaming( output.enqueue({ type: 'user', content: (userMsg.message as { content?: string })?.content ?? '', - message: userMsg.message as { role: string; content: unknown }, + message: userMsg.message as unknown, session_id: sessionId, parent_tool_use_id: null, uuid: userMsg.uuid as string, timestamp: (userMsg as { timestamp?: string }).timestamp, isReplay: true, - } as unknown as SDKUserMessageReplay as StdoutMessage) + } as unknown as StdoutMessage) } // Historical dup = transcript already has this turn's output, so it // ran but its lifecycle was never closed (interrupted before ack). @@ -4554,7 +4554,7 @@ async function handleRewindFiles( ) return { canRewind: true, - filesChanged: diffStats?.filesChanged, + filesChanged: diffStats?.filesChanged ?? [], insertions: diffStats?.insertions, deletions: diffStats?.deletions, } diff --git a/src/commands.ts b/src/commands.ts index e8aa0e642..defc80f02 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -463,8 +463,8 @@ const loadAllCommands = memoize(async (cwd: string): Promise => { ...bundledSkills, ...builtinPluginSkills, ...skillDirCommands, - ...workflowCommands, - ...pluginCommands, + ...(workflowCommands as Command[]), + ...(pluginCommands as Command[]), ...pluginSkills, ...COMMANDS(), ] diff --git a/src/commands/ant-trace/index.d.ts b/src/commands/ant-trace/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/ant-trace/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/autofix-pr/index.d.ts b/src/commands/autofix-pr/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/autofix-pr/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/backfill-sessions/index.d.ts b/src/commands/backfill-sessions/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/backfill-sessions/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/branch/branch.ts b/src/commands/branch/branch.ts index a4742bf92..50eeb8e78 100644 --- a/src/commands/branch/branch.ts +++ b/src/commands/branch/branch.ts @@ -44,7 +44,7 @@ export function deriveFirstPrompt( typeof content === 'string' ? content : content.find( - (block): block is { type: 'text'; text: string } => + (block: { type: string; text?: string }): block is { type: 'text'; text: string } => block.type === 'text', )?.text if (!raw) return 'Branched conversation' diff --git a/src/commands/break-cache/index.d.ts b/src/commands/break-cache/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/break-cache/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/bridge/bridge.tsx b/src/commands/bridge/bridge.tsx index 78b0341f1..a9d9bc5ac 100644 --- a/src/commands/bridge/bridge.tsx +++ b/src/commands/bridge/bridge.tsx @@ -154,7 +154,7 @@ function BridgeDisconnectDialog({ onDone }: Props): React.ReactNode { type: 'utf8', errorCorrectionLevel: 'L', small: true, - }) + } as Parameters[1]) .then(setQrText) .catch(() => setQrText('')) }, [showQR, displayUrl]) diff --git a/src/commands/btw/btw.tsx b/src/commands/btw/btw.tsx index 753b5821e..7da3141b9 100644 --- a/src/commands/btw/btw.tsx +++ b/src/commands/btw/btw.tsx @@ -162,7 +162,7 @@ function BtwSideQuestion({ */ function stripInProgressAssistantMessage(messages: Message[]): Message[] { const last = messages.at(-1) - if (last?.type === 'assistant' && last.message.stop_reason === null) { + if (last?.type === 'assistant' && last.message!.stop_reason === null) { return messages.slice(0, -1) } return messages diff --git a/src/commands/buddy/buddy.ts b/src/commands/buddy/buddy.ts index 8d14ab4e6..d54a92d93 100644 --- a/src/commands/buddy/buddy.ts +++ b/src/commands/buddy/buddy.ts @@ -128,7 +128,7 @@ export async function call( return React.createElement(CompanionCard, { companion, lastReaction, - onDone, + onDone: onDone as unknown as Parameters[0]['onDone'], }) } diff --git a/src/commands/bughunter/index.d.ts b/src/commands/bughunter/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/bughunter/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/compact/compact.ts b/src/commands/compact/compact.ts index b61e8f8ab..16a8b586d 100644 --- a/src/commands/compact/compact.ts +++ b/src/commands/compact/compact.ts @@ -1,5 +1,6 @@ import { feature } from 'bun:bundle' import chalk from 'chalk' +import type { SDKStatus } from '../../entrypoints/agentSdkTypes.js' import { markPostCompaction } from 'src/bootstrap/state.js' import { getSystemPrompt } from '../../constants/prompts.js' import { getSystemContext, getUserContext } from '../../context.js' @@ -223,7 +224,7 @@ async function compactViaReactive( context.setStreamMode?.('requesting') context.setResponseLength?.(() => 0) context.onCompactProgress?.({ type: 'compact_end' }) - context.setSDKStatus?.(null) + context.setSDKStatus?.("" as SDKStatus) } } diff --git a/src/commands/ctx_viz/index.d.ts b/src/commands/ctx_viz/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/ctx_viz/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/debug-tool-call/index.d.ts b/src/commands/debug-tool-call/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/debug-tool-call/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/env/index.d.ts b/src/commands/env/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/env/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/fork/fork.tsx b/src/commands/fork/fork.tsx index cf120c615..d1053dc15 100644 --- a/src/commands/fork/fork.tsx +++ b/src/commands/fork/fork.tsx @@ -55,7 +55,7 @@ export async function call( AgentTool.call( input, context, - context.canUseTool, + context.canUseTool!, lastAssistantMessage ).catch(error => { logForDebugging(`Fork subagent async error: ${error}`, { level: 'error' }) diff --git a/src/commands/good-claude/index.d.ts b/src/commands/good-claude/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/good-claude/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/install-github-app/types.ts b/src/commands/install-github-app/types.ts index 8caa15694..652e2d705 100644 --- a/src/commands/install-github-app/types.ts +++ b/src/commands/install-github-app/types.ts @@ -1,4 +1,30 @@ -// Auto-generated stub — replace with real implementation -export type Workflow = any; -export type State = any; -export type Warning = any; +export type Workflow = 'claude' | 'claude-review' | string + +export type Warning = { + title: string + message: string + instructions: string[] +} + +export type State = { + step: string + selectedRepoName: string + currentRepo: string + useCurrentRepo: boolean + apiKeyOrOAuthToken: string + useExistingKey: boolean + currentWorkflowInstallStep: number + warnings: Warning[] + secretExists: boolean + secretName: string + useExistingSecret: boolean + workflowExists: boolean + selectedWorkflows: Workflow[] + selectedApiKeyOption: 'existing' | 'new' | 'oauth' + authType: 'api_key' | 'oauth_token' + workflowAction?: string + error?: string + errorReason?: string + errorInstructions?: string[] + [key: string]: unknown +} diff --git a/src/commands/issue/index.d.ts b/src/commands/issue/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/issue/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/mock-limits/index.d.ts b/src/commands/mock-limits/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/mock-limits/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/oauth-refresh/index.d.ts b/src/commands/oauth-refresh/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/oauth-refresh/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/onboarding/index.d.ts b/src/commands/onboarding/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/onboarding/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/perf-issue/index.d.ts b/src/commands/perf-issue/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/perf-issue/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/provider.ts b/src/commands/provider.ts index 8dd166b17..19b19c021 100644 --- a/src/commands/provider.ts +++ b/src/commands/provider.ts @@ -25,7 +25,9 @@ function getEnvVarForProvider(provider: string): string { // Get merged env: process.env + settings.env (from userSettings) function getMergedEnv(): Record { const settings = getSettings_DEPRECATED() - const merged = { ...process.env } + const merged: Record = Object.fromEntries( + Object.entries(process.env).filter((e): e is [string, string] => e[1] !== undefined) + ) if (settings?.env) { Object.assign(merged, settings.env) } diff --git a/src/commands/review/reviewRemote.ts b/src/commands/review/reviewRemote.ts index 24b0f94cd..23d3943ef 100644 --- a/src/commands/review/reviewRemote.ts +++ b/src/commands/review/reviewRemote.ts @@ -23,6 +23,7 @@ import { formatPreconditionError, getRemoteTaskSessionUrl, registerRemoteAgentTask, + type BackgroundRemoteSessionPrecondition, } from '../../tasks/RemoteAgentTask/RemoteAgentTask.js' import { isEnterpriseSubscriber, isTeamSubscriber } from '../../utils/auth.js' import { detectCurrentRepositoryWithHost } from '../../utils/detectRepository.js' @@ -147,7 +148,7 @@ export async function launchRemoteReview( ',', ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, }) - const reasons = blockers.map(formatPreconditionError).join('\n') + const reasons = (blockers as BackgroundRemoteSessionPrecondition[]).map(formatPreconditionError).join('\n') return [ { type: 'text', diff --git a/src/commands/share/index.d.ts b/src/commands/share/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/share/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/summary/index.d.ts b/src/commands/summary/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/summary/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/commands/teleport/index.d.ts b/src/commands/teleport/index.d.ts new file mode 100644 index 000000000..292a8d3fb --- /dev/null +++ b/src/commands/teleport/index.d.ts @@ -0,0 +1,3 @@ +import type { Command } from '../../types/command.js' +declare const _default: Command +export default _default diff --git a/src/components/BridgeDialog.tsx b/src/components/BridgeDialog.tsx index 27707875d..14c8c8ce2 100644 --- a/src/components/BridgeDialog.tsx +++ b/src/components/BridgeDialog.tsx @@ -64,7 +64,7 @@ export function BridgeDialog({ onDone }: Props): React.ReactNode { return } qrToString(displayUrl, { - type: 'utf8', + type: 'terminal', errorCorrectionLevel: 'L', small: true, }) diff --git a/src/components/Feedback.tsx b/src/components/Feedback.tsx index 4c4b8f1dd..56a7ba99c 100644 --- a/src/components/Feedback.tsx +++ b/src/components/Feedback.tsx @@ -613,7 +613,7 @@ async function generateTitle( }, }) - const _firstBlock = response.message.content[0] as unknown as Record | undefined + const _firstBlock = response?.message?.content?.[0] as unknown as Record | undefined const title = _firstBlock?.type === 'text' ? (_firstBlock.text as string) diff --git a/src/components/FeedbackSurvey/useMemorySurvey.tsx b/src/components/FeedbackSurvey/useMemorySurvey.tsx index c0679606d..8ed385f3e 100644 --- a/src/components/FeedbackSurvey/useMemorySurvey.tsx +++ b/src/components/FeedbackSurvey/useMemorySurvey.tsx @@ -35,7 +35,7 @@ function hasMemoryFileRead(messages: Message[]): boolean { if (message.type !== 'assistant') { continue } - const content = message.message.content + const content = message.message!.content if (!Array.isArray(content)) { continue } diff --git a/src/components/FullscreenLayout.tsx b/src/components/FullscreenLayout.tsx index 608c7b1c9..e767f10c3 100644 --- a/src/components/FullscreenLayout.tsx +++ b/src/components/FullscreenLayout.tsx @@ -242,8 +242,8 @@ export function countUnseenAssistantTurns( function assistantHasVisibleText(m: Message): boolean { if (m.type !== 'assistant') return false - if (!Array.isArray(m.message.content)) return false - for (const b of m.message.content) { + if (!Array.isArray(m.message!.content)) return false + for (const b of m.message!.content) { if (typeof b !== 'string' && b.type === 'text' && b.text.trim() !== '') return true } return false diff --git a/src/components/MessageSelector.tsx b/src/components/MessageSelector.tsx index aa73e6145..d41cf36f9 100644 --- a/src/components/MessageSelector.tsx +++ b/src/components/MessageSelector.tsx @@ -748,9 +748,9 @@ function UserMessageOption({ ) } - const content = userMessage.message.content + const content = userMessage.message!.content const lastBlock = - typeof content === 'string' ? null : content[content.length - 1] + typeof content === 'string' ? null : content![content!.length - 1] const rawMessageText = typeof content === 'string' ? content.trim() @@ -897,8 +897,8 @@ export function selectableUserMessagesFilter( return false } if ( - Array.isArray(message.message.content) && - message.message.content[0]?.type === 'tool_result' + Array.isArray(message.message!.content) && + message.message!.content[0]?.type === 'tool_result' ) { return false } @@ -912,9 +912,9 @@ export function selectableUserMessagesFilter( return false } - const content = message.message.content + const content = message.message!.content const lastBlock = - typeof content === 'string' ? null : content[content.length - 1] + typeof content === 'string' ? null : content![content!.length - 1] const messageText = typeof content === 'string' ? content.trim() @@ -960,7 +960,7 @@ export function messagesAfterAreOnlySynthetic( // Assistant with actual content = meaningful if (msg.type === 'assistant') { - const content = msg.message.content + const content = msg.message!.content if (Array.isArray(content)) { const hasMeaningfulContent = content.some( block => diff --git a/src/components/MessageTimestamp.tsx b/src/components/MessageTimestamp.tsx index cb99ffc2a..0aa93dc5c 100644 --- a/src/components/MessageTimestamp.tsx +++ b/src/components/MessageTimestamp.tsx @@ -15,7 +15,7 @@ export function MessageTimestamp({ isTranscriptMode && message.timestamp && message.type === 'assistant' && - (Array.isArray(message.message.content) ? (message.message.content as {type: string}[]).some(c => c.type === 'text') : false) + (Array.isArray(message.message!.content) ? (message.message!.content as {type: string}[]).some(c => c.type === 'text') : false) if (!shouldShowTimestamp) { return null diff --git a/src/components/Messages.tsx b/src/components/Messages.tsx index 60e91c245..82caacd38 100644 --- a/src/components/Messages.tsx +++ b/src/components/Messages.tsx @@ -460,7 +460,7 @@ const MessagesImpl = ({ for (let i = normalizedMessages.length - 1; i >= 0; i--) { const msg = normalizedMessages[i] if (msg?.type === 'assistant') { - const content = msg.message.content as Array<{ type: string }> + const content = msg.message!.content as Array<{ type: string }> // Find the last thinking block in this message for (let j = content.length - 1; j >= 0; j--) { if (content[j]?.type === 'thinking') { @@ -468,7 +468,7 @@ const MessagesImpl = ({ } } } else if (msg?.type === 'user') { - const content = msg.message.content as Array<{ type: string }> + const content = msg.message!.content as Array<{ type: string }> const hasToolResult = content.some( block => block.type === 'tool_result', ) @@ -488,7 +488,7 @@ const MessagesImpl = ({ for (let i = normalizedMessages.length - 1; i >= 0; i--) { const msg = normalizedMessages[i] if (msg?.type === 'user') { - const content = msg.message.content as Array<{ type: string; text?: string }> + const content = msg.message!.content as Array<{ type: string; text?: string }> // Check if any text content is bash output for (const block of content) { if (block.type === 'text') { @@ -741,7 +741,8 @@ const MessagesImpl = ({ (msg: RenderableMessage): boolean => { if (msg.type === 'collapsed_read_search') return true if (msg.type === 'assistant') { - const b = msg.message.content[0] as unknown as AdvisorBlock | undefined + const content = msg.message!.content + const b = (Array.isArray(content) ? content[0] : undefined) as unknown as AdvisorBlock | undefined return ( b != null && isAdvisorBlock(b) && @@ -750,11 +751,11 @@ const MessagesImpl = ({ ) } if (msg.type !== 'user') return false - const b = (msg.message.content as Array<{ type: string; tool_use_id?: string; is_error?: boolean; [key: string]: unknown }>)[0] + const b = (msg.message!.content as Array<{ type: string; tool_use_id?: string; is_error?: boolean; [key: string]: unknown }>)[0] if (b?.type !== 'tool_result' || b.is_error || !msg.toolUseResult) return false const name = lookupsRef.current.toolUseByToolUseID.get( - b.tool_use_id, + b.tool_use_id ?? '', )?.name const tool = name ? findToolByName(tools, name) : undefined return tool?.isResultTruncated?.(msg.toolUseResult as never) ?? false @@ -1111,7 +1112,7 @@ export function shouldRenderStatically( case 'user': case 'assistant': { if (message.type === 'assistant') { - const block = (message.message.content as Array<{ type: string; id?: string }>)[0] + const block = (message.message!.content as Array<{ type: string; id?: string }>)[0] if (block?.type === 'server_tool_use') { return lookups.resolvedToolUseIDs.has(block.id!) } @@ -1142,7 +1143,7 @@ export function shouldRenderStatically( } case 'grouped_tool_use': { const allResolved = message.messages.every(msg => { - const content = (msg.message.content as Array<{ type: string; id?: string }>)[0] + const content = (msg.message!.content as Array<{ type: string; id?: string }>)[0] return ( content?.type === 'tool_use' && lookups.resolvedToolUseIDs.has(content.id!) @@ -1155,5 +1156,7 @@ export function shouldRenderStatically( // (In transcript mode, we already returned true at the top of this function) return false } + default: + return true } } diff --git a/src/components/PromptInput/PromptInput.tsx b/src/components/PromptInput/PromptInput.tsx index d4071391e..418ce82e1 100644 --- a/src/components/PromptInput/PromptInput.tsx +++ b/src/components/PromptInput/PromptInput.tsx @@ -3140,8 +3140,8 @@ function getInitialPasteId(messages: Message[]): number { } } // Check text paste references in message content - if (Array.isArray(message.message.content)) { - for (const block of message.message.content) { + if (Array.isArray(message.message!.content)) { + for (const block of message.message!.content) { if (block.type === 'text') { const refs = parseReferences(block.text) for (const ref of refs) { diff --git a/src/components/messageActions.tsx b/src/components/messageActions.tsx index 57b054009..098aff640 100644 --- a/src/components/messageActions.tsx +++ b/src/components/messageActions.tsx @@ -87,6 +87,7 @@ export function isNavigableMessage(msg: NavigableMessage): boolean { } return false } + return false } type PrimaryInput = { @@ -395,6 +396,7 @@ export function copyTextOf(msg: NavigableMessage): string { return `[${a.type}]` } } + return '' } function toolResultText(r: NormalizedUserMessage): string { diff --git a/src/components/messages/nullRenderingAttachments.ts b/src/components/messages/nullRenderingAttachments.ts index 6cb3a7947..31709f5fe 100644 --- a/src/components/messages/nullRenderingAttachments.ts +++ b/src/components/messages/nullRenderingAttachments.ts @@ -63,6 +63,6 @@ export function isNullRenderingAttachment( ): boolean { return ( msg.type === 'attachment' && - NULL_RENDERING_ATTACHMENT_TYPES.has(msg.attachment.type as Attachment['type']) + NULL_RENDERING_ATTACHMENT_TYPES.has(msg.attachment!.type as Attachment['type']) ) } diff --git a/src/components/permissions/FallbackPermissionRequest.tsx b/src/components/permissions/FallbackPermissionRequest.tsx index 23075aa58..12bb4ef7d 100644 --- a/src/components/permissions/FallbackPermissionRequest.tsx +++ b/src/components/permissions/FallbackPermissionRequest.tsx @@ -53,7 +53,7 @@ export function FallbackPermissionRequest({ event: 'accept', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) @@ -66,7 +66,7 @@ export function FallbackPermissionRequest({ event: 'accept', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) @@ -92,7 +92,7 @@ export function FallbackPermissionRequest({ event: 'reject', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) @@ -111,7 +111,7 @@ export function FallbackPermissionRequest({ event: 'reject', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) diff --git a/src/components/permissions/FilePermissionDialog/useFilePermissionDialog.ts b/src/components/permissions/FilePermissionDialog/useFilePermissionDialog.ts index 3272c967c..7deb84bd2 100644 --- a/src/components/permissions/FilePermissionDialog/useFilePermissionDialog.ts +++ b/src/components/permissions/FilePermissionDialog/useFilePermissionDialog.ts @@ -89,7 +89,7 @@ export function useFilePermissionDialog({ const onChange = useCallback( (option: PermissionOption, input: T, feedback?: string) => { const params: PermissionHandlerParams = { - messageId: toolUseConfirm.assistantMessage.message.id, + messageId: toolUseConfirm.assistantMessage.message.id!, path: filePath, toolUseConfirm, toolPermissionContext, diff --git a/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx b/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx index 4822dac9a..023f8c6bf 100644 --- a/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx +++ b/src/components/permissions/NotebookEditPermissionRequest/NotebookEditToolDiff.tsx @@ -3,6 +3,7 @@ import * as React from 'react' import { Suspense, use, useMemo } from 'react' import { Box, NoSelect, Text } from '@anthropic/ink' import type { + NotebookCell, NotebookCellType, NotebookContent, } from '../../../types/notebook.js' @@ -79,7 +80,7 @@ function NotebookEditToolDiffInner({ } return '' } - const cell = notebookData.cells.find(cell => cell.id === cell_id) + const cell = notebookData.cells.find((cell: NotebookCell) => cell.id === cell_id) if (!cell) { return '' } diff --git a/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx b/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx index d9e4050fc..bd73023dd 100644 --- a/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx +++ b/src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx @@ -129,7 +129,7 @@ export function SkillPermissionRequest( event: 'accept', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) @@ -142,7 +142,7 @@ export function SkillPermissionRequest( event: 'accept', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) @@ -169,7 +169,7 @@ export function SkillPermissionRequest( event: 'accept', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) @@ -201,7 +201,7 @@ export function SkillPermissionRequest( event: 'reject', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) @@ -220,7 +220,7 @@ export function SkillPermissionRequest( event: 'reject', metadata: { language_name: 'none', - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) diff --git a/src/components/permissions/hooks.ts b/src/components/permissions/hooks.ts index 7cba31b7d..e037083db 100644 --- a/src/components/permissions/hooks.ts +++ b/src/components/permissions/hooks.ts @@ -201,7 +201,7 @@ export function usePermissionRequestLogging( event: 'response', metadata: { language_name: unaryEvent.language_name, - message_id: toolUseConfirm.assistantMessage.message.id, + message_id: toolUseConfirm.assistantMessage.message.id!, platform: env.platform, }, }) diff --git a/src/components/permissions/utils.ts b/src/components/permissions/utils.ts index 90b7b0ba1..90f46b984 100644 --- a/src/components/permissions/utils.ts +++ b/src/components/permissions/utils.ts @@ -17,7 +17,7 @@ export function logUnaryPermissionEvent( event, metadata: { language_name: 'none', - message_id, + message_id: message_id!, platform: getHostPlatformForAnalytics(), hasFeedback: hasFeedback ?? false, }, diff --git a/src/components/tasks/BackgroundTasksDialog.tsx b/src/components/tasks/BackgroundTasksDialog.tsx index 30eca7444..9d1d065e1 100644 --- a/src/components/tasks/BackgroundTasksDialog.tsx +++ b/src/components/tasks/BackgroundTasksDialog.tsx @@ -1,5 +1,6 @@ import { feature } from 'bun:bundle' import figures from 'figures' +import type { AgentId } from '../../types/ids.js' import React, { type ReactNode, useEffect, @@ -535,12 +536,12 @@ export function BackgroundTasksDialog({ } onSkipAgent={ task.status === 'running' && skipWorkflowAgent - ? agentId => skipWorkflowAgent(task.id, agentId, setAppState) + ? (agentId: AgentId) => skipWorkflowAgent(task.id, agentId, setAppState) : undefined } onRetryAgent={ task.status === 'running' && retryWorkflowAgent - ? agentId => retryWorkflowAgent(task.id, agentId, setAppState) + ? (agentId: AgentId) => retryWorkflowAgent(task.id, agentId, setAppState) : undefined } onBack={goBackToList} diff --git a/src/dialogLaunchers.tsx b/src/dialogLaunchers.tsx index ab903ca1b..e39c42119 100644 --- a/src/dialogLaunchers.tsx +++ b/src/dialogLaunchers.tsx @@ -87,7 +87,7 @@ export async function launchAssistantSessionChooser( return showSetupDialog(root, done => ( done(id)} + onSelect={(id: string) => done(id)} onCancel={() => done(null)} /> )) diff --git a/src/hooks/toolPermission/PermissionContext.ts b/src/hooks/toolPermission/PermissionContext.ts index 4b83bcf71..21d6020a1 100644 --- a/src/hooks/toolPermission/PermissionContext.ts +++ b/src/hooks/toolPermission/PermissionContext.ts @@ -102,7 +102,7 @@ function createPermissionContext( setToolPermissionContext: (context: ToolPermissionContext) => void, queueOps?: PermissionQueueOps, ) { - const messageId = assistantMessage.message.id + const messageId = assistantMessage.message.id! const ctx = { tool, input, @@ -234,7 +234,7 @@ function createPermissionContext( const finalInput = decision.updatedInput ?? updatedInput ?? input return await this.handleHookAllow( finalInput, - decision.updatedPermissions ?? [], + (decision.updatedPermissions ?? []) as unknown as import('../../types/permissions.js').PermissionUpdate[], permissionPromptStartTimeMs, ) } else if (decision.behavior === 'deny') { diff --git a/src/hooks/useCanUseTool.tsx b/src/hooks/useCanUseTool.tsx index cf8af94f8..8f76743b7 100644 --- a/src/hooks/useCanUseTool.tsx +++ b/src/hooks/useCanUseTool.tsx @@ -152,7 +152,7 @@ function useCanUseTool( tool, input, toolUseContext, - messageId: ctx.messageId, + messageId: ctx.messageId!, toolUseID, }, { decision: 'reject', source: 'config' }, diff --git a/src/hooks/useIssueFlagBanner.ts b/src/hooks/useIssueFlagBanner.ts index adb308380..da4f07527 100644 --- a/src/hooks/useIssueFlagBanner.ts +++ b/src/hooks/useIssueFlagBanner.ts @@ -47,7 +47,7 @@ export function isSessionContainerCompatible(messages: Message[]): boolean { if (msg.type !== 'assistant') { continue } - const content = msg.message.content + const content = msg.message!.content if (!Array.isArray(content)) { continue } diff --git a/src/hooks/useTurnDiffs.ts b/src/hooks/useTurnDiffs.ts index 01a47bc68..c78ff59af 100644 --- a/src/hooks/useTurnDiffs.ts +++ b/src/hooks/useTurnDiffs.ts @@ -69,7 +69,7 @@ function countHunkLines(hunks: StructuredPatchHunk[]): { function getUserPromptPreview(message: Message): string { if (message.type !== 'user') return '' - const content = message.message.content + const content = message.message!.content const text = typeof content === 'string' ? content : '' // Truncate to ~30 chars if (text.length <= 30) return text @@ -124,8 +124,8 @@ export function useTurnDiffs(messages: Message[]): TurnDiff[] { // Check if this is a user prompt (not a tool result) const isToolResult = message.toolUseResult || - (Array.isArray(message.message.content) && - message.message.content[0]?.type === 'tool_result') + (Array.isArray(message.message!.content) && + message.message!.content[0]?.type === 'tool_result') if (!isToolResult && !message.isMeta) { // Start a new turn on user prompt diff --git a/src/main.tsx b/src/main.tsx index 4318003d1..b53e47bf6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4458,7 +4458,7 @@ async function run(): Promise { ...(uploaderReady && { onTurnComplete: (messages: MessageType[]) => { void uploaderReady.then((uploader) => - uploader?.(messages), + (uploader as ((msgs: MessageType[]) => void) | null)?.(messages), ); }, }), @@ -4616,13 +4616,13 @@ async function run(): Promise { createLocalSSHSession, SSHSessionError, } = await import("./ssh/createSSHSession.js"); - let sshSession; + let sshSession: import('./ssh/createSSHSession.js').SSHSession | undefined; try { if (_pendingSSH.local) { process.stderr.write( "Starting local ssh-proxy test session...\n", ); - sshSession = createLocalSSHSession({ + sshSession = await createLocalSSHSession({ cwd: _pendingSSH.cwd, permissionMode: _pendingSSH.permissionMode, dangerouslySkipPermissions: @@ -4649,7 +4649,7 @@ async function run(): Promise { }, isTTY ? { - onProgress: (msg) => { + onProgress: (msg: string) => { hadProgress = true; process.stderr.write( `\r ${msg}\x1b[K`, diff --git a/src/query.ts b/src/query.ts index fec85afed..9e5c7ed46 100644 --- a/src/query.ts +++ b/src/query.ts @@ -803,7 +803,7 @@ async function* queryLoop( if ( contextCollapse?.isWithheldPromptTooLong( message as Message, - isPromptTooLongMessage, + isPromptTooLongMessage as (msg: Message) => boolean, querySource, ) ) { @@ -1084,7 +1084,7 @@ async function* queryLoop( // prevents a spiral and the error surfaces. const isWithheldMedia = mediaRecoveryEnabled && - reactiveCompact?.isWithheldMediaSizeError(lastMessage) + reactiveCompact?.isWithheldMediaSizeError(lastMessage as Message) if (isWithheld413) { // First: drain all staged context-collapses. Gated on the PREVIOUS // transition not being collapse_drain_retry — if we already drained @@ -1173,8 +1173,8 @@ async function* queryLoop( // so hooks have nothing meaningful to evaluate. Running stop hooks // on prompt-too-long creates a death spiral: error → hook blocking // → retry → error → … (the hook injects more tokens each cycle). - yield lastMessage - void executeStopFailureHooks(lastMessage, toolUseContext) + yield lastMessage! + void executeStopFailureHooks(lastMessage!, toolUseContext) return { reason: isWithheldMedia ? 'image_error' : 'prompt_too_long' } } else if (feature('CONTEXT_COLLAPSE') && isWithheld413) { // reactiveCompact compiled out but contextCollapse withheld and @@ -1390,7 +1390,7 @@ async function* queryLoop( if ( update.message.type === 'attachment' && - update.message.attachment.type === 'hook_stopped_continuation' + update.message.attachment!.type === 'hook_stopped_continuation' ) { shouldPreventContinuation = true } diff --git a/src/query/stopHooks.ts b/src/query/stopHooks.ts index b6cba6b96..457fe03ea 100644 --- a/src/query/stopHooks.ts +++ b/src/query/stopHooks.ts @@ -148,7 +148,7 @@ export async function* handleStopHooks( // but before gracefulShutdownSync (see drainPendingExtraction). void extractMemoriesModule!.executeExtractMemories( stopHookContext, - toolUseContext.appendSystemMessage, + toolUseContext.appendSystemMessage as ((msg: import('../types/message.js').SystemMessage) => void) | undefined, ) } if (!toolUseContext.agentId) { @@ -215,7 +215,7 @@ export async function* handleStopHooks( } // Track errors and output from attachments if (result.message.type === 'attachment') { - const attachment = result.message.attachment + const attachment = result.message.attachment! if ( 'hookEvent' in attachment && (attachment.hookEvent === 'Stop' || diff --git a/src/remote/sdkMessageAdapter.ts b/src/remote/sdkMessageAdapter.ts index cdfceb46a..60390adfb 100644 --- a/src/remote/sdkMessageAdapter.ts +++ b/src/remote/sdkMessageAdapter.ts @@ -32,8 +32,8 @@ import { createUserMessage } from '../utils/messages.js' function convertAssistantMessage(msg: SDKAssistantMessage): AssistantMessage { return { type: 'assistant', - message: msg.message, - uuid: msg.uuid, + message: msg.message!, + uuid: msg.uuid!, requestId: undefined, timestamp: new Date().toISOString(), error: msg.error, @@ -64,7 +64,7 @@ function convertResultMessage(msg: SDKResultMessage): SystemMessage { subtype: 'informational', content, level: isError ? 'warning' : 'info', - uuid: msg.uuid, + uuid: msg.uuid!, timestamp: new Date().toISOString(), } } @@ -78,7 +78,7 @@ function convertInitMessage(msg: SDKSystemMessage): SystemMessage { subtype: 'informational', content: `Remote session initialized (model: ${msg.model})`, level: 'info', - uuid: msg.uuid, + uuid: msg.uuid!, timestamp: new Date().toISOString(), } } @@ -99,7 +99,7 @@ function convertStatusMessage(msg: SDKStatusMessage): SystemMessage | null { ? 'Compacting conversation…' : `Status: ${msg.status}`, level: 'info', - uuid: msg.uuid, + uuid: msg.uuid!, timestamp: new Date().toISOString(), } } @@ -117,7 +117,7 @@ function convertToolProgressMessage( subtype: 'informational', content: `Tool ${msg.tool_name} running for ${msg.elapsed_time_seconds}s…`, level: 'info', - uuid: msg.uuid, + uuid: msg.uuid!, timestamp: new Date().toISOString(), toolUseID: msg.tool_use_id, } @@ -134,7 +134,7 @@ function convertCompactBoundaryMessage( subtype: 'compact_boundary', content: 'Conversation compacted', level: 'info', - uuid: msg.uuid, + uuid: msg.uuid!, timestamp: new Date().toISOString(), compactMetadata: fromSDKCompactMetadata(msg.compact_metadata), } diff --git a/src/screens/REPL.tsx b/src/screens/REPL.tsx index 3cd1a775e..d6a134c45 100644 --- a/src/screens/REPL.tsx +++ b/src/screens/REPL.tsx @@ -2974,11 +2974,11 @@ export function REPL({ for (const m of messagesRef.current) { if ( m.type === 'attachment' && - m.attachment.type === 'queued_command' && - m.attachment.commandMode === 'task-notification' && - typeof m.attachment.prompt === 'string' + m.attachment!.type === 'queued_command' && + m.attachment!.commandMode === 'task-notification' && + typeof m.attachment!.prompt === 'string' ) { - existingPrompts.add(m.attachment.prompt); + existingPrompts.add(m.attachment!.prompt); } } const uniqueNotifications = notificationMessages.filter( @@ -3156,7 +3156,7 @@ export function REPL({ // title silently fell through to the "Claude Code" default. if (!titleDisabled && !sessionTitle && !agentTitle && !haikuTitleAttemptedRef.current) { const firstUserMessage = newMessages.find(m => m.type === 'user' && !m.isMeta); - const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message.content) : null; + const text = firstUserMessage?.type === 'user' ? getContentText(firstUserMessage.message!.content as string | ContentBlockParam[]) : null; // Skip synthetic breadcrumbs — slash-command output, prompt-skill // expansions (/commit → ), local-command headers // (/help → ), and bash-mode (!cmd → ). @@ -3419,7 +3419,7 @@ export function REPL({ // replayed as user-visible text. newMessages .filter((m): m is UserMessage => m.type === 'user' && !m.isMeta) - .map(_ => getContentText(_.message.content)) + .map(_ => getContentText(_.message.content as string | ContentBlockParam[])) .filter(_ => _ !== null) .forEach((msg, i) => { enqueue({ value: msg, mode: 'prompt' }); @@ -3658,13 +3658,13 @@ export function REPL({ ...prev, initialMessage: null, toolPermissionContext: updatedToolPermissionContext, - ...(shouldStorePlanForVerification && { + ...(shouldStorePlanForVerification ? { pendingPlanVerification: { plan: initialMsg.message.planContent as string, verificationStarted: false, verificationCompleted: false, }, - }), + } : {}), }; }); @@ -4381,7 +4381,7 @@ export function REPL({ const newPastedContents: Record = {}; imageBlocks.forEach((block, index) => { if (block.source.type === 'base64') { - const id = message.imagePasteIds?.[index] ?? index + 1; + const id = (message.imagePasteIds as number[] | undefined)?.[index] ?? index + 1; newPastedContents[id] = { id, type: 'image', @@ -4880,7 +4880,7 @@ export function REPL({ // Count completed hooks const completedCount = count(messages, m => { if (m.type !== 'attachment') return false; - const attachment = m.attachment; + const attachment = m.attachment!; return ( 'hookEvent' in attachment && (attachment.hookEvent === 'Stop' || attachment.hookEvent === 'SubagentStop') && diff --git a/src/services/AgentSummary/agentSummary.ts b/src/services/AgentSummary/agentSummary.ts index d034a6c53..26a9ece65 100644 --- a/src/services/AgentSummary/agentSummary.ts +++ b/src/services/AgentSummary/agentSummary.ts @@ -130,7 +130,7 @@ export function startAgentSummarization( ) continue } - const contentArr = Array.isArray(msg.message.content) ? msg.message.content : [] + const contentArr = Array.isArray(msg.message!.content) ? msg.message!.content : [] const textBlock = contentArr.find(b => b.type === 'text') if (textBlock?.type === 'text' && textBlock.text.trim()) { const summaryText = textBlock.text.trim() diff --git a/src/services/PromptSuggestion/promptSuggestion.ts b/src/services/PromptSuggestion/promptSuggestion.ts index 69412f783..c54df2a40 100644 --- a/src/services/PromptSuggestion/promptSuggestion.ts +++ b/src/services/PromptSuggestion/promptSuggestion.ts @@ -243,11 +243,11 @@ export function getParentCacheSuppressReason( ): string | null { if (!lastAssistantMessage) return null - const usage = lastAssistantMessage.message.usage - const inputTokens = usage.input_tokens ?? 0 - const cacheWriteTokens = usage.cache_creation_input_tokens ?? 0 + const usage = lastAssistantMessage.message!.usage + const inputTokens = usage!.input_tokens ?? 0 + const cacheWriteTokens = usage!.cache_creation_input_tokens ?? 0 // The fork re-processes the parent's output (never cached) plus its own prompt. - const outputTokens = usage.output_tokens ?? 0 + const outputTokens = usage!.output_tokens ?? 0 return (inputTokens as number) + (cacheWriteTokens as number) + (outputTokens as number) > MAX_PARENT_UNCACHED_TOKENS @@ -339,7 +339,7 @@ export async function generateSuggestion( for (const msg of result.messages) { if (msg.type !== 'assistant') continue - const contentArr = Array.isArray(msg.message.content) ? msg.message.content as Array<{ type: string; text?: string }> : [] + const contentArr = Array.isArray(msg.message!.content) ? msg.message!.content as Array<{ type: string; text?: string }> : [] const textBlock = contentArr.find(b => b.type === 'text') if (textBlock?.type === 'text' && typeof textBlock.text === 'string') { const suggestion = textBlock.text.trim() diff --git a/src/services/PromptSuggestion/speculation.ts b/src/services/PromptSuggestion/speculation.ts index 0d96557fe..de0b523a6 100644 --- a/src/services/PromptSuggestion/speculation.ts +++ b/src/services/PromptSuggestion/speculation.ts @@ -197,7 +197,7 @@ function getBoundaryDetail( function isUserMessageWithArrayContent( m: Message, ): m is Message & { message: { content: unknown[] } } { - return m.type === 'user' && 'message' in m && Array.isArray(m.message.content) + return m.type === 'user' && 'message' in m && Array.isArray(m.message?.content) } export function prepareMessagesForInjection(messages: Message[]): Message[] { @@ -254,9 +254,9 @@ export function prepareMessagesForInjection(messages: Message[]): Message[] { return messages .map(msg => { - if (!('message' in msg) || !Array.isArray(msg.message.content)) return msg - const content = msg.message.content.filter(keep) - if (content.length === msg.message.content.length) return msg + if (!('message' in msg) || !Array.isArray(msg.message?.content)) return msg + const content = msg.message!.content.filter(keep) + if (content.length === msg.message!.content.length) return msg if (content.length === 0) return null // Drop messages where all remaining blocks are whitespace-only text // (API rejects these with 400: "text content blocks must contain non-whitespace text") diff --git a/src/services/SessionMemory/sessionMemory.ts b/src/services/SessionMemory/sessionMemory.ts index b0c67fba8..32e42af8b 100644 --- a/src/services/SessionMemory/sessionMemory.ts +++ b/src/services/SessionMemory/sessionMemory.ts @@ -121,7 +121,7 @@ function countToolCallsSince( } if (message.type === 'assistant') { - const content = message.message.content + const content = message.message!.content if (Array.isArray(content)) { toolCallCount += count(content, block => block.type === 'tool_use') } diff --git a/src/services/api/claude.ts b/src/services/api/claude.ts index ee7f2e361..5e31a3f16 100644 --- a/src/services/api/claude.ts +++ b/src/services/api/claude.ts @@ -579,13 +579,13 @@ export function userMessageToMessageParam( querySource?: QuerySource, ): MessageParam { if (addCache) { - if (typeof message.message.content === 'string') { + if (typeof message.message!.content === 'string') { return { role: 'user', content: [ { type: 'text', - text: message.message.content, + text: message.message!.content, ...(enablePromptCaching && { cache_control: getCacheControl({ querySource }), }), @@ -595,9 +595,9 @@ export function userMessageToMessageParam( } else { return { role: 'user', - content: message.message.content.map((_, i) => ({ + content: message.message!.content!.map((_, i) => ({ ..._, - ...(i === message.message.content.length - 1 + ...(i === message.message!.content!.length - 1 ? enablePromptCaching ? { cache_control: getCacheControl({ querySource }) } : {} @@ -611,9 +611,9 @@ export function userMessageToMessageParam( // to addCacheBreakpoints share the same array and each splices in duplicate cache_edits. return { role: 'user', - content: Array.isArray(message.message.content) - ? [...message.message.content] - : message.message.content, + content: (Array.isArray(message.message!.content) + ? [...message.message!.content] + : message.message!.content) as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlockParam[], } } @@ -624,13 +624,13 @@ export function assistantMessageToMessageParam( querySource?: QuerySource, ): MessageParam { if (addCache) { - if (typeof message.message.content === 'string') { + if (typeof message.message!.content === 'string') { return { role: 'assistant', content: [ { type: 'text', - text: message.message.content, + text: message.message!.content, ...(enablePromptCaching && { cache_control: getCacheControl({ querySource }), }), @@ -640,11 +640,11 @@ export function assistantMessageToMessageParam( } else { return { role: 'assistant', - content: message.message.content.map((_, i) => { + content: message.message!.content!.map((_, i) => { const contentBlock = stripGeminiProviderMetadata(_) return { ...contentBlock, - ...(i === message.message.content.length - 1 && + ...(i === message.message!.content!.length - 1 && contentBlock.type !== 'thinking' && contentBlock.type !== 'redacted_thinking' && (feature('CONNECTOR_TEXT') @@ -662,9 +662,9 @@ export function assistantMessageToMessageParam( return { role: 'assistant', content: - typeof message.message.content === 'string' - ? message.message.content - : message.message.content.map(stripGeminiProviderMetadata) as BetaContentBlockParam[], + typeof message.message!.content === 'string' + ? message.message!.content + : message.message!.content!.map(stripGeminiProviderMetadata) as BetaContentBlockParam[], } } @@ -972,8 +972,8 @@ export function stripExcessMediaItems( ): (UserMessage | AssistantMessage)[] { let toRemove = 0 for (const msg of messages) { - if (!Array.isArray(msg.message.content)) continue - for (const block of msg.message.content) { + if (!Array.isArray(msg.message!.content)) continue + for (const block of msg.message!.content) { if (isMedia(block)) toRemove++ if (isToolResult(block) && Array.isArray(block.content)) { for (const nested of block.content) { @@ -987,7 +987,7 @@ export function stripExcessMediaItems( return messages.map(msg => { if (toRemove <= 0) return msg - const content = msg.message.content + const content = msg.message!.content if (!Array.isArray(content)) return msg const before = toRemove diff --git a/src/services/api/errors.ts b/src/services/api/errors.ts index 6ab10fc80..a33ba8cf7 100644 --- a/src/services/api/errors.ts +++ b/src/services/api/errors.ts @@ -65,7 +65,7 @@ export function isPromptTooLongMessage(msg: AssistantMessage): boolean { if (!msg.isApiErrorMessage) { return false } - const content = msg.message.content + const content = msg.message!.content if (!Array.isArray(content)) { return false } @@ -230,7 +230,7 @@ function logToolUseToolResultMismatch( for (let i = 0; i < messagesForAPI.length; i++) { const msg = messagesForAPI[i] if (!msg) continue - const content = msg.message.content + const content = msg.message!.content if (Array.isArray(content)) { for (const block of content) { if ( @@ -252,7 +252,7 @@ function logToolUseToolResultMismatch( const msg = messages[i] if (!msg) continue if (msg.type === 'assistant' && 'message' in msg) { - const content = msg.message.content + const content = msg.message!.content if (Array.isArray(content)) { for (const block of content) { if ( @@ -274,10 +274,10 @@ function logToolUseToolResultMismatch( for (let i = normalizedIndex + 1; i < messagesForAPI.length; i++) { const msg = messagesForAPI[i] if (!msg) continue - const content = msg.message.content + const content = msg.message!.content if (Array.isArray(content)) { for (const block of content) { - const role = msg.message.role + const role = msg.message!.role if (block.type === 'tool_use' && 'id' in block) { normalizedSeq.push(`${role}:tool_use:${block.id}`) } else if (block.type === 'tool_result' && 'tool_use_id' in block) { @@ -307,10 +307,10 @@ function logToolUseToolResultMismatch( case 'user': case 'assistant': { if ('message' in msg) { - const content = msg.message.content + const content = msg.message!.content if (Array.isArray(content)) { for (const block of content) { - const role = msg.message.role + const role = msg.message!.role if (block.type === 'tool_use' && 'id' in block) { preNormalizedSeq.push(`${role}:tool_use:${block.id}`) } else if ( @@ -331,14 +331,14 @@ function logToolUseToolResultMismatch( } } } else if (typeof content === 'string') { - preNormalizedSeq.push(`${msg.message.role}:string_content`) + preNormalizedSeq.push(`${msg.message!.role}:string_content`) } } break } case 'attachment': if ('attachment' in msg) { - preNormalizedSeq.push(`attachment:${msg.attachment.type}`) + preNormalizedSeq.push(`attachment:${msg.attachment!.type}`) } break case 'system': diff --git a/src/services/api/gemini/convertMessages.ts b/src/services/api/gemini/convertMessages.ts index ef949f5fb..0bdf22223 100644 --- a/src/services/api/gemini/convertMessages.ts +++ b/src/services/api/gemini/convertMessages.ts @@ -84,7 +84,7 @@ function convertInternalUserMessage( return { role: 'user', parts: content.flatMap(block => - convertUserContentBlockToGeminiParts(block, toolNamesById), + convertUserContentBlockToGeminiParts(block as unknown as string | Record, toolNamesById), ), } } diff --git a/src/services/api/gemini/streamAdapter.ts b/src/services/api/gemini/streamAdapter.ts index 56a30c3e7..d40980e04 100644 --- a/src/services/api/gemini/streamAdapter.ts +++ b/src/services/api/gemini/streamAdapter.ts @@ -45,9 +45,8 @@ export async function* adaptGeminiStreamToAnthropic( cache_read_input_tokens: 0, }, }, - } as BetaRawMessageStreamEvent + } as unknown as BetaRawMessageStreamEvent } - const candidate = chunk.candidates?.[0] const parts = candidate?.content?.parts ?? [] diff --git a/src/services/api/openai/__tests__/thinking.test.ts b/src/services/api/openai/__tests__/thinking.test.ts index 1a0b38fd8..48d754bb5 100644 --- a/src/services/api/openai/__tests__/thinking.test.ts +++ b/src/services/api/openai/__tests__/thinking.test.ts @@ -168,7 +168,7 @@ describe('buildOpenAIRequestBody — thinking params', () => { const body = buildOpenAIRequestBody({ ...baseParams, enableThinking: true }) expect(body.thinking).toEqual({ type: 'enabled' }) expect(body.enable_thinking).toBe(true) - expect(body.chat_template_kwargs.thinking).toBe(true) + expect(body.chat_template_kwargs!.thinking).toBe(true) }) test('does NOT include thinking params when disabled', () => { diff --git a/src/services/api/openai/index.ts b/src/services/api/openai/index.ts index 443957c76..74b112193 100644 --- a/src/services/api/openai/index.ts +++ b/src/services/api/openai/index.ts @@ -6,6 +6,7 @@ import type { SystemAPIErrorMessage, AssistantMessage, } from '../../../types/message.js' +import type { AgentId } from '../../../types/ids.js' import type { Tools } from '../../../Tool.js' import type { Stream } from 'openai/streaming.mjs' import type { @@ -149,7 +150,7 @@ function assembleFinalAssistantOutputs(params: { outputs.push({ message: { ...partialMessage, - content: normalizeContentFromAPI(allBlocks, tools, agentId), + content: normalizeContentFromAPI(allBlocks, tools, agentId as AgentId | undefined), usage, stop_reason: stopReason, stop_sequence: null, diff --git a/src/services/api/openai/streamAdapter.ts b/src/services/api/openai/streamAdapter.ts index 0f53a26cb..70f7161ff 100644 --- a/src/services/api/openai/streamAdapter.ts +++ b/src/services/api/openai/streamAdapter.ts @@ -111,9 +111,10 @@ export async function* adaptOpenAIStreamToAnthropic( cache_read_input_tokens: cachedReadTokens, }, }, - } as BetaRawMessageStreamEvent + } as unknown as BetaRawMessageStreamEvent } + // Skip chunks that carry only usage data (no delta content) if (!delta) continue // Handle reasoning_content → Anthropic thinking block diff --git a/src/services/autoDream/autoDream.ts b/src/services/autoDream/autoDream.ts index 1afc9aaee..12a8d4a75 100644 --- a/src/services/autoDream/autoDream.ts +++ b/src/services/autoDream/autoDream.ts @@ -288,7 +288,7 @@ function makeDreamProgressWatcher( let text = '' let toolUseCount = 0 const touchedPaths: string[] = [] - const contentBlocks = msg.message.content as ContentBlockParam[] + const contentBlocks = msg.message!.content as ContentBlockParam[] for (const block of contentBlocks) { if (block.type === 'text') { text += block.text diff --git a/src/services/compact/__tests__/grouping.test.ts b/src/services/compact/__tests__/grouping.test.ts index c59f75437..fac5aa56d 100644 --- a/src/services/compact/__tests__/grouping.test.ts +++ b/src/services/compact/__tests__/grouping.test.ts @@ -93,8 +93,8 @@ describe("groupMessagesByApiRound", () => { test("preserves message order within groups", () => { const messages = [makeMsg("assistant", "a1"), makeMsg("user", "u2")]; const groups = groupMessagesByApiRound(messages); - expect(groups[0][0].message.id).toBe("a1"); - expect(groups[0][1].message.id).toBe("u2"); + expect(groups[0]![0]!.message!.id).toBe("a1"); + expect(groups[0]![1]!.message!.id).toBe("u2"); }); test("handles system messages", () => { diff --git a/src/services/compact/compact.ts b/src/services/compact/compact.ts index 4be8b45df..8e3e0560d 100644 --- a/src/services/compact/compact.ts +++ b/src/services/compact/compact.ts @@ -39,6 +39,7 @@ import { getAgentListingDeltaAttachment, getDeferredToolsDeltaAttachment, getMcpInstructionsDeltaAttachment, + type Attachment, } from '../../utils/attachments.js' import { getMemoryPath } from '../../utils/config.js' import { COMPACT_MAX_OUTPUT_TOKENS } from '../../utils/context.js' @@ -114,6 +115,7 @@ import { roughTokenCountEstimation, roughTokenCountEstimationForMessages, } from '../tokenEstimation.js' +import type { SDKStatus } from '../../entrypoints/agentSdkTypes.js' import { groupMessagesByApiRound } from './grouping.js' import { getCompactPrompt, @@ -150,7 +152,7 @@ export function stripImagesFromMessages(messages: Message[]): Message[] { return message } - const content = message.message.content + const content = message.message!.content if (!Array.isArray(content)) { return message } @@ -216,8 +218,8 @@ export function stripReinjectedAttachments(messages: Message[]): Message[] { m => !( m.type === 'attachment' && - (m.attachment.type === 'skill_discovery' || - m.attachment.type === 'skill_listing') + (m.attachment!.type === 'skill_discovery' || + m.attachment!.type === 'skill_listing') ), ) } @@ -251,8 +253,8 @@ export function truncateHeadForPTLRetry( // (drops only the marker, re-adds it, zero progress on retry 2+). const input = messages[0]?.type === 'user' && - messages[0].isMeta && - messages[0].message.content === PTL_RETRY_MARKER + messages[0]?.isMeta && + messages[0]?.message?.content === PTL_RETRY_MARKER ? messages.slice(1) : messages @@ -760,7 +762,7 @@ export async function compactConversation( context.setStreamMode?.('requesting') context.setResponseLength?.(() => 0) context.onCompactProgress?.({ type: 'compact_end' }) - context.setSDKStatus?.(null) + context.setSDKStatus?.("" as SDKStatus) } } @@ -1103,7 +1105,7 @@ export async function partialCompactConversation( context.setStreamMode?.('requesting') context.setResponseLength?.(() => 0) context.onCompactProgress?.({ type: 'compact_end' }) - context.setSDKStatus?.(null) + context.setSDKStatus?.("" as SDKStatus) } } @@ -1453,7 +1455,7 @@ export async function createPostCompactFileAttachments( ) let usedTokens = 0 - return results.filter((result): result is AttachmentMessage => { + return results.filter((result): result is AttachmentMessage => { if (result === null) { return false } @@ -1613,10 +1615,10 @@ export async function createAsyncAgentAttachmentsIfNeeded( function collectReadToolFilePaths(messages: Message[]): Set { const stubIds = new Set() for (const message of messages) { - if (message.type !== 'user' || !Array.isArray(message.message.content)) { + if (message.type !== 'user' || !Array.isArray(message.message!.content)) { continue } - for (const block of message.message.content) { + for (const block of message.message!.content) { if ( block.type === 'tool_result' && typeof block.content === 'string' && @@ -1631,11 +1633,11 @@ function collectReadToolFilePaths(messages: Message[]): Set { for (const message of messages) { if ( message.type !== 'assistant' || - !Array.isArray(message.message.content) + !Array.isArray(message.message!.content) ) { continue } - for (const block of message.message.content) { + for (const block of message.message!.content) { if ( block.type !== 'tool_use' || block.name !== FILE_READ_TOOL_NAME || diff --git a/src/services/compact/grouping.ts b/src/services/compact/grouping.ts index 66437e9cc..92cdf3278 100644 --- a/src/services/compact/grouping.ts +++ b/src/services/compact/grouping.ts @@ -43,7 +43,7 @@ export function groupMessagesByApiRound(messages: Message[]): Message[][] { for (const msg of messages) { if ( msg.type === 'assistant' && - msg.message.id !== lastAssistantId && + msg.message!.id !== lastAssistantId && current.length > 0 ) { groups.push(current) @@ -52,7 +52,7 @@ export function groupMessagesByApiRound(messages: Message[]): Message[][] { current.push(msg) } if (msg.type === 'assistant') { - lastAssistantId = msg.message.id + lastAssistantId = msg.message!.id } } diff --git a/src/services/compact/microCompact.ts b/src/services/compact/microCompact.ts index bab885f02..417e25926 100644 --- a/src/services/compact/microCompact.ts +++ b/src/services/compact/microCompact.ts @@ -169,11 +169,11 @@ export function estimateMessageTokens(messages: Message[]): number { continue } - if (!Array.isArray(message.message.content)) { + if (!Array.isArray(message.message!.content)) { continue } - for (const block of message.message.content) { + for (const block of message.message!.content) { if (block.type === 'text') { totalTokens += roughTokenCountEstimation(block.text) } else if (block.type === 'tool_result') { @@ -228,9 +228,9 @@ function collectCompactableToolIds(messages: Message[]): string[] { for (const message of messages) { if ( message.type === 'assistant' && - Array.isArray(message.message.content) + Array.isArray(message.message!.content) ) { - for (const block of message.message.content) { + for (const block of message.message!.content) { if (block.type === 'tool_use' && COMPACTABLE_TOOLS.has(block.name)) { ids.push(block.id) } @@ -313,9 +313,9 @@ async function cachedMicrocompactPath( const compactableToolIds = new Set(collectCompactableToolIds(messages)) // Second pass: register tool results grouped by user message for (const message of messages) { - if (message.type === 'user' && Array.isArray(message.message.content)) { + if (message.type === 'user' && Array.isArray(message.message!.content)) { const groupIds: string[] = [] - for (const block of message.message.content) { + for (const block of message.message!.content) { if ( block.type === 'tool_result' && compactableToolIds.has(block.tool_use_id) && @@ -375,7 +375,7 @@ async function cachedMicrocompactPath( const baseline = lastAsst?.type === 'assistant' ? (( - lastAsst.message.usage as unknown as Record< + lastAsst.message!.usage as unknown as Record< string, number | undefined > @@ -468,11 +468,11 @@ function maybeTimeBasedMicrocompact( let tokensSaved = 0 const result: Message[] = messages.map(message => { - if (message.type !== 'user' || !Array.isArray(message.message.content)) { + if (message.type !== 'user' || !Array.isArray(message.message!.content)) { return message } let touched = false - const newContent = message.message.content.map(block => { + const newContent = message.message!.content.map(block => { if ( block.type === 'tool_result' && clearSet.has(block.tool_use_id) && diff --git a/src/services/compact/sessionMemoryCompact.ts b/src/services/compact/sessionMemoryCompact.ts index 93bd0b981..fae482c40 100644 --- a/src/services/compact/sessionMemoryCompact.ts +++ b/src/services/compact/sessionMemoryCompact.ts @@ -134,11 +134,11 @@ async function initSessionMemoryCompactConfig(): Promise { */ export function hasTextBlocks(message: Message): boolean { if (message.type === 'assistant') { - const content = message.message.content + const content = message.message!.content return Array.isArray(content) && content.some(block => block.type === 'text') } if (message.type === 'user') { - const content = message.message.content + const content = message.message!.content if (typeof content === 'string') { return content.length > 0 } @@ -156,7 +156,7 @@ function getToolResultIds(message: Message): string[] { if (message.type !== 'user') { return [] } - const content = message.message.content + const content = message.message!.content if (!Array.isArray(content)) { return [] } @@ -176,7 +176,7 @@ function hasToolUseWithIds(message: Message, toolUseIds: Set): boolean { if (message.type !== 'assistant') { return false } - const content = message.message.content + const content = message.message!.content if (!Array.isArray(content)) { return false } @@ -251,8 +251,8 @@ export function adjustIndexToPreserveAPIInvariants( const toolUseIdsInKeptRange = new Set() for (let i = adjustedIndex; i < messages.length; i++) { const msg = messages[i]! - if (msg.type === 'assistant' && Array.isArray(msg.message.content)) { - for (const block of msg.message.content) { + if (msg.type === 'assistant' && Array.isArray(msg.message!.content)) { + for (const block of msg.message!.content) { if (block.type === 'tool_use') { toolUseIdsInKeptRange.add(block.id) } @@ -273,9 +273,9 @@ export function adjustIndexToPreserveAPIInvariants( // Remove found tool_use_ids from the set if ( message.type === 'assistant' && - Array.isArray(message.message.content) + Array.isArray(message.message!.content) ) { - for (const block of message.message.content) { + for (const block of message.message!.content) { if (block.type === 'tool_use' && neededToolUseIds.has(block.id)) { neededToolUseIds.delete(block.id) } @@ -290,8 +290,8 @@ export function adjustIndexToPreserveAPIInvariants( const messageIdsInKeptRange = new Set() for (let i = adjustedIndex; i < messages.length; i++) { const msg = messages[i]! - if (msg.type === 'assistant' && msg.message.id) { - messageIdsInKeptRange.add(msg.message.id) + if (msg.type === 'assistant' && msg.message!.id) { + messageIdsInKeptRange.add(msg.message!.id) } } @@ -301,8 +301,8 @@ export function adjustIndexToPreserveAPIInvariants( const message = messages[i]! if ( message.type === 'assistant' && - message.message.id && - messageIdsInKeptRange.has(message.message.id) + message.message!.id && + messageIdsInKeptRange.has(message.message!.id) ) { // This message has the same message.id as one in the kept range // Include it so thinking blocks can be properly merged diff --git a/src/services/tips/tipRegistry.ts b/src/services/tips/tipRegistry.ts index 05b727dcc..e802e8903 100644 --- a/src/services/tips/tipRegistry.ts +++ b/src/services/tips/tipRegistry.ts @@ -443,7 +443,7 @@ const externalTips: Tip[] = [ }, { id: 'desktop-shortcut', - content: async ctx => { + content: async (ctx: TipContext) => { const blue = color('suggestion', ctx.theme) return `Continue your session in Claude Code Desktop with ${blue('/desktop')}` }, @@ -489,24 +489,24 @@ const externalTips: Tip[] = [ }, { id: 'frontend-design-plugin', - content: async ctx => { + content: async (ctx: TipContext) => { const blue = color('suggestion', ctx.theme) return `Working with HTML/CSS? Install the frontend-design plugin:\n${blue(`/plugin install frontend-design@${OFFICIAL_MARKETPLACE_NAME}`)}` }, cooldownSessions: 3, - isRelevant: async context => + isRelevant: async (context: TipContext) => isMarketplacePluginRelevant('frontend-design', context, { filePath: /\.(html|css|htm)$/i, }), }, { id: 'vercel-plugin', - content: async ctx => { + content: async (ctx: TipContext) => { const blue = color('suggestion', ctx.theme) return `Working with Vercel? Install the vercel plugin:\n${blue(`/plugin install vercel@${OFFICIAL_MARKETPLACE_NAME}`)}` }, cooldownSessions: 3, - isRelevant: async context => + isRelevant: async (context: TipContext) => isMarketplacePluginRelevant('vercel', context, { filePath: /(?:^|[/\\])vercel\.json$/i, cli: ['vercel'], @@ -514,7 +514,7 @@ const externalTips: Tip[] = [ }, { id: 'effort-high-nudge', - content: async ctx => { + content: async (ctx: TipContext) => { const blue = color('suggestion', ctx.theme) const cmd = blue('/effort high') const variant = getFeatureValue_CACHED_MAY_BE_STALE< @@ -544,7 +544,7 @@ const externalTips: Tip[] = [ }, { id: 'subagent-fanout-nudge', - content: async ctx => { + content: async (ctx: TipContext) => { const blue = color('suggestion', ctx.theme) const variant = getFeatureValue_CACHED_MAY_BE_STALE< 'off' | 'copy_a' | 'copy_b' @@ -566,7 +566,7 @@ const externalTips: Tip[] = [ }, { id: 'loop-command-nudge', - content: async ctx => { + content: async (ctx: TipContext) => { const blue = color('suggestion', ctx.theme) const variant = getFeatureValue_CACHED_MAY_BE_STALE< 'off' | 'copy_a' | 'copy_b' @@ -589,7 +589,7 @@ const externalTips: Tip[] = [ }, { id: 'guest-passes', - content: async ctx => { + content: async (ctx: TipContext) => { const claude = color('claude', ctx.theme) const reward = getCachedReferrerReward() return reward @@ -608,7 +608,7 @@ const externalTips: Tip[] = [ }, { id: 'overage-credit', - content: async ctx => { + content: async (ctx: TipContext) => { const claude = color('claude', ctx.theme) const info = getCachedOverageCreditGrant() const amount = info ? formatGrantAmount(info) : null diff --git a/src/services/tools/StreamingToolExecutor.ts b/src/services/tools/StreamingToolExecutor.ts index c4f06a961..40c3dff3d 100644 --- a/src/services/tools/StreamingToolExecutor.ts +++ b/src/services/tools/StreamingToolExecutor.ts @@ -346,8 +346,8 @@ export class StreamingToolExecutor { const isErrorResult = update.message.type === 'user' && - Array.isArray(update.message.message.content) && - update.message.message.content.some( + Array.isArray(update.message.message!.content) && + update.message.message!.content.some( _ => _.type === 'tool_result' && _.is_error === true, ) diff --git a/src/services/tools/toolExecution.ts b/src/services/tools/toolExecution.ts index 71a6ea3b8..255fc878f 100644 --- a/src/services/tools/toolExecution.ts +++ b/src/services/tools/toolExecution.ts @@ -815,7 +815,7 @@ async function checkPermissionsAndCallTool( tool, processedInput, toolUseID, - assistantMessage.message.id, + assistantMessage.message.id!, requestId, mcpServerType, mcpServerBaseUrl, @@ -1497,7 +1497,7 @@ async function checkPermissionsAndCallTool( toolUseContext, tool, toolUseID, - assistantMessage.message.id, + assistantMessage.message.id!, processedInput, toolOutput, requestId, diff --git a/src/services/tools/toolHooks.ts b/src/services/tools/toolHooks.ts index cf6370a53..c1e6f702c 100644 --- a/src/services/tools/toolHooks.ts +++ b/src/services/tools/toolHooks.ts @@ -67,7 +67,7 @@ export async function* runPostToolUseHooks( // IMPORTANT: We emit a cancelled event per hook if ( result.message?.type === 'attachment' && - result.message.attachment.type === 'hook_cancelled' + result.message.attachment!.type === 'hook_cancelled' ) { logEvent('tengu_post_tool_hooks_cancelled', { toolName: sanitizeToolNameForAnalytics(tool.name), @@ -96,7 +96,7 @@ export async function* runPostToolUseHooks( result.message && !( result.message.type === 'attachment' && - result.message.attachment.type === 'hook_blocking_error' + result.message.attachment!.type === 'hook_blocking_error' ) ) { yield { message: result.message as AttachmentMessage | ProgressMessage } @@ -223,7 +223,7 @@ export async function* runPostToolUseFailureHooks( // Check if we were aborted during hook execution if ( result.message?.type === 'attachment' && - result.message.attachment.type === 'hook_cancelled' + result.message.attachment!.type === 'hook_cancelled' ) { logEvent('tengu_post_tool_failure_hooks_cancelled', { toolName: sanitizeToolNameForAnalytics(tool.name), @@ -248,7 +248,7 @@ export async function* runPostToolUseFailureHooks( result.message && !( result.message.type === 'attachment' && - result.message.attachment.type === 'hook_blocking_error' + result.message.attachment!.type === 'hook_blocking_error' ) ) { yield { message: result.message as AttachmentMessage | ProgressMessage } diff --git a/src/services/vcr.ts b/src/services/vcr.ts index 8c3ce6ca9..8ee060c1d 100644 --- a/src/services/vcr.ts +++ b/src/services/vcr.ts @@ -180,7 +180,7 @@ function mapMessages( if (typeof _ === 'string') { return f(_) } - return _.map(_ => { + return _!.map(_ => { switch (_.type) { case 'tool_result': if (typeof _.content === 'string') { diff --git a/src/tasks/LocalAgentTask/LocalAgentTask.tsx b/src/tasks/LocalAgentTask/LocalAgentTask.tsx index aed9a61dc..0f7eb3740 100644 --- a/src/tasks/LocalAgentTask/LocalAgentTask.tsx +++ b/src/tasks/LocalAgentTask/LocalAgentTask.tsx @@ -106,14 +106,14 @@ export function updateProgressFromMessage( if (message.type !== 'assistant') { return } - const usage = message.message.usage as BetaUsage + const usage = message.message!.usage as BetaUsage // Keep latest input (it's cumulative in the API), sum outputs tracker.latestInputTokens = (usage.input_tokens as number) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) tracker.cumulativeOutputTokens += usage.output_tokens as number - for (const content of (message.message.content ?? []) as Array<{ type: string; name?: string; input?: unknown }>) { + for (const content of (message.message!.content ?? []) as Array<{ type: string; name?: string; input?: unknown }>) { if (content.type === 'tool_use') { tracker.toolUseCount++ // Omit StructuredOutput from preview - it's an internal tool diff --git a/src/tasks/LocalMainSessionTask.ts b/src/tasks/LocalMainSessionTask.ts index f04694bd1..e0d4bf90d 100644 --- a/src/tasks/LocalMainSessionTask.ts +++ b/src/tasks/LocalMainSessionTask.ts @@ -423,11 +423,11 @@ export function startBackgroundSession({ const contentBlocks = (msg.message?.content ?? []) as Array<{ type: string; text?: string; name?: string; input?: unknown }> for (const block of contentBlocks) { if (block.type === 'text') { - tokenCount += roughTokenCountEstimation(block.text) + tokenCount += roughTokenCountEstimation(block.text ?? '') } else if (block.type === 'tool_use') { toolCount++ const activity: ToolActivity = { - toolName: block.name, + toolName: block.name ?? '', input: block.input as Record, } recentActivities.push(activity) diff --git a/src/tasks/RemoteAgentTask/RemoteAgentTask.tsx b/src/tasks/RemoteAgentTask/RemoteAgentTask.tsx index a622dc3b4..75e096014 100644 --- a/src/tasks/RemoteAgentTask/RemoteAgentTask.tsx +++ b/src/tasks/RemoteAgentTask/RemoteAgentTask.tsx @@ -29,6 +29,7 @@ import { type BackgroundRemoteSessionPrecondition, checkBackgroundRemoteSessionEligibility, } from '../../utils/background/remote/remoteSession.js' +export type { BackgroundRemoteSessionPrecondition } import { logForDebugging } from '../../utils/debug.js' import { logError } from '../../utils/log.js' import { enqueuePendingNotification } from '../../utils/messageQueueManager.js' diff --git a/src/tools/AgentTool/AgentTool.tsx b/src/tools/AgentTool/AgentTool.tsx index 91cee5dac..8e4ac12d3 100644 --- a/src/tools/AgentTool/AgentTool.tsx +++ b/src/tools/AgentTool/AgentTool.tsx @@ -45,6 +45,7 @@ import { formatPreconditionError, getRemoteTaskSessionUrl, registerRemoteAgentTask, + type BackgroundRemoteSessionPrecondition, } from '../../tasks/RemoteAgentTask/RemoteAgentTask.js' import { assembleToolPool } from '../../tools.js' import { asAgentId } from '../../types/ids.js' @@ -668,7 +669,7 @@ export const AgentTool = buildTool({ if (process.env.USER_TYPE === 'ant' && effectiveIsolation === 'remote') { const eligibility = await checkRemoteAgentEligibility() if (!eligibility.eligible) { - const reasons = (eligibility as { eligible: false; errors: Array<{ type: string; message?: string }> }).errors + const reasons = (eligibility as { eligible: false; errors: BackgroundRemoteSessionPrecondition[] }).errors .map(formatPreconditionError) .join('\n') throw new Error(`Cannot launch remote agent:\n${reasons}`) diff --git a/src/tools/AgentTool/UI.tsx b/src/tools/AgentTool/UI.tsx index 19b4680a9..e52e09ca8 100644 --- a/src/tools/AgentTool/UI.tsx +++ b/src/tools/AgentTool/UI.tsx @@ -1,7 +1,9 @@ import type { + ContentBlock, ToolResultBlockParam, ToolUseBlockParam, } from '@anthropic-ai/sdk/resources/index.mjs' +type BetaContentBlock = ContentBlock | ToolResultBlockParam import * as React from 'react' import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHint.js' import { @@ -555,7 +557,7 @@ export function renderToolUseProgressMessage( } const message = msg.data.message return message.message.content.some( - content => content.type === 'tool_use', + (content: BetaContentBlock) => content.type === 'tool_use', ) }) @@ -630,7 +632,7 @@ export function renderToolUseProgressMessage( return false } return data.message.message.content.some( - content => content.type === 'tool_use', + (content: BetaContentBlock) => content.type === 'tool_use', ) }) @@ -799,7 +801,7 @@ function calculateAgentStats(progressMessages: ProgressMessage[]): { const message = msg.data.message return ( message.type === 'user' && - message.message.content.some(content => content.type === 'tool_result') + message.message.content.some((content: BetaContentBlock) => content.type === 'tool_result') ) }) @@ -1078,14 +1080,14 @@ export function extractLastToolInfo( const message = msg.data.message return ( message.type === 'user' && - message.message.content.some(c => c.type === 'tool_result') + message.message.content.some((c: BetaContentBlock) => c.type === 'tool_result') ) }, ) if (lastToolResult?.data.message.type === 'user') { const toolResultBlock = lastToolResult.data.message.message.content.find( - c => c.type === 'tool_result', + (c: BetaContentBlock) => c.type === 'tool_result', ) if (toolResultBlock?.type === 'tool_result') { diff --git a/src/tools/AgentTool/forkSubagent.ts b/src/tools/AgentTool/forkSubagent.ts index 9a9e43302..553d455de 100644 --- a/src/tools/AgentTool/forkSubagent.ts +++ b/src/tools/AgentTool/forkSubagent.ts @@ -78,7 +78,7 @@ export const FORK_AGENT = { export function isInForkChild(messages: MessageType[]): boolean { return messages.some(m => { if (m.type !== 'user') return false - const content = m.message.content + const content = m.message!.content if (!Array.isArray(content)) return false return content.some( block => diff --git a/src/tools/NotebookEditTool/NotebookEditTool.ts b/src/tools/NotebookEditTool/NotebookEditTool.ts index 4551412fa..828fb6848 100644 --- a/src/tools/NotebookEditTool/NotebookEditTool.ts +++ b/src/tools/NotebookEditTool/NotebookEditTool.ts @@ -267,7 +267,7 @@ export const NotebookEditTool = buildTool({ } } else { // First try to find the cell by its actual ID - const cellIndex = notebook.cells.findIndex(cell => cell.id === cell_id) + const cellIndex = notebook.cells.findIndex((cell: NotebookCell) => cell.id === cell_id) if (cellIndex === -1) { // If not found, try to parse as a numeric index (cell-N format) @@ -352,7 +352,7 @@ export const NotebookEditTool = buildTool({ cellIndex = 0 // Default to inserting at the beginning if no cell_id is provided } else { // First try to find the cell by its actual ID - cellIndex = notebook.cells.findIndex(cell => cell.id === cell_id) + cellIndex = notebook.cells.findIndex((cell: NotebookCell) => cell.id === cell_id) // If not found, try to parse as a numeric index (cell-N format) if (cellIndex === -1) { diff --git a/src/tools/WebFetchTool/utils.ts b/src/tools/WebFetchTool/utils.ts index f75e358b4..912d51f09 100644 --- a/src/tools/WebFetchTool/utils.ts +++ b/src/tools/WebFetchTool/utils.ts @@ -519,9 +519,9 @@ export async function applyPromptToMarkdown( throw new AbortError() } - const { content } = assistantMessage.message - if (content.length > 0) { - const contentBlock = content[0] + const { content } = assistantMessage.message! + if (content!.length > 0) { + const contentBlock = content![0] if (contentBlock && typeof contentBlock === 'object' && 'text' in contentBlock) { return (contentBlock as { text: string }).text } diff --git a/src/types/internal-modules.d.ts b/src/types/internal-modules.d.ts index 95ff7a068..6f94d93e9 100644 --- a/src/types/internal-modules.d.ts +++ b/src/types/internal-modules.d.ts @@ -15,4 +15,17 @@ declare module "bun:ffi" { export function dlopen>(path: string, symbols: T): { symbols: { [K in keyof T]: (...args: unknown[]) => unknown }; close(): void }; } -// +// Third-party modules without @types packages +declare module 'bidi-js' { + function getEmbeddingLevels(text: string, defaultDirection?: string): { paragraphLevel: number; levels: Uint8Array } + function getReorderSegments(text: string, embeddingLevels: { paragraphLevel: number; levels: Uint8Array }, start?: number, end?: number): [number, number][] + function getVisualOrder(reorderSegments: [number, number][]): number[] + export { getEmbeddingLevels, getReorderSegments, getVisualOrder } + export default { getEmbeddingLevels, getReorderSegments, getVisualOrder } +} + +declare module 'asciichart' { + function plot(series: number[] | number[][], config?: Record): string + export { plot } + export default { plot } +} diff --git a/src/types/message.ts b/src/types/message.ts index 1dfa18086..fce9b433e 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -37,7 +37,7 @@ export type Message = { isCompactSummary?: boolean toolUseResult?: unknown isVisibleInTranscriptOnly?: boolean - attachment?: { type: string; toolUseID?: string; [key: string]: unknown } + attachment?: { type: string; toolUseID?: string; [key: string]: unknown; addedNames: string[]; addedLines: string[]; removedNames: string[] } message?: { role?: string id?: string @@ -48,12 +48,19 @@ export type Message = { [key: string]: unknown } -export type AssistantMessage = Message & { type: 'assistant' } -export type AttachmentMessage = Message & { type: 'attachment'; attachment: { type: string; [key: string]: unknown } } +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 SystemLocalCommandMessage = Message & { type: 'system' } export type SystemMessage = Message & { type: 'system' } -export type UserMessage = Message & { type: 'user' } +export type UserMessage = Message & { + type: 'user' + message: NonNullable + imagePasteIds?: number[] +} export type NormalizedUserMessage = UserMessage export type RequestStartEvent = { type: string; [key: string]: unknown } export type StreamEvent = { type: string; [key: string]: unknown } diff --git a/src/utils/__tests__/messages.test.ts b/src/utils/__tests__/messages.test.ts index 316b34250..31d2eb55b 100644 --- a/src/utils/__tests__/messages.test.ts +++ b/src/utils/__tests__/messages.test.ts @@ -86,9 +86,9 @@ describe("createAssistantMessage", () => { test("creates assistant message with string content", () => { const msg = createAssistantMessage({ content: "hello" }); expect(msg.type).toBe("assistant"); - expect(msg.message.role).toBe("assistant"); - expect(msg.message.content).toHaveLength(1); - expect((msg.message.content[0] as any).text).toBe("hello"); + expect(msg.message!.role).toBe("assistant"); + expect(msg.message!.content![0] as any).toBeTruthy(); + expect((msg.message!.content![0] as any).text).toBe("hello"); }); test("creates assistant message with content blocks", () => { @@ -501,7 +501,7 @@ describe("normalizeMessagesForAPI", () => { ]); const normalized = normalizeMessagesForAPI([assistant]); - const block = (normalized[0] as AssistantMessage).message.content[0] as any; + const block = (normalized[0] as AssistantMessage).message!.content![0] as any; expect(block.type).toBe("tool_use"); expect(block._geminiThoughtSignature).toBe("sig-123"); diff --git a/src/utils/analyzeContext.ts b/src/utils/analyzeContext.ts index f5ced1f54..4af0f8f73 100644 --- a/src/utils/analyzeContext.ts +++ b/src/utils/analyzeContext.ts @@ -445,8 +445,8 @@ async function countBuiltInToolTokens( if (messages) { const deferredToolNameSet = new Set(deferredBuiltinTools.map(t => t.name)) for (const msg of messages) { - if (msg.type === 'assistant' && Array.isArray(msg.message.content)) { - for (const block of msg.message.content) { + if (msg.type === 'assistant' && Array.isArray(msg.message!.content)) { + for (const block of msg.message!.content) { if ( typeof block !== 'string' && 'type' in block && @@ -683,8 +683,8 @@ export async function countMcpToolTokens( if (isDeferred && messages) { const mcpToolNameSet = new Set(mcpTools.map(t => t.name)) for (const msg of messages) { - if (msg.type === 'assistant' && Array.isArray(msg.message.content)) { - for (const block of msg.message.content) { + if (msg.type === 'assistant' && Array.isArray(msg.message!.content)) { + for (const block of msg.message!.content) { if ( typeof block !== 'string' && 'type' in block && @@ -786,7 +786,7 @@ function processAssistantMessage( breakdown: MessageBreakdown, ): void { // Process each content block individually - const contentBlocks = Array.isArray(msg.message.content) ? msg.message.content : [] + const contentBlocks = Array.isArray(msg.message!.content) ? msg.message!.content : [] for (const block of contentBlocks) { const blockStr = jsonStringify(block) const blockTokens = roughTokenCountEstimation(blockStr) @@ -811,20 +811,19 @@ function processUserMessage( toolUseIdToName: Map, ): void { // Handle both string and array content - if (typeof msg.message.content === 'string') { + if (typeof msg.message!.content === 'string') { // Simple string content - const tokens = roughTokenCountEstimation(msg.message.content) + const tokens = roughTokenCountEstimation(msg.message!.content) breakdown.userMessageTokens += tokens return } // Process each content block individually - for (const block of msg.message.content) { + for (const block of (msg.message!.content ?? [])) { const blockStr = jsonStringify(block) const blockTokens = roughTokenCountEstimation(blockStr) if ('type' in block && block.type === 'tool_result') { - breakdown.toolResultTokens += blockTokens const toolUseId = 'tool_use_id' in block ? block.tool_use_id : undefined const toolName = (toolUseId ? toolUseIdToName.get(toolUseId) : undefined) || 'unknown' @@ -874,8 +873,8 @@ async function approximateMessageTokens( // Build a map of tool_use_id to tool_name for easier lookup const toolUseIdToName = new Map() for (const msg of microcompactResult.messages) { - if (msg.type === 'assistant' && Array.isArray(msg.message.content)) { - for (const block of msg.message.content) { + if (msg.type === 'assistant' && Array.isArray(msg.message!.content)) { + for (const block of msg.message!.content) { if (typeof block !== 'string' && 'type' in block && block.type === 'tool_use') { const toolUseId = 'id' in block ? (block.id as string) : undefined const toolName = diff --git a/src/utils/asciicast.ts b/src/utils/asciicast.ts index 42ff569a6..d3d89dfa9 100644 --- a/src/utils/asciicast.ts +++ b/src/utils/asciicast.ts @@ -193,8 +193,8 @@ export function installAsciicastRecorder(): void { ) as typeof process.stdout.write process.stdout.write = function ( chunk: string | Uint8Array, - encodingOrCb?: BufferEncoding | ((err?: Error) => void), - cb?: (err?: Error) => void, + encodingOrCb?: BufferEncoding | ((err?: Error | null) => void), + cb?: (err?: Error | null) => void, ): boolean { // Record the output event const elapsed = (performance.now() - startTime) / 1000 diff --git a/src/utils/attachments.ts b/src/utils/attachments.ts index 8cc818a7c..e92b712d9 100644 --- a/src/utils/attachments.ts +++ b/src/utils/attachments.ts @@ -1147,13 +1147,13 @@ function getPlanModeAttachmentTurnCount(messages: Message[]): { if ( message?.type === 'user' && !message.isMeta && - !hasToolResultContent(message.message.content) + !hasToolResultContent(message.message!.content) ) { turnsSinceLastAttachment++ } else if ( message?.type === 'attachment' && - (message.attachment.type === 'plan_mode' || - message.attachment.type === 'plan_mode_reentry') + (message.attachment!.type === 'plan_mode' || + message.attachment!.type === 'plan_mode_reentry') ) { foundPlanModeAttachment = true break @@ -1173,10 +1173,10 @@ function countPlanModeAttachmentsSinceLastExit(messages: Message[]): number { for (let i = messages.length - 1; i >= 0; i--) { const message = messages[i] if (message?.type === 'attachment') { - if (message.attachment.type === 'plan_mode_exit') { + if (message.attachment!.type === 'plan_mode_exit') { break // Stop counting at the last exit } - if (message.attachment.type === 'plan_mode') { + if (message.attachment!.type === 'plan_mode') { count++ } } @@ -1292,18 +1292,18 @@ function getAutoModeAttachmentTurnCount(messages: Message[]): { if ( message?.type === 'user' && !message.isMeta && - !hasToolResultContent(message.message.content) + !hasToolResultContent(message.message!.content) ) { turnsSinceLastAttachment++ } else if ( message?.type === 'attachment' && - message.attachment.type === 'auto_mode' + message.attachment!.type === 'auto_mode' ) { foundAutoModeAttachment = true break } else if ( message?.type === 'attachment' && - message.attachment.type === 'auto_mode_exit' + message.attachment!.type === 'auto_mode_exit' ) { // Exit resets the throttle — treat as if no prior attachment exists break @@ -1322,10 +1322,10 @@ function countAutoModeAttachmentsSinceLastExit(messages: Message[]): number { for (let i = messages.length - 1; i >= 0; i--) { const message = messages[i] if (message?.type === 'attachment') { - if (message.attachment.type === 'auto_mode_exit') { + if (message.attachment!.type === 'auto_mode_exit') { break } - if (message.attachment.type === 'auto_mode') { + if (message.attachment!.type === 'auto_mode') { count++ } } @@ -1525,9 +1525,9 @@ export function getAgentListingDeltaAttachment( const announced = new Set() for (const msg of messages ?? []) { if (msg.type !== 'attachment') continue - if (msg.attachment.type !== 'agent_listing_delta') continue - for (const t of msg.attachment.addedTypes as string[]) announced.add(t) - for (const t of msg.attachment.removedTypes as string[]) announced.delete(t) + if (msg.attachment!.type !== 'agent_listing_delta') continue + for (const t of msg.attachment!.addedTypes as string[]) announced.add(t) + for (const t of msg.attachment!.removedTypes as string[]) announced.delete(t) } const currentTypes = new Set(filtered.map(a => a.agentType)) @@ -2256,8 +2256,8 @@ export function collectSurfacedMemories(messages: ReadonlyArray): { const paths = new Set() let totalBytes = 0 for (const m of messages) { - if (m.type === 'attachment' && m.attachment.type === 'relevant_memories') { - for (const mem of m.attachment.memories as { path: string; content: string; mtimeMs: number }[]) { + if (m.type === 'attachment' && m.attachment!.type === 'relevant_memories') { + for (const mem of m.attachment!.memories as { path: string; content: string; mtimeMs: number }[]) { paths.add(mem.path) totalBytes += mem.content.length } @@ -2473,16 +2473,16 @@ export function collectRecentSuccessfulTools( const m = messages[i] if (!m) continue if (isHumanTurn(m) && m !== lastUserMessage) break - if (m.type === 'assistant' && typeof m.message.content !== 'string') { - for (const block of m.message.content) { + if (m.type === 'assistant' && typeof m.message!.content !== 'string') { + for (const block of m.message!.content as Array<{type: string; id: string; name: string}>) { if (block.type === 'tool_use') useIdToName.set(block.id, block.name) } } else if ( m.type === 'user' && 'message' in m && - Array.isArray(m.message.content) + Array.isArray(m.message!.content) ) { - for (const block of m.message.content) { + for (const block of m.message!.content as Array<{type: string}>) { if (isToolResultBlock(block)) { resultByUseId.set(block.tool_use_id, block.is_error === true) } @@ -3201,13 +3201,13 @@ export async function generateFileAttachment( export function createAttachmentMessage( attachment: Attachment, -): AttachmentMessage { +): AttachmentMessage { return { attachment, type: 'attachment', uuid: randomUUID(), timestamp: new Date().toISOString(), - } + } as unknown as AttachmentMessage } function getTodoReminderTurnCounts(messages: Message[]): { @@ -3248,7 +3248,7 @@ function getTodoReminderTurnCounts(messages: Message[]): { } else if ( lastReminderIndex === -1 && message?.type === 'attachment' && - message.attachment.type === 'todo_reminder' + message.attachment!.type === 'todo_reminder' ) { lastReminderIndex = i } @@ -3357,7 +3357,7 @@ function getTaskReminderTurnCounts(messages: Message[]): { } else if ( lastReminderIndex === -1 && message?.type === 'attachment' && - message.attachment.type === 'task_reminder' + message.attachment!.type === 'task_reminder' ) { lastReminderIndex = i } @@ -3880,7 +3880,7 @@ export function getVerifyPlanReminderTurnCount(messages: Message[]): number { // Stop counting at plan_mode_exit attachment (marks when implementation started) if ( message?.type === 'attachment' && - message.attachment.type === 'plan_mode_exit' + message.attachment!.type === 'plan_mode_exit' ) { return turnCount } diff --git a/src/utils/collapseReadSearch.ts b/src/utils/collapseReadSearch.ts index 41c454720..ad905008e 100644 --- a/src/utils/collapseReadSearch.ts +++ b/src/utils/collapseReadSearch.ts @@ -940,7 +940,7 @@ export function collapseReadSearchGroups( // suppresses the fallback). createCollapsedGroup adds .length to // memoryReadCount after the readCount subtraction instead. currentGroup.relevantMemories ??= [] - currentGroup.relevantMemories.push(...msg.attachment.memories) + currentGroup.relevantMemories.push(...(msg.attachment.memories ?? [])) } else if (shouldSkipMessage(msg)) { // Don't flush the group for skippable messages (thinking, attachments, system) // If a group is in progress, defer these messages to output after the collapsed group diff --git a/src/utils/collapseTeammateShutdowns.ts b/src/utils/collapseTeammateShutdowns.ts index 929769b04..846be71eb 100644 --- a/src/utils/collapseTeammateShutdowns.ts +++ b/src/utils/collapseTeammateShutdowns.ts @@ -43,7 +43,7 @@ export function collapseTeammateShutdowns( type: 'teammate_shutdown_batch', count, }, - }) + } as unknown as RenderableMessage) } } else { result.push(msg) diff --git a/src/utils/computerUse/platforms/win32.ts b/src/utils/computerUse/platforms/win32.ts index 4394d1bb5..fbf50d9c6 100644 --- a/src/utils/computerUse/platforms/win32.ts +++ b/src/utils/computerUse/platforms/win32.ts @@ -394,10 +394,10 @@ public class WScroll { // --------------------------------------------------------------------------- const screenshot: ScreenshotPlatform = { - async captureScreen(displayId) { + async captureScreen(displayId): Promise { // If HWND is bound, capture that specific window if (boundHwnd) { - const result = this.captureWindow?.(String(boundHwnd)) + const result = await this.captureWindow?.(String(boundHwnd)) if (result) return result } @@ -415,10 +415,10 @@ const screenshot: ScreenshotPlatform = { ) }, - async captureRegion(x, y, w, h) { + async captureRegion(x, y, w, h): Promise { // When HWND is bound, the window IS the region (matches macOS behavior) if (boundHwnd) { - const result = this.captureWindow?.(String(boundHwnd)) + const result = await this.captureWindow?.(String(boundHwnd)) if (result) return result } return this.captureScreen() diff --git a/src/utils/contextAnalysis.ts b/src/utils/contextAnalysis.ts index 2801d37f2..7d78b77f1 100644 --- a/src/utils/contextAnalysis.ts +++ b/src/utils/contextAnalysis.ts @@ -46,14 +46,14 @@ export function analyzeContext(messages: Message[]): TokenStats { messages.forEach(msg => { if (msg.type === 'attachment') { - const type = msg.attachment.type || 'unknown' + const type = msg.attachment!.type || 'unknown' stats.attachments.set(type, (stats.attachments.get(type) || 0) + 1) } }) const normalizedMessages = normalizeMessagesForAPI(messages) normalizedMessages.forEach(msg => { - const { content } = msg.message + const { content } = msg.message! // Not sure if this path is still used, but adding as a fallback if (typeof content === 'string') { @@ -67,7 +67,7 @@ export function analyzeContext(messages: Message[]): TokenStats { tokens } } else { - content.forEach(block => + content!.forEach(block => processBlock( block, msg, diff --git a/src/utils/conversationRecovery.ts b/src/utils/conversationRecovery.ts index 6d56f2ff7..4afbd2862 100644 --- a/src/utils/conversationRecovery.ts +++ b/src/utils/conversationRecovery.ts @@ -124,7 +124,7 @@ function migrateLegacyAttachmentTypes(message: Message): Message { ...attachment, displayPath: relative(getCwd(), path), }, - } as Message + } as unknown as Message } } @@ -359,7 +359,7 @@ function isTerminalToolResult( for (let i = resultIdx - 1; i >= 0; i--) { const msg = messages[i]! if (msg.type !== 'assistant') continue - const msgContent = msg.message.content + const msgContent = msg.message!.content if (!Array.isArray(msgContent)) continue for (const b of msgContent) { if (typeof b !== 'string' && 'type' in b && b.type === 'tool_use' && 'id' in b && b.id === toolUseId) { @@ -386,8 +386,8 @@ export function restoreSkillStateFromMessages(messages: Message[]): void { if (message.type !== 'attachment') { continue } - if (message.attachment.type === 'invoked_skills') { - const skills = message.attachment.skills as Array<{ name?: string; path?: string; content?: string }>; + if (message.attachment!.type === 'invoked_skills') { + const skills = message.attachment!.skills as Array<{ name?: string; path?: string; content?: string }>; for (const skill of skills) { if (skill.name && skill.path && skill.content) { // Resume only happens for the main session, so agentId is null @@ -399,7 +399,7 @@ export function restoreSkillStateFromMessages(messages: Message[]): void { // in the transcript the model is about to see. sentSkillNames is // process-local, so without this every resume re-announces the same // ~600 tokens. Fire-once latch; consumed on the first attachment pass. - if (message.attachment.type === 'skill_listing') { + if (message.attachment!.type === 'skill_listing') { suppressNextSkillListing() } } diff --git a/src/utils/exportRenderer.tsx b/src/utils/exportRenderer.tsx index 795eb6bfb..df4bc7ffc 100644 --- a/src/utils/exportRenderer.tsx +++ b/src/utils/exportRenderer.tsx @@ -46,7 +46,7 @@ function StaticKeybindingProvider({ // AttachmentMessage etc. have no .message and normalize to ≤1. function normalizedUpperBound(m: Message): number { if (!('message' in m)) return 1 - const c = m.message.content + const c = m.message!.content return Array.isArray(c) ? c.length : 1 } diff --git a/src/utils/groupToolUses.ts b/src/utils/groupToolUses.ts index a75c74fdf..427131675 100644 --- a/src/utils/groupToolUses.ts +++ b/src/utils/groupToolUses.ts @@ -136,7 +136,7 @@ export function applyGrouping( const results: NormalizedUserMessage[] = [] for (const assistantMsg of group) { const toolUseId = ( - assistantMsg.message.content[0] as { id: string } + assistantMsg.message!.content![0] as { id: string } ).id const resultMsg = resultsByToolUseId.get(toolUseId) if (resultMsg) { diff --git a/src/utils/hooks.ts b/src/utils/hooks.ts index e380583a3..bf0327ccb 100644 --- a/src/utils/hooks.ts +++ b/src/utils/hooks.ts @@ -2377,7 +2377,7 @@ async function* executeHooks({ ) // Inject timing fields for hook visibility if (promptResult.message?.type === 'attachment') { - const att = promptResult.message.attachment + const att = promptResult.message.attachment! if ( att.type === 'hook_success' || att.type === 'hook_non_blocking_error' @@ -2417,7 +2417,7 @@ async function* executeHooks({ ) // Inject timing fields for hook visibility if (agentResult.message?.type === 'attachment') { - const att = agentResult.message.attachment + const att = agentResult.message.attachment! if ( att.type === 'hook_success' || att.type === 'hook_non_blocking_error' diff --git a/src/utils/hooks/apiQueryHookHelper.ts b/src/utils/hooks/apiQueryHookHelper.ts index 2dd98e903..53b3d5999 100644 --- a/src/utils/hooks/apiQueryHookHelper.ts +++ b/src/utils/hooks/apiQueryHookHelper.ts @@ -117,7 +117,7 @@ export function createApiQueryHook( type: 'success', queryName: config.name, result, - messageId: response.message.id, + messageId: response.message.id ?? '', model, uuid, }, diff --git a/src/utils/hooks/hooksConfigManager.ts b/src/utils/hooks/hooksConfigManager.ts index c7c5a0db5..0990d8247 100644 --- a/src/utils/hooks/hooksConfigManager.ts +++ b/src/utils/hooks/hooksConfigManager.ts @@ -327,7 +327,7 @@ export function groupHooksByEventAndMatcher( const eventGroup = grouped[hookEvent] if (!eventGroup) continue - for (const matcher of matchers) { + for (const matcher of (matchers ?? [])) { const matcherKey = matcher.matcher || '' // Only PluginHookMatcher has pluginRoot; HookCallbackMatcher (internal diff --git a/src/utils/hooks/skillImprovement.ts b/src/utils/hooks/skillImprovement.ts index 972dc73d2..9d6066c9b 100644 --- a/src/utils/hooks/skillImprovement.ts +++ b/src/utils/hooks/skillImprovement.ts @@ -41,10 +41,10 @@ function formatRecentMessages(messages: Message[]): string { .filter(m => m.type === 'user' || m.type === 'assistant') .map(m => { const role = m.type === 'user' ? 'User' : 'Assistant' - const content = m.message.content + const content = m.message!.content if (typeof content === 'string') return `${role}: ${content.slice(0, 500)}` - const text = content + const text = (content ?? []) .filter( (b): b is Extract => b.type === 'text', ) diff --git a/src/utils/mcpInstructionsDelta.ts b/src/utils/mcpInstructionsDelta.ts index 5a487b2dc..e3c83cf3d 100644 --- a/src/utils/mcpInstructionsDelta.ts +++ b/src/utils/mcpInstructionsDelta.ts @@ -63,10 +63,10 @@ export function getMcpInstructionsDelta( for (const msg of messages) { if (msg.type !== 'attachment') continue attachmentCount++ - if (msg.attachment.type !== 'mcp_instructions_delta') continue + if (msg.attachment!.type !== 'mcp_instructions_delta') continue midCount++ - for (const n of (msg.attachment as any).addedNames) announced.add(n) - for (const n of (msg.attachment as any).removedNames) announced.delete(n) + for (const n of (msg.attachment! as any).addedNames) announced.add(n) + for (const n of (msg.attachment! as any).removedNames) announced.delete(n) } const connected = mcpClients.filter( diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 607d06d18..0d58b9b33 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -313,9 +313,9 @@ export function isSyntheticMessage(message: Message): boolean { message.type !== 'progress' && message.type !== 'attachment' && message.type !== 'system' && - Array.isArray(message.message.content) && - message.message.content[0]?.type === 'text' && - SYNTHETIC_MESSAGES.has(message.message.content[0].text) + Array.isArray(message.message?.content) && + message.message?.content[0]?.type === 'text' && + SYNTHETIC_MESSAGES.has((message.message?.content[0] as { text: string }).text) ) } @@ -325,7 +325,7 @@ function isSyntheticApiErrorMessage( return ( message.type === 'assistant' && message.isApiErrorMessage === true && - message.message.model === SYNTHETIC_MODEL + message.message?.model === SYNTHETIC_MODEL ) } @@ -696,27 +696,30 @@ export function isNotEmptyMessage(message: Message): boolean { return true } - if (typeof message.message.content === 'string') { - return message.message.content.trim().length > 0 + const msg = message.message + if (!msg) return true + + if (typeof msg.content === 'string') { + return msg.content.trim().length > 0 } - if (message.message.content.length === 0) { + if (!msg.content || msg.content.length === 0) { return false } // Skip multi-block messages for now - if (message.message.content.length > 1) { + if (msg.content.length > 1) { return true } - if (message.message.content[0]!.type !== 'text') { + if (msg.content[0]!.type !== 'text') { return true } return ( - message.message.content[0]!.text.trim().length > 0 && - message.message.content[0]!.text !== NO_CONTENT_MESSAGE && - message.message.content[0]!.text !== INTERRUPT_MESSAGE_FOR_TOOL_USE + (msg.content[0] as { text: string }).text.trim().length > 0 && + (msg.content[0] as { text: string }).text !== NO_CONTENT_MESSAGE && + (msg.content[0] as { text: string }).text !== INTERRUPT_MESSAGE_FOR_TOOL_USE ) } @@ -750,7 +753,8 @@ export function normalizeMessages(messages: Message[]): NormalizedMessage[] { return messages.flatMap(message => { switch (message.type) { case 'assistant': { - const assistantContent = Array.isArray(message.message.content) ? message.message.content : [] + const aMsg = message as AssistantMessage + const assistantContent = Array.isArray(aMsg.message.content) ? aMsg.message.content : [] isNewChain = isNewChain || assistantContent.length > 1 return assistantContent.map((_, index) => { const uuid = isNewChain @@ -760,9 +764,9 @@ export function normalizeMessages(messages: Message[]): NormalizedMessage[] { type: 'assistant' as const, timestamp: message.timestamp, message: { - ...message.message, + ...aMsg.message, content: [_], - context_management: message.message.context_management ?? null, + context_management: aMsg.message.context_management ?? null, }, isMeta: message.isMeta, isVirtual: message.isVirtual, @@ -781,45 +785,48 @@ export function normalizeMessages(messages: Message[]): NormalizedMessage[] { case 'system': return [message] case 'user': { - if (typeof message.message.content === 'string') { - const uuid = isNewChain ? deriveUUID(message.uuid, 0) : message.uuid + const uMsg = message as UserMessage + if (typeof uMsg.message.content === 'string') { + const uuid = isNewChain ? deriveUUID(uMsg.uuid, 0) : uMsg.uuid return [ { - ...message, + ...uMsg, uuid, message: { - ...message.message, - content: [{ type: 'text', text: message.message.content }], + ...uMsg.message, + content: [{ type: 'text', text: uMsg.message.content }], }, } as NormalizedMessage, ] } - isNewChain = isNewChain || message.message.content.length > 1 + isNewChain = isNewChain || (uMsg.message.content?.length ?? 0) > 1 let imageIndex = 0 - return message.message.content.map((_, index) => { + return (uMsg.message.content ?? []).map((_, index) => { const isImage = _.type === 'image' // For image content blocks, extract just the ID for this image const imageId = - isImage && message.imagePasteIds - ? message.imagePasteIds[imageIndex] + isImage && uMsg.imagePasteIds + ? (uMsg.imagePasteIds as number[])[imageIndex] : undefined if (isImage) imageIndex++ return { ...createUserMessage({ content: [_], - toolUseResult: message.toolUseResult, - mcpMeta: message.mcpMeta as { _meta?: Record; structuredContent?: Record }, - isMeta: message.isMeta === true ? true : undefined, - isVisibleInTranscriptOnly: message.isVisibleInTranscriptOnly === true ? true : undefined, - isVirtual: (message.isVirtual as boolean | undefined) === true ? true : undefined, - timestamp: message.timestamp as string | undefined, + toolUseResult: uMsg.toolUseResult, + mcpMeta: uMsg.mcpMeta as { _meta?: Record; structuredContent?: Record }, + isMeta: uMsg.isMeta === true ? true : undefined, + isVisibleInTranscriptOnly: uMsg.isVisibleInTranscriptOnly === true ? true : undefined, + isVirtual: (uMsg.isVirtual as boolean | undefined) === true ? true : undefined, + timestamp: uMsg.timestamp as string | undefined, imagePasteIds: imageId !== undefined ? [imageId] : undefined, - origin: message.origin as MessageOrigin | undefined, + origin: uMsg.origin as MessageOrigin | undefined, }), - uuid: isNewChain ? deriveUUID(message.uuid, index) : message.uuid, + uuid: isNewChain ? deriveUUID(uMsg.uuid, index) : uMsg.uuid, } as NormalizedMessage }) } + default: + return [message] } }) } @@ -834,8 +841,8 @@ export function isToolUseRequestMessage( return ( message.type === 'assistant' && // Note: stop_reason === 'tool_use' is unreliable -- it's not always set correctly - Array.isArray(message.message.content) && - message.message.content.some(_ => _.type === 'tool_use') + Array.isArray(message.message?.content) && + (message.message?.content as Array<{type: string}>).some(_ => _.type === 'tool_use') ) } @@ -848,8 +855,8 @@ export function isToolUseResultMessage( ): message is ToolUseResultMessage { return ( message.type === 'user' && - ((Array.isArray(message.message.content) && - message.message.content[0]?.type === 'tool_result') || + ((Array.isArray(message.message?.content) && + (message.message?.content as Array<{type: string}>)[0]?.type === 'tool_result') || Boolean(message.toolUseResult)) ) } @@ -1035,14 +1042,14 @@ function isHookAttachmentMessage( ): message is AttachmentMessage { return ( message.type === 'attachment' && - (message.attachment.type === 'hook_blocking_error' || - message.attachment.type === 'hook_cancelled' || - message.attachment.type === 'hook_error_during_execution' || - message.attachment.type === 'hook_non_blocking_error' || - message.attachment.type === 'hook_success' || - message.attachment.type === 'hook_system_message' || - message.attachment.type === 'hook_additional_context' || - message.attachment.type === 'hook_stopped_continuation') + (message.attachment?.type === 'hook_blocking_error' || + message.attachment?.type === 'hook_cancelled' || + message.attachment?.type === 'hook_error_during_execution' || + message.attachment?.type === 'hook_non_blocking_error' || + message.attachment?.type === 'hook_success' || + message.attachment?.type === 'hook_system_message' || + message.attachment?.type === 'hook_additional_context' || + message.attachment?.type === 'hook_stopped_continuation') ) } @@ -1105,11 +1112,11 @@ export function getToolResultIDs(normalizedMessages: NormalizedMessage[]): { } { return Object.fromEntries( normalizedMessages.flatMap(_ => - _.type === 'user' && Array.isArray(_.message.content) && _.message.content[0]?.type === 'tool_result' + _.type === 'user' && Array.isArray(_.message?.content) && (_.message?.content as Array<{type:string}>)[0]?.type === 'tool_result' ? [ [ - (_.message.content[0] as ToolResultBlockParam).tool_use_id, - (_.message.content[0] as ToolResultBlockParam).is_error ?? false, + ((_.message?.content as Array<{type:string}>)[0] as ToolResultBlockParam).tool_use_id, + ((_.message?.content as Array<{type:string}>)[0] as ToolResultBlockParam).is_error ?? false, ], ] : ([] as [string, boolean][]), @@ -1129,8 +1136,8 @@ export function getSiblingToolUseIDs( const unnormalizedMessage = messages.find( (_): _ is AssistantMessage => _.type === 'assistant' && - Array.isArray(_.message.content) && - _.message.content.some(block => block.type === 'tool_use' && (block as ToolUseBlock).id === toolUseID), + Array.isArray(_.message?.content) && + (_.message?.content as Array<{type:string; id?:string}>).some(block => block.type === 'tool_use' && block.id === toolUseID), ) if (!unnormalizedMessage) { return new Set() @@ -1139,13 +1146,13 @@ export function getSiblingToolUseIDs( const messageID = unnormalizedMessage.message.id const siblingMessages = messages.filter( (_): _ is AssistantMessage => - _.type === 'assistant' && _.message.id === messageID, + _.type === 'assistant' && _.message?.id === messageID, ) return new Set( siblingMessages.flatMap(_ => - Array.isArray(_.message.content) - ? _.message.content.filter(_ => _.type === 'tool_use').map(_ => (_ as ToolUseBlock).id) + Array.isArray(_.message?.content) + ? (_.message?.content as Array<{type:string; id?:string}>).filter(_ => _.type === 'tool_use').map(_ => _.id!) : [], ), ) @@ -1185,14 +1192,15 @@ export function buildMessageLookups( const toolUseByToolUseID = new Map() for (const msg of messages) { if (msg.type === 'assistant') { - const id = msg.message.id + const aMsg = msg as AssistantMessage + const id = aMsg.message.id! let toolUseIDs = toolUseIDsByMessageID.get(id) if (!toolUseIDs) { toolUseIDs = new Set() toolUseIDsByMessageID.set(id, toolUseIDs) } - if (Array.isArray(msg.message.content)) { - for (const content of msg.message.content) { + if (Array.isArray(aMsg.message.content)) { + for (const content of aMsg.message.content) { if (typeof content !== 'string' && content.type === 'tool_use') { const toolUseContent = content as ToolUseBlock toolUseIDs.add(toolUseContent.id) @@ -1247,8 +1255,8 @@ export function buildMessageLookups( } // Build tool result lookup and resolved/errored sets - if (msg.type === 'user' && Array.isArray(msg.message.content)) { - for (const content of msg.message.content) { + if (msg.type === 'user' && Array.isArray(msg.message?.content)) { + for (const content of (msg.message?.content ?? [])) { if (typeof content !== 'string' && content.type === 'tool_result') { const tr = content as ToolResultBlockParam toolResultByToolUseID.set(tr.tool_use_id, msg) @@ -1260,8 +1268,8 @@ export function buildMessageLookups( } } - if (msg.type === 'assistant' && Array.isArray(msg.message.content)) { - for (const content of msg.message.content) { + if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) { + for (const content of (msg.message?.content ?? [])) { if (typeof content === 'string') continue // Track all server-side *_tool_result blocks (advisor, web_search, // code_execution, mcp, etc.) — any block with tool_use_id is a result. @@ -1321,14 +1329,15 @@ export function buildMessageLookups( // perpetually spinning. const lastMsg = messages.at(-1) const lastAssistantMsgId = - lastMsg?.type === 'assistant' ? lastMsg.message.id : undefined + lastMsg?.type === 'assistant' ? lastMsg.message?.id : undefined for (const msg of normalizedMessages) { if (msg.type !== 'assistant') continue + const aMsg = msg as AssistantMessage // Skip blocks from the last original message if it's an assistant, // since it may still be in progress. - if (msg.message.id === lastAssistantMsgId) continue - if (!Array.isArray(msg.message.content)) continue - for (const content of msg.message.content) { + if (aMsg.message.id === lastAssistantMsgId) continue + if (!Array.isArray(aMsg.message.content)) continue + for (const content of aMsg.message.content) { if ( typeof content !== 'string' && ((content.type as string) === 'server_tool_use' || @@ -1483,10 +1492,10 @@ export function getToolUseIDs( .filter( (_): _ is NormalizedAssistantMessage => _.type === 'assistant' && - Array.isArray(_.message.content) && - _.message.content[0]?.type === 'tool_use', + Array.isArray(_.message?.content) && + (_.message?.content as Array<{type:string}>)[0]?.type === 'tool_use', ) - .map(_ => (_.message.content[0] as BetaToolUseBlock).id), + .map(_ => ((_.message?.content as Array)[0]).id), ) } @@ -1515,8 +1524,8 @@ export function reorderAttachmentsForAPI(messages: Message[]): Message[] { const isStoppingPoint = message.type === 'assistant' || (message.type === 'user' && - Array.isArray(message.message.content) && - message.message.content[0]?.type === 'tool_result') + Array.isArray(message.message?.content) && + (message.message?.content as Array<{type:string}>)[0]?.type === 'tool_result') if (isStoppingPoint && pendingAttachments.length > 0) { // Hit a stopping point — attachments stop here (go after the stopping point). @@ -1815,6 +1824,7 @@ function contentHasToolReference( */ function ensureSystemReminderWrap(msg: UserMessage): UserMessage { const content = msg.message.content + if (!content) return msg if (typeof content === 'string') { if (content.startsWith('')) return msg return { @@ -2397,8 +2407,8 @@ export function mergeUserMessagesAndToolResults( a: UserMessage, b: UserMessage, ): UserMessage { - const lastContent = normalizeUserTextContent(a.message.content) - const currentContent = normalizeUserTextContent(b.message.content) + const lastContent = normalizeUserTextContent(a.message.content as string | ContentBlockParam[]) + const currentContent = normalizeUserTextContent(b.message.content as string | ContentBlockParam[]) return { ...a, message: { @@ -2430,14 +2440,14 @@ function isToolResultMessage(msg: Message): boolean { if (msg.type !== 'user') { return false } - const content = msg.message.content - if (typeof content === 'string') return false - return content.some(block => block.type === 'tool_result') + const content = msg.message?.content + if (!content || typeof content === 'string') return false + return (content as Array<{type:string}>).some(block => block.type === 'tool_result') } export function mergeUserMessages(a: UserMessage, b: UserMessage): UserMessage { - const lastContent = normalizeUserTextContent(a.message.content) - const currentContent = normalizeUserTextContent(b.message.content) + const lastContent = normalizeUserTextContent(a.message.content as string | ContentBlockParam[]) + const currentContent = normalizeUserTextContent(b.message.content as string | ContentBlockParam[]) if (feature('HISTORY_SNIP')) { // A merged message is only meta if ALL merged messages are meta. If any // operand is real user content, the result must not be flagged isMeta @@ -2793,12 +2803,12 @@ export function getToolUseID(message: NormalizedMessage): string | null { switch (message.type) { case 'attachment': if (isHookAttachmentMessage(message)) { - return message.attachment.toolUseID + return message.attachment.toolUseID ?? null } return null case 'assistant': { - const aContent = Array.isArray(message.message.content) ? message.message.content : [] - const firstBlock = aContent[0] + const aContent = Array.isArray(message.message?.content) ? message.message?.content : [] + const firstBlock = aContent![0] if (!firstBlock || typeof firstBlock === 'string' || firstBlock.type !== 'tool_use') { return null } @@ -2808,8 +2818,8 @@ export function getToolUseID(message: NormalizedMessage): string | null { if (message.sourceToolUseID) { return message.sourceToolUseID as string } - const uContent = Array.isArray(message.message.content) ? message.message.content : [] - const firstUBlock = uContent[0] + const uContent = Array.isArray(message.message?.content) ? message.message?.content : [] + const firstUBlock = uContent![0] if (!firstUBlock || typeof firstUBlock === 'string' || firstUBlock.type !== 'tool_result') { return null } @@ -2821,6 +2831,8 @@ export function getToolUseID(message: NormalizedMessage): string | null { return (message.subtype as string) === 'informational' ? ((message.toolUseID as string) ?? null) : null + default: + return null } } @@ -2835,14 +2847,14 @@ export function filterUnresolvedToolUses(messages: Message[]): Message[] { for (const msg of messages) { if (msg.type !== 'user' && msg.type !== 'assistant') continue - const content = msg.message.content + const content = msg.message?.content if (!Array.isArray(content)) continue - for (const block of content) { + for (const block of content as Array<{type:string; id?:string; tool_use_id?:string}>) { if (block.type === 'tool_use') { - toolUseIds.add(block.id) + toolUseIds.add(block.id!) } if (block.type === 'tool_result') { - toolResultIds.add(block.tool_use_id) + toolResultIds.add(block.tool_use_id!) } } } @@ -2858,12 +2870,12 @@ export function filterUnresolvedToolUses(messages: Message[]): Message[] { // Filter out assistant messages whose tool_use blocks are all unresolved return messages.filter(msg => { if (msg.type !== 'assistant') return true - const content = msg.message.content + const content = msg.message?.content if (!Array.isArray(content)) return true const toolUseBlockIds: string[] = [] - for (const b of content) { + for (const b of content as Array<{type:string; id?:string}>) { if (b.type === 'tool_use') { - toolUseBlockIds.push(b.id) + toolUseBlockIds.push(b.id!) } } if (toolUseBlockIds.length === 0) return true @@ -2878,11 +2890,11 @@ export function getAssistantMessageText(message: Message): string | null { } // For content blocks array, extract and concatenate text blocks - if (Array.isArray(message.message.content)) { + if (Array.isArray(message.message?.content)) { return ( - message.message.content + (message.message?.content as Array<{type:string; text?:string}>) .filter(block => block.type === 'text') - .map(block => (block.type === 'text' ? block.text : '')) + .map(block => block.text ?? '') .join('\n') .trim() || null ) @@ -2897,9 +2909,9 @@ export function getUserMessageText( return null } - const content = message.message.content + const content = message.message?.content - return getContentText(content) + return getContentText(content as string | ContentBlockParam[]) } export function textForResubmit( @@ -4462,7 +4474,7 @@ export function createStopHookSummaryMessage( timestamp: new Date().toISOString(), uuid: randomUUID(), toolUseID, - hookLabel, + hookLabel: hookLabel ?? '', totalDurationMs, } } @@ -4720,8 +4732,8 @@ export function shouldShowUserMessage( export function isThinkingMessage(message: Message): boolean { if (message.type !== 'assistant') return false - if (!Array.isArray(message.message.content)) return false - return message.message.content.every( + if (!Array.isArray(message.message?.content)) return false + return (message.message?.content as Array<{type:string}>).every( block => block.type === 'thinking' || block.type === 'redacted_thinking', ) } @@ -4738,8 +4750,8 @@ export function countToolCalls( let count = 0 for (const msg of messages) { if (!msg) continue - if (msg.type === 'assistant' && Array.isArray(msg.message.content)) { - const hasToolUse = msg.message.content.some( + if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) { + const hasToolUse = (msg.message?.content as Array<{type:string; name?:string}>).some( (block): block is ToolUseBlock => block.type === 'tool_use' && block.name === toolName, ) @@ -4767,8 +4779,8 @@ export function hasSuccessfulToolCall( for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i] if (!msg) continue - if (msg.type === 'assistant' && Array.isArray(msg.message.content)) { - const toolUse = msg.message.content.find( + if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) { + const toolUse = (msg.message?.content as Array<{type:string; name?:string; id?:string}>).find( (block): block is ToolUseBlock => block.type === 'tool_use' && block.name === toolName, ) @@ -4785,8 +4797,8 @@ export function hasSuccessfulToolCall( for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i] if (!msg) continue - if (msg.type === 'user' && Array.isArray(msg.message.content)) { - const toolResult = msg.message.content.find( + if (msg.type === 'user' && Array.isArray(msg.message?.content)) { + const toolResult = (msg.message?.content as Array<{type:string; tool_use_id?:string; is_error?:boolean}>).find( (block): block is ToolResultBlockParam => block.type === 'tool_result' && block.tool_use_id === mostRecentToolUseId, @@ -4925,8 +4937,7 @@ export function filterWhitespaceOnlyAssistantMessages( return true } - const content = message.message.content - // Keep messages with empty arrays (handled elsewhere) or that have real content + const content = message.message?.content if (!Array.isArray(content) || content.length === 0) { return true } @@ -5046,14 +5057,14 @@ export function filterOrphanedThinkingOnlyMessages( for (const msg of messages) { if (msg.type !== 'assistant') continue - const content = msg.message.content + const content = msg.message?.content if (!Array.isArray(content)) continue - const hasNonThinking = content.some( + const hasNonThinking = (content as Array<{type:string}>).some( block => block.type !== 'thinking' && block.type !== 'redacted_thinking', ) - if (hasNonThinking && msg.message.id) { - messageIdsWithNonThinkingContent.add(msg.message.id) + if (hasNonThinking && msg.message?.id) { + messageIdsWithNonThinkingContent.add(msg.message.id as string) } } @@ -5063,13 +5074,13 @@ export function filterOrphanedThinkingOnlyMessages( return true } - const content = msg.message.content + const content = msg.message?.content if (!Array.isArray(content) || content.length === 0) { return true } // Check if ALL content blocks are thinking blocks - const allThinking = content.every( + const allThinking = (content as Array<{type:string}>).every( block => block.type === 'thinking' || block.type === 'redacted_thinking', ) @@ -5080,8 +5091,8 @@ export function filterOrphanedThinkingOnlyMessages( // It's thinking-only. Keep it if there's another message with same id // that has non-thinking content (they'll be merged later) if ( - msg.message.id && - messageIdsWithNonThinkingContent.has(msg.message.id) + msg.message?.id && + messageIdsWithNonThinkingContent.has(msg.message.id as string) ) { return true } @@ -5091,7 +5102,7 @@ export function filterOrphanedThinkingOnlyMessages( messageUUID: msg.uuid as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, messageId: msg.message - .id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + ?.id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, blockCount: content.length, }) return false @@ -5111,7 +5122,7 @@ export function stripSignatureBlocks(messages: Message[]): Message[] { const result = messages.map(msg => { if (msg.type !== 'assistant') return msg - const content = msg.message.content + const content = (msg as AssistantMessage).message.content if (!Array.isArray(content)) return msg const filtered = content.filter(block => { @@ -5247,7 +5258,8 @@ export function ensureToolResultPairing( // Collect server-side tool result IDs (*_tool_result blocks have tool_use_id). const serverResultIds = new Set() - for (const c of msg.message.content) { + const aMsg5 = msg as AssistantMessage + for (const c of aMsg5.message.content as (ContentBlockParam | ContentBlock)[]) { if (typeof c !== 'string' && 'tool_use_id' in c && typeof (c as { tool_use_id: string }).tool_use_id === 'string') { serverResultIds.add((c as { tool_use_id: string }).tool_use_id) } @@ -5266,7 +5278,7 @@ export function ensureToolResultPairing( // has no matching *_tool_result and the API rejects with e.g. "advisor // tool use without corresponding advisor_tool_result". const seenToolUseIds = new Set() - const assistantContent = Array.isArray(msg.message.content) ? msg.message.content : [] + const assistantContent = Array.isArray(aMsg5.message.content) ? aMsg5.message.content : [] const finalContent = assistantContent.filter(block => { if (typeof block === 'string') return true if (block.type === 'tool_use') { @@ -5288,7 +5300,7 @@ export function ensureToolResultPairing( }) const assistantContentChanged = - finalContent.length !== msg.message.content.length + finalContent.length !== (aMsg5.message.content as (ContentBlockParam | ContentBlock)[]).length // If stripping orphaned server tool uses empties the content array, // insert a placeholder so the API doesn't reject empty assistant content. @@ -5372,11 +5384,12 @@ export function ensureToolResultPairing( if (nextMsg?.type === 'user') { // Next message is already a user message - patch it + const nextUserMsg = nextMsg as UserMessage let content: (ContentBlockParam | ContentBlock)[] = Array.isArray( - nextMsg.message.content, + nextUserMsg.message.content, ) - ? nextMsg.message.content - : [{ type: 'text' as const, text: nextMsg.message.content }] + ? nextUserMsg.message.content as (ContentBlockParam | ContentBlock)[] + : [{ type: 'text' as const, text: (nextUserMsg.message.content as string | undefined) ?? '' }] // Strip orphaned tool_results and dedupe duplicate tool_result IDs if (orphanedIds.length > 0 || hasDuplicateToolResults) { @@ -5402,9 +5415,9 @@ export function ensureToolResultPairing( // If content is now empty after stripping orphans, skip the user message if (patchedContent.length > 0) { const patchedNext: UserMessage = { - ...nextMsg, + ...nextUserMsg, message: { - ...nextMsg.message, + ...nextUserMsg.message, content: patchedContent, }, } diff --git a/src/utils/messages/mappers.ts b/src/utils/messages/mappers.ts index c94ace87d..d04a4235e 100644 --- a/src/utils/messages/mappers.ts +++ b/src/utils/messages/mappers.ts @@ -48,10 +48,9 @@ export function toInternalMessages( uuid: message.uuid ?? randomUUID(), timestamp: message.timestamp ?? new Date().toISOString(), isMeta: message.isSynthetic, - } as Message, + } as unknown as Message, ] - case 'system': - // Handle compact boundary messages + // Handle compact boundary messages if (message.subtype === 'compact_boundary') { const compactMsg = message return [ @@ -272,7 +271,7 @@ function normalizeAssistantMessageForSDK( const normalizedContent = content.map((block): BetaContentBlock => { if (block.type !== 'tool_use') { - return block + return block as unknown as BetaContentBlock } if (block.name === EXIT_PLAN_MODE_V2_TOOL_NAME) { diff --git a/src/utils/notebook.ts b/src/utils/notebook.ts index 3630b0980..8640e2685 100644 --- a/src/utils/notebook.ts +++ b/src/utils/notebook.ts @@ -171,13 +171,13 @@ export async function readNotebook( const notebook = jsonParse(content) as NotebookContent const language = notebook.metadata.language_info?.name ?? 'python' if (cellId) { - const cell = notebook.cells.find(c => c.id === cellId) + const cell = notebook.cells.find((c: NotebookCell) => c.id === cellId) if (!cell) { throw new Error(`Cell with ID "${cellId}" not found in notebook`) } return [processCell(cell, notebook.cells.indexOf(cell), language, true)] } - return notebook.cells.map((cell, index) => + return notebook.cells.map((cell: NotebookCell, index: number) => processCell(cell, index, language, false), ) } diff --git a/src/utils/permissions/yoloClassifier.ts b/src/utils/permissions/yoloClassifier.ts index c311a6c16..8f0b3cc4f 100644 --- a/src/utils/permissions/yoloClassifier.ts +++ b/src/utils/permissions/yoloClassifier.ts @@ -302,8 +302,8 @@ export type TranscriptEntry = { export function buildTranscriptEntries(messages: Message[]): TranscriptEntry[] { const transcript: TranscriptEntry[] = [] for (const msg of messages) { - if (msg.type === 'attachment' && msg.attachment.type === 'queued_command') { - const prompt = msg.attachment.prompt + if (msg.type === 'attachment' && msg.attachment!.type === 'queued_command') { + const prompt = msg.attachment!.prompt let text: string | null = null if (typeof prompt === 'string') { text = prompt @@ -324,7 +324,7 @@ export function buildTranscriptEntries(messages: Message[]): TranscriptEntry[] { }) } } else if (msg.type === 'user') { - const content = msg.message.content + const content = msg.message!.content const textBlocks: TranscriptBlock[] = [] if (typeof content === 'string') { textBlocks.push({ type: 'text', text: content }) @@ -340,7 +340,7 @@ export function buildTranscriptEntries(messages: Message[]): TranscriptEntry[] { } } else if (msg.type === 'assistant') { const blocks: TranscriptBlock[] = [] - for (const block of msg.message.content) { + for (const block of (msg.message!.content ?? [])) { // Only include tool_use blocks — assistant text is model-authored // and could be crafted to influence the classifier's decision. if (typeof block !== 'string' && block.type === 'tool_use') { diff --git a/src/utils/plugins/loadPluginHooks.ts b/src/utils/plugins/loadPluginHooks.ts index 92eaa249f..283b59c1d 100644 --- a/src/utils/plugins/loadPluginHooks.ts +++ b/src/utils/plugins/loadPluginHooks.ts @@ -69,7 +69,7 @@ function convertPluginHooksToMatchers( continue } - for (const matcher of matchers) { + for (const matcher of (matchers ?? [])) { if (matcher.hooks.length > 0) { pluginMatchers[hookEvent].push({ matcher: matcher.matcher, @@ -195,7 +195,7 @@ export async function pruneRemovedPluginHooks(): Promise { // clearRegisteredPluginHooks; we only need to re-register survivors. const survivors: Partial> = {} for (const [event, matchers] of Object.entries(current)) { - const kept = matchers.filter( + const kept = (matchers ?? []).filter( (m): m is PluginHookMatcher => 'pluginRoot' in m && enabledRoots.has(m.pluginRoot), ) diff --git a/src/utils/plugins/lspPluginIntegration.ts b/src/utils/plugins/lspPluginIntegration.ts index c5c565512..57b72651e 100644 --- a/src/utils/plugins/lspPluginIntegration.ts +++ b/src/utils/plugins/lspPluginIntegration.ts @@ -259,7 +259,7 @@ export function resolvePluginLspEnvironment( // Resolve args if (resolved.args) { - resolved.args = resolved.args.map(arg => resolveValue(arg)) + resolved.args = resolved.args.map((arg: string) => resolveValue(arg)) } // Resolve environment variables and add CLAUDE_PLUGIN_ROOT / CLAUDE_PLUGIN_DATA diff --git a/src/utils/plugins/pluginLoader.ts b/src/utils/plugins/pluginLoader.ts index 0c861bf42..00ee20b35 100644 --- a/src/utils/plugins/pluginLoader.ts +++ b/src/utils/plugins/pluginLoader.ts @@ -81,6 +81,7 @@ import { setPluginSettingsBase, } from '../settings/settingsCache.js' import type { HooksSettings } from '../settings/types.js' +import type { HookMatcher } from '../../schemas/hooks.js' import { SettingsSchema } from '../settings/types.js' import { jsonParse, jsonStringify } from '../slowOperations.js' import { getAddDirEnabledPlugins } from './addDirPluginSettings.js' @@ -1861,15 +1862,13 @@ function mergeHooksSettings( const merged = { ...base } - for (const [event, matchers] of Object.entries(additional)) { + for (const [event, matchers] of Object.entries(additional) as [string, HookMatcher[]][]) { if (!merged[event as keyof HooksSettings]) { merged[event as keyof HooksSettings] = matchers } else { // Merge matchers for this event - merged[event as keyof HooksSettings] = [ - ...(merged[event as keyof HooksSettings] || []), - ...matchers, - ] + const existing = ((merged[event as keyof HooksSettings] as unknown) ?? []) as HookMatcher[] + merged[event as keyof HooksSettings] = [...existing, ...matchers] } } diff --git a/src/utils/processUserInput/processUserInput.ts b/src/utils/processUserInput/processUserInput.ts index f33acc957..82afc7dd9 100644 --- a/src/utils/processUserInput/processUserInput.ts +++ b/src/utils/processUserInput/processUserInput.ts @@ -241,17 +241,17 @@ export async function processUserInput({ // TODO: Clean this up if (hookResult.message) { - switch (hookResult.message.attachment.type) { + switch (hookResult.message.attachment!.type) { case 'hook_success': - if (!hookResult.message.attachment.content) { + if (!hookResult.message.attachment!.content) { // Skip if there is no content break } result.messages.push({ ...hookResult.message, attachment: { - ...hookResult.message.attachment, - content: applyTruncation(hookResult.message.attachment.content as string), + ...hookResult.message.attachment!, + content: applyTruncation(hookResult.message.attachment!.content as string), }, } as AttachmentMessage) break diff --git a/src/utils/queryContext.ts b/src/utils/queryContext.ts index 67dc62c2f..0312111cd 100644 --- a/src/utils/queryContext.ts +++ b/src/utils/queryContext.ts @@ -135,7 +135,7 @@ export async function buildSideQuestionFallbackParams({ // as btw.tsx. The SDK can fire side_question mid-turn. const last = messages.at(-1) const forkContextMessages = - last?.type === 'assistant' && last.message.stop_reason === null + last?.type === 'assistant' && last.message!.stop_reason === null ? messages.slice(0, -1) : messages diff --git a/src/utils/queryHelpers.ts b/src/utils/queryHelpers.ts index c1c82bb6e..c08428d69 100644 --- a/src/utils/queryHelpers.ts +++ b/src/utils/queryHelpers.ts @@ -68,7 +68,8 @@ export function isResultSuccessful( if (!message) return false if (message.type === 'assistant') { - const lastContent = last(message.message.content) + const content = message.message!.content + const lastContent = Array.isArray(content) ? content[content.length - 1] : undefined return ( lastContent?.type === 'text' || lastContent?.type === 'thinking' || @@ -78,7 +79,7 @@ export function isResultSuccessful( if (message.type === 'user') { // Check if all content blocks are tool_result type - const content = message.message.content + const content = message.message!.content if ( Array.isArray(content) && content.length > 0 && @@ -323,8 +324,8 @@ export async function* handleOrphanedPermission( const alreadyPresent = mutableMessages.some( m => m.type === 'assistant' && - Array.isArray(m.message.content) && - m.message.content.some( + Array.isArray(m.message!.content) && + m.message!.content.some( b => b.type === 'tool_use' && 'id' in b && b.id === toolUseID, ), ) @@ -385,9 +386,9 @@ export function extractReadFilesFromMessages( for (const message of messages) { if ( message.type === 'assistant' && - Array.isArray(message.message.content) + Array.isArray(message.message!.content) ) { - for (const content of message.message.content) { + for (const content of message.message!.content) { if ( content.type === 'tool_use' && content.name === FILE_READ_TOOL_NAME @@ -442,8 +443,8 @@ export function extractReadFilesFromMessages( // Second pass: find corresponding tool results and extract content for (const message of messages) { - if (message.type === 'user' && Array.isArray(message.message.content)) { - for (const content of message.message.content) { + if (message.type === 'user' && Array.isArray(message.message!.content)) { + for (const content of message.message!.content) { if (content.type === 'tool_result' && content.tool_use_id) { // Handle Read tool results const readFilePath = fileReadToolUseIds.get(content.tool_use_id) @@ -537,9 +538,9 @@ export function extractBashToolsFromMessages(messages: Message[]): Set { for (const message of messages) { if ( message.type === 'assistant' && - Array.isArray(message.message.content) + Array.isArray(message.message!.content) ) { - for (const content of message.message.content) { + for (const content of message.message!.content) { if (content.type === 'tool_use' && content.name === BASH_TOOL_NAME) { const { input } = content if ( diff --git a/src/utils/sessionRestore.ts b/src/utils/sessionRestore.ts index dc5f8d55f..9f77841c7 100644 --- a/src/utils/sessionRestore.ts +++ b/src/utils/sessionRestore.ts @@ -78,7 +78,7 @@ 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 toolUse = (msg.message!.content as any[]).find( block => block.type === 'tool_use' && block.name === TODO_WRITE_TOOL_NAME, ) if (!toolUse || toolUse.type !== 'tool_use') continue diff --git a/src/utils/sessionStorage.ts b/src/utils/sessionStorage.ts index 291aecb00..579e1b7b7 100644 --- a/src/utils/sessionStorage.ts +++ b/src/utils/sessionStorage.ts @@ -1927,9 +1927,9 @@ function applyPreservedSegmentRelinks( messages.set(uuid, { ...msg, message: { - ...msg.message, + ...msg.message!, usage: { - ...msg.message.usage, + ...msg.message!.usage, input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, @@ -2131,7 +2131,7 @@ function recoverOrphanedParallelToolResults( // already in chain order, so later iterations overwrite → last wins. const anchorByMsgId = new Map() for (const a of chainAssistants) { - if (a.message.id) anchorByMsgId.set(a.message.id, a) + if (a.message!.id) anchorByMsgId.set(a.message!.id, a) } // O(n) precompute: sibling groups and TR index. @@ -2140,15 +2140,15 @@ function recoverOrphanedParallelToolResults( const siblingsByMsgId = new Map() const toolResultsByAsst = new Map() for (const m of messages.values()) { - if (m.type === 'assistant' && m.message.id) { - const group = siblingsByMsgId.get(m.message.id) + if (m.type === 'assistant' && m.message!.id) { + const group = siblingsByMsgId.get(m.message!.id) if (group) group.push(m) - else siblingsByMsgId.set(m.message.id, [m]) + else siblingsByMsgId.set(m.message!.id, [m]) } else if ( m.type === 'user' && m.parentUuid && - Array.isArray(m.message.content) && - m.message.content.some(b => b.type === 'tool_result') + Array.isArray(m.message!.content) && + (m.message!.content as Array<{type: string}>).some(b => b.type === 'tool_result') ) { const group = toolResultsByAsst.get(m.parentUuid) if (group) group.push(m) @@ -2164,7 +2164,7 @@ function recoverOrphanedParallelToolResults( const inserts = new Map() let recoveredCount = 0 for (const asst of chainAssistants) { - const msgId = asst.message.id + const msgId = asst.message!.id if (!msgId || processedGroups.has(msgId)) continue processedGroups.add(msgId) @@ -4357,7 +4357,7 @@ export function isLoggableMessage(m: Message): boolean { // user-configured hook output that is useful for session context on resume. if (m.type === 'attachment' && getUserType() !== 'ant') { if ( - m.attachment.type === 'hook_additional_context' && + m.attachment!.type === 'hook_additional_context' && isEnvTruthy(process.env.CLAUDE_CODE_SAVE_HOOK_ADDITIONAL_CONTEXT) ) { return true @@ -4370,8 +4370,8 @@ export function isLoggableMessage(m: Message): boolean { function collectReplIds(messages: readonly Message[]): Set { const ids = new Set() for (const m of messages) { - if (m.type === 'assistant' && Array.isArray(m.message.content)) { - for (const b of m.message.content) { + if (m.type === 'assistant' && Array.isArray(m.message!.content)) { + for (const b of m.message!.content as Array<{type: string; name: string; id: string}>) { if (b.type === 'tool_use' && b.name === REPL_TOOL_NAME) { ids.add(b.id) } @@ -4488,9 +4488,9 @@ export async function findUnresolvedToolUse( // Find the tool use but make sure there's not also a result for (const message of messages.values()) { if (message.type === 'assistant') { - const content = message.message.content + const content = message.message!.content if (Array.isArray(content)) { - for (const block of content) { + for (const block of content as Array<{type: string; id: string}>) { if (block.type === 'tool_use' && block.id === toolUseId) { toolUseMessage = message break @@ -4498,9 +4498,9 @@ export async function findUnresolvedToolUse( } } } else if (message.type === 'user') { - const content = message.message.content + const content = message.message!.content if (Array.isArray(content)) { - for (const block of content) { + for (const block of content as Array<{type: string; tool_use_id: string}>) { if ( block.type === 'tool_result' && block.tool_use_id === toolUseId @@ -4513,7 +4513,7 @@ export async function findUnresolvedToolUse( } } - return toolUseMessage + return toolUseMessage as AssistantMessage | null } catch { return null } diff --git a/src/utils/sessionTitle.ts b/src/utils/sessionTitle.ts index ccbd3ec74..ba413e8d0 100644 --- a/src/utils/sessionTitle.ts +++ b/src/utils/sessionTitle.ts @@ -36,7 +36,7 @@ export function extractConversationText(messages: Message[]): string { if (msg.type !== 'user' && msg.type !== 'assistant') continue if ('isMeta' in msg && msg.isMeta) continue if ('origin' in msg && (msg as any).origin && (msg as any).origin.kind !== 'human') continue - const content = msg.message.content + const content = msg.message!.content if (typeof content === 'string') { parts.push(content) } else if (Array.isArray(content)) { diff --git a/src/utils/sideQuestion.ts b/src/utils/sideQuestion.ts index 860d42107..4d2755e20 100644 --- a/src/utils/sideQuestion.ts +++ b/src/utils/sideQuestion.ts @@ -125,7 +125,7 @@ ${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) { diff --git a/src/utils/streamlinedTransform.ts b/src/utils/streamlinedTransform.ts index d8e13fce8..abe3ccdf6 100644 --- a/src/utils/streamlinedTransform.ts +++ b/src/utils/streamlinedTransform.ts @@ -110,7 +110,7 @@ function accumulateToolUses( message: SDKAssistantMessage, counts: ToolCounts, ): void { - const content = message.message.content + const content = message.message!.content if (!Array.isArray(content)) { return } diff --git a/src/utils/swarm/inProcessRunner.ts b/src/utils/swarm/inProcessRunner.ts index f24af327a..245acf19f 100644 --- a/src/utils/swarm/inProcessRunner.ts +++ b/src/utils/swarm/inProcessRunner.ts @@ -1235,7 +1235,7 @@ export async function runInProcessTeammate( // Track in-progress tool use IDs for animation in transcript view let inProgressToolUseIDs = task.inProgressToolUseIDs if (message.type === 'assistant') { - for (const block of (Array.isArray(message.message.content) ? message.message.content : [])) { + for (const block of (Array.isArray(message.message!.content) ? message.message!.content : [])) { if (typeof block !== 'string' && block.type === 'tool_use') { inProgressToolUseIDs = new Set([ ...(inProgressToolUseIDs ?? []), @@ -1244,7 +1244,7 @@ export async function runInProcessTeammate( } } } else if (message.type === 'user') { - const content = message.message.content + const content = message.message!.content if (Array.isArray(content)) { for (const block of content) { if ( diff --git a/src/utils/teammateMailbox.ts b/src/utils/teammateMailbox.ts index f0610497f..d2841766a 100644 --- a/src/utils/teammateMailbox.ts +++ b/src/utils/teammateMailbox.ts @@ -1152,7 +1152,7 @@ export function getLastPeerDmSummary(messages: Message[]): string | undefined { if (!msg) continue // Stop at wake-up boundary: a user prompt (string content), not tool results (array content) - if (msg.type === 'user' && typeof msg.message.content === 'string') { + if (msg.type === 'user' && typeof msg.message!.content === 'string') { break } diff --git a/src/utils/teleport.tsx b/src/utils/teleport.tsx index bdbd6e05c..e3330880c 100644 --- a/src/utils/teleport.tsx +++ b/src/utils/teleport.tsx @@ -184,7 +184,7 @@ async function generateTitleAndBranch( }) // Extract text from the response - const firstBlock = response.message.content[0] as { type?: string; text?: string } | undefined + const firstBlock = response.message!.content?.[0] as { type?: string; text?: string } | undefined if (firstBlock?.type !== 'text') { return { title: fallbackTitle, branchName: fallbackBranch } } diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts index 65f9a135a..e326d350d 100644 --- a/src/utils/tokens.ts +++ b/src/utils/tokens.ts @@ -30,10 +30,10 @@ export function getTokenUsage(message: Message): Usage | undefined { function getAssistantMessageId(message: Message): string | undefined { if ( message?.type === 'assistant' && - 'id' in message.message && - message.message.model !== SYNTHETIC_MODEL + 'id' in message.message! && + message.message!.model !== SYNTHETIC_MODEL ) { - return message.message.id + return message.message!.id } return undefined } diff --git a/src/utils/toolResultStorage.ts b/src/utils/toolResultStorage.ts index f4dfef326..9d2819bc6 100644 --- a/src/utils/toolResultStorage.ts +++ b/src/utils/toolResultStorage.ts @@ -537,7 +537,7 @@ function buildToolNameMap(messages: Message[]): Map { const map = new Map() for (const message of messages) { if (message.type !== 'assistant') continue - const content = message.message.content + const content = message.message!.content if (!Array.isArray(content)) continue for (const block of content) { if (block.type === 'tool_use') { @@ -555,10 +555,10 @@ function buildToolNameMap(messages: Message[]): Map { * Returns [] for messages with no eligible blocks. */ function collectCandidatesFromMessage(message: Message): ToolResultCandidate[] { - if (message.type !== 'user' || !Array.isArray(message.message.content)) { + if (message.type !== 'user' || !Array.isArray(message.message!.content)) { return [] } - return message.message.content.flatMap(block => { + return message.message!.content.flatMap(block => { if (block.type !== 'tool_result' || !block.content) return [] if (isContentAlreadyCompacted(block.content)) return [] if (hasImageBlock(block.content)) return [] @@ -625,9 +625,9 @@ function collectCandidatesByMessage( if (message.type === 'user') { current.push(...collectCandidatesFromMessage(message)) } else if (message.type === 'assistant') { - if (!seenAsstIds.has(message.message.id)) { + if (!seenAsstIds.has(message.message!.id ?? '')) { flush() - seenAsstIds.add(message.message.id) + seenAsstIds.add(message.message!.id ?? '') } } // progress / attachment / system are filtered or merged by @@ -701,10 +701,10 @@ function replaceToolResultContents( replacementMap: Map, ): Message[] { return messages.map(message => { - if (message.type !== 'user' || !Array.isArray(message.message.content)) { + if (message.type !== 'user' || !Array.isArray(message.message!.content)) { return message } - const content = message.message.content + const content = message.message!.content const needsReplace = content.some( b => b.type === 'tool_result' && replacementMap.has(b.tool_use_id), ) diff --git a/src/utils/toolSearch.ts b/src/utils/toolSearch.ts index 4531470fd..a860c2b36 100644 --- a/src/utils/toolSearch.ts +++ b/src/utils/toolSearch.ts @@ -655,11 +655,11 @@ export function getDeferredToolsDelta( for (const msg of messages) { if (msg.type !== 'attachment') continue attachmentCount++ - attachmentTypesSeen.add(msg.attachment.type) - if (msg.attachment.type !== 'deferred_tools_delta') continue + attachmentTypesSeen.add(msg.attachment!.type) + if (msg.attachment!.type !== 'deferred_tools_delta') continue dtdCount++ - for (const n of (msg.attachment as any).addedNames) announced.add(n) - for (const n of (msg.attachment as any).removedNames) announced.delete(n) + for (const n of msg.attachment!.addedNames) announced.add(n) + for (const n of msg.attachment!.removedNames) announced.delete(n) } const deferred: Tool[] = tools.filter(isDeferredTool) diff --git a/src/utils/transcriptSearch.ts b/src/utils/transcriptSearch.ts index 80e7bf27f..e8219c4a2 100644 --- a/src/utils/transcriptSearch.ts +++ b/src/utils/transcriptSearch.ts @@ -33,12 +33,12 @@ function computeSearchText(msg: RenderableMessage): string { let raw = '' switch (msg.type) { case 'user': { - const c = msg.message.content + const c = msg.message!.content if (typeof c === 'string') { raw = RENDERED_AS_SENTINEL.has(c) ? '' : c } else { const parts: string[] = [] - for (const b of c) { + for (const b of (c ?? [])) { if (b.type === 'text') { if (!RENDERED_AS_SENTINEL.has(b.text)) parts.push(b.text) } else if (b.type === 'tool_result') { @@ -83,8 +83,8 @@ function computeSearchText(msg: RenderableMessage): string { // relevant_memories renders full m.content in transcript mode // (AttachmentMessage.tsx {m.content}). Visible but // unsearchable without this — [ dump finds it, / doesn't. - if (msg.attachment.type === 'relevant_memories') { - raw = msg.attachment.memories.map(m => m.content).join('\n') + if (msg.attachment!.type === 'relevant_memories') { + raw = (msg.attachment!.memories ?? []).map((m: { content: string }) => m.content).join('\n') } else if ( // Mid-turn prompts — queued while an agent is running. Render via // UserTextMessage (AttachmentMessage.tsx:~348). stickyPromptText