Ver código fonte

feat: 问就是封包

claude-code-best 3 semanas atrás
pai
commit
dd9cd782a7
67 arquivos alterados com 426 adições e 175 exclusões
  1. 248 21
      README.md
  2. 6 3
      package.json
  3. 1 1
      packages/color-diff-napi/src/index.ts
  4. 1 1
      src/bootstrap/state.ts
  5. 2 2
      src/cli/handlers/auth.ts
  6. 2 2
      src/cli/handlers/mcp.tsx
  7. 4 4
      src/cli/print.ts
  8. 3 3
      src/cli/transports/ccrClient.ts
  9. 2 2
      src/commands/branch/branch.ts
  10. 3 3
      src/commands/plugin/BrowseMarketplace.tsx
  11. 2 2
      src/commands/plugin/DiscoverPlugins.tsx
  12. 3 2
      src/commands/remote-setup/remote-setup.tsx
  13. 4 3
      src/commands/reset-limits/index.ts
  14. 3 2
      src/commands/resume/resume.tsx
  15. 1 1
      src/commands/review/reviewRemote.ts
  16. 3 2
      src/commands/ultraplan.tsx
  17. 1 1
      src/components/ConsoleOAuthFlow.tsx
  18. 5 2
      src/components/Feedback.tsx
  19. 1 1
      src/components/FileEditToolDiff.tsx
  20. 1 1
      src/components/HighlightedCode/Fallback.tsx
  21. 2 2
      src/components/Markdown.tsx
  22. 1 1
      src/components/Message.tsx
  23. 1 1
      src/components/MessageSelector.tsx
  24. 2 1
      src/components/Settings/Settings.tsx
  25. 1 1
      src/components/Settings/Status.tsx
  26. 4 4
      src/components/Spinner/GlimmerMessage.tsx
  27. 1 1
      src/components/StatusNotices.tsx
  28. 2 2
      src/components/TextInput.tsx
  29. 1 1
      src/components/ThemePicker.tsx
  30. 3 3
      src/components/VirtualMessageList.tsx
  31. 2 2
      src/components/agents/ToolSelector.tsx
  32. 1 1
      src/components/memory/MemoryFileSelector.tsx
  33. 1 1
      src/components/messages/AssistantToolUseMessage.tsx
  34. 1 3
      src/components/messages/nullRenderingAttachments.ts
  35. 2 2
      src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.tsx
  36. 1 1
      src/components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.tsx
  37. 1 1
      src/components/permissions/PermissionDecisionDebugInfo.tsx
  38. 1 1
      src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.tsx
  39. 1 1
      src/components/tasks/ShellDetailDialog.tsx
  40. 3 3
      src/hooks/useCanUseTool.tsx
  41. 2 2
      src/hooks/useReplBridge.tsx
  42. 2 2
      src/hooks/useVoiceIntegration.tsx
  43. 1 1
      src/ink/Ansi.tsx
  44. 2 2
      src/services/api/client.ts
  45. 2 2
      src/services/api/errors.ts
  46. 2 1
      src/services/mcp/config.ts
  47. 3 3
      src/services/tools/toolHooks.ts
  48. 6 6
      src/services/vcr.ts
  49. 2 2
      src/tasks/LocalShellTask/LocalShellTask.tsx
  50. 6 6
      src/tools/AgentTool/runAgent.ts
  51. 1 1
      src/tools/FileEditTool/UI.tsx
  52. 6 6
      src/tools/FileReadTool/FileReadTool.ts
  53. 2 2
      src/tools/SendMessageTool/SendMessageTool.ts
  54. 11 0
      src/types/global.d.ts
  55. 5 5
      src/upstreamproxy/relay.ts
  56. 2 2
      src/utils/cronScheduler.ts
  57. 8 8
      src/utils/dxt/helpers.ts
  58. 3 3
      src/utils/filePersistence/outputsScanner.ts
  59. 3 3
      src/utils/forkedAgent.ts
  60. 4 3
      src/utils/permissions/filesystem.ts
  61. 5 4
      src/utils/permissions/pathValidation.ts
  62. 3 3
      src/utils/permissions/permissions.ts
  63. 3 3
      src/utils/processUserInput/processUserInput.ts
  64. 3 3
      src/utils/sideQuestion.ts
  65. 2 2
      src/utils/sliceAnsi.ts
  66. 6 5
      src/utils/teleport/gitBundle.ts
  67. 3 3
      src/utils/toolSearch.ts

+ 248 - 21
README.md

@@ -2,27 +2,254 @@
 
 Anthropic 官方 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI 工具的源码反编译/逆向还原项目。目标是将 Claude Code 核心功能跑通,必要时删减次级能力。
 
-## 核心能力
-
-- API 通信(Anthropic SDK / Bedrock / Vertex)
-- Bash / FileRead / FileWrite / FileEdit 等核心工具
-- REPL 交互界面(ink 终端渲染)
-- 对话历史与会话管理
-- 权限系统
-- Agent / 子代理系统
-
-## 已删减模块
-
-| 模块 | 处理方式 |
-|------|----------|
-| Computer Use (`@ant/computer-use-*`) | stub |
-| Claude for Chrome MCP | stub |
-| Magic Docs / Voice Mode / LSP Server | 移除 |
-| Analytics / GrowthBook / Sentry | 空实现 |
-| Plugins / Marketplace / Desktop Upsell | 移除 |
-| Ultraplan / Tungsten / Auto Dream | 移除 |
-| MCP OAuth/IDP | 简化 |
-| DAEMON / BRIDGE / BG_SESSIONS / TEMPLATES 等 | feature flag 关闭 |
+## 能力清单
+
+> ✅ = 已实现  ⚠️ = 部分实现 / 条件启用  ❌ = stub / 移除 / feature flag 关闭
+
+### 核心系统
+
+| 能力 | 状态 | 说明 |
+|------|------|------|
+| REPL 交互界面(Ink 终端渲染) | ✅ | 主屏幕 5000+ 行,完整交互 |
+| API 通信 — Anthropic Direct | ✅ | 支持 API Key + OAuth |
+| API 通信 — AWS Bedrock | ✅ | 支持凭据刷新、Bearer Token |
+| API 通信 — Google Vertex | ✅ | 支持 GCP 凭据刷新 |
+| API 通信 — Azure Foundry | ✅ | 支持 API Key + Azure AD |
+| 流式对话与工具调用循环 (`query.ts`) | ✅ | 1700+ 行,含自动压缩、token 追踪 |
+| 会话引擎 (`QueryEngine.ts`) | ✅ | 1300+ 行,管理对话状态与归因 |
+| 上下文构建(git status / CLAUDE.md / memory) | ✅ | `context.ts` 完整实现 |
+| 权限系统(plan/auto/manual 模式) | ✅ | 6300+ 行,含 YOLO 分类器、路径验证、规则匹配 |
+| Hook 系统(pre/post tool use) | ✅ | 支持 settings.json 配置 |
+| 会话恢复 (`/resume`) | ✅ | 独立 ResumeConversation 屏幕 |
+| Doctor 诊断 (`/doctor`) | ✅ | 版本、API、插件、沙箱检查 |
+| 自动压缩 (compaction) | ✅ | auto-compact / micro-compact / API compact |
+
+### 工具 — 始终可用
+
+| 工具 | 状态 | 说明 |
+|------|------|------|
+| BashTool | ✅ | Shell 执行,沙箱,权限检查 |
+| FileReadTool | ✅ | 文件 / PDF / 图片 / Notebook 读取 |
+| FileEditTool | ✅ | 字符串替换式编辑 + diff 追踪 |
+| FileWriteTool | ✅ | 文件创建 / 覆写 + diff 生成 |
+| NotebookEditTool | ✅ | Jupyter Notebook 单元格编辑 |
+| AgentTool | ✅ | 子代理派生(fork / async / background / remote) |
+| WebFetchTool | ✅ | URL 抓取 → Markdown → AI 摘要 |
+| WebSearchTool | ✅ | 网页搜索 + 域名过滤 |
+| AskUserQuestionTool | ✅ | 多问题交互提示 + 预览 |
+| SendMessageTool | ✅ | 消息发送(peers / teammates / mailbox) |
+| SkillTool | ✅ | 斜杠命令 / Skill 调用 |
+| EnterPlanModeTool | ✅ | 进入计划模式 |
+| ExitPlanModeTool (V2) | ✅ | 退出计划模式 |
+| TodoWriteTool | ✅ | Todo 列表 v1 |
+| BriefTool | ✅ | 简短消息 + 附件发送 |
+| TaskOutputTool | ✅ | 后台任务输出读取 |
+| TaskStopTool | ✅ | 后台任务停止 |
+| ListMcpResourcesTool | ✅ | MCP 资源列表 |
+| ReadMcpResourceTool | ✅ | MCP 资源读取 |
+| SyntheticOutputTool | ✅ | 非交互会话结构化输出 |
+
+### 工具 — 条件启用
+
+| 工具 | 状态 | 启用条件 |
+|------|------|----------|
+| GlobTool | ✅ | 未嵌入 bfs/ugrep 时启用(默认启用) |
+| GrepTool | ✅ | 同上 |
+| TaskCreateTool | ⚠️ | `isTodoV2Enabled()` 为 true 时 |
+| TaskGetTool | ⚠️ | 同上 |
+| TaskUpdateTool | ⚠️ | 同上 |
+| TaskListTool | ⚠️ | 同上 |
+| EnterWorktreeTool | ⚠️ | `isWorktreeModeEnabled()` |
+| ExitWorktreeTool | ⚠️ | 同上 |
+| TeamCreateTool | ⚠️ | `isAgentSwarmsEnabled()` |
+| TeamDeleteTool | ⚠️ | 同上 |
+| ToolSearchTool | ⚠️ | `isToolSearchEnabledOptimistic()` |
+| PowerShellTool | ⚠️ | Windows 平台检测 |
+| LSPTool | ⚠️ | `ENABLE_LSP_TOOL` 环境变量 |
+| ConfigTool | ❌ | `USER_TYPE === 'ant'`(永远为 false) |
+
+### 工具 — Feature Flag 关闭(全部不可用)
+
+| 工具 | Feature Flag |
+|------|-------------|
+| SleepTool | `PROACTIVE` / `KAIROS` |
+| CronCreate/Delete/ListTool | `AGENT_TRIGGERS` |
+| RemoteTriggerTool | `AGENT_TRIGGERS_REMOTE` |
+| MonitorTool | `MONITOR_TOOL` |
+| SendUserFileTool | `KAIROS` |
+| OverflowTestTool | `OVERFLOW_TEST_TOOL` |
+| TerminalCaptureTool | `TERMINAL_PANEL` |
+| WebBrowserTool | `WEB_BROWSER_TOOL` |
+| SnipTool | `HISTORY_SNIP` |
+| WorkflowTool | `WORKFLOW_SCRIPTS` |
+| PushNotificationTool | `KAIROS` |
+| SubscribePRTool | `KAIROS_GITHUB_WEBHOOKS` |
+| ListPeersTool | `UDS_INBOX` |
+| CtxInspectTool | `CONTEXT_COLLAPSE` |
+
+### 工具 — Stub / 不可用
+
+| 工具 | 说明 |
+|------|------|
+| TungstenTool | ANT-ONLY stub |
+| REPLTool | ANT-ONLY,`isEnabled: () => false` |
+| SuggestBackgroundPRTool | ANT-ONLY,`isEnabled: () => false` |
+| VerifyPlanExecutionTool | 需 `CLAUDE_CODE_VERIFY_PLAN=true` 环境变量,且为 stub |
+| ReviewArtifactTool | stub,未注册到 tools.ts |
+| DiscoverSkillsTool | stub,未注册到 tools.ts |
+
+### 斜杠命令 — 可用
+
+| 命令 | 状态 | 说明 |
+|------|------|------|
+| `/add-dir` | ✅ | 添加目录 |
+| `/advisor` | ✅ | Advisor 配置 |
+| `/agents` | ✅ | 代理列表/管理 |
+| `/branch` | ✅ | 分支管理 |
+| `/btw` | ✅ | 快速备注 |
+| `/chrome` | ✅ | Chrome 集成 |
+| `/clear` | ✅ | 清屏 |
+| `/color` | ✅ | Agent 颜色 |
+| `/compact` | ✅ | 压缩对话 |
+| `/config` (`/settings`) | ✅ | 配置管理 |
+| `/context` | ✅ | 上下文信息 |
+| `/copy` | ✅ | 复制最后消息 |
+| `/cost` | ✅ | 会话费用 |
+| `/desktop` | ✅ | Claude Desktop 集成 |
+| `/diff` | ✅ | 显示 diff |
+| `/doctor` | ✅ | 健康检查 |
+| `/effort` | ✅ | 设置 effort 等级 |
+| `/exit` | ✅ | 退出 |
+| `/export` | ✅ | 导出对话 |
+| `/extra-usage` | ✅ | 额外用量信息 |
+| `/fast` | ✅ | 切换 fast 模式 |
+| `/feedback` | ✅ | 反馈 |
+| `/files` | ✅ | 已跟踪文件 |
+| `/heapdump` | ✅ | Heap dump(调试) |
+| `/help` | ✅ | 帮助 |
+| `/hooks` | ✅ | Hook 管理 |
+| `/ide` | ✅ | IDE 连接 |
+| `/init` | ✅ | 初始化项目 |
+| `/install-github-app` | ✅ | 安装 GitHub App |
+| `/install-slack-app` | ✅ | 安装 Slack App |
+| `/keybindings` | ✅ | 快捷键管理 |
+| `/login` / `/logout` | ✅ | 登录 / 登出 |
+| `/mcp` | ✅ | MCP 服务管理 |
+| `/memory` | ✅ | Memory / CLAUDE.md 管理 |
+| `/mobile` | ✅ | 移动端 QR 码 |
+| `/model` | ✅ | 模型选择 |
+| `/output-style` | ✅ | 输出风格 |
+| `/passes` | ✅ | 推荐码 |
+| `/permissions` | ✅ | 权限管理 |
+| `/plan` | ✅ | 计划模式 |
+| `/plugin` | ✅ | 插件管理 |
+| `/pr-comments` | ✅ | PR 评论 |
+| `/privacy-settings` | ✅ | 隐私设置 |
+| `/rate-limit-options` | ✅ | 限速选项 |
+| `/release-notes` | ✅ | 更新日志 |
+| `/reload-plugins` | ✅ | 重载插件 |
+| `/remote-env` | ✅ | 远程环境配置 |
+| `/rename` | ✅ | 重命名会话 |
+| `/resume` | ✅ | 恢复会话 |
+| `/review` | ✅ | 代码审查(本地) |
+| `/ultrareview` | ✅ | 云端审查 |
+| `/rewind` | ✅ | 回退对话 |
+| `/sandbox-toggle` | ✅ | 切换沙箱 |
+| `/security-review` | ✅ | 安全审查 |
+| `/session` | ✅ | 会话信息 |
+| `/skills` | ✅ | Skill 管理 |
+| `/stats` | ✅ | 会话统计 |
+| `/status` | ✅ | 状态信息 |
+| `/statusline` | ✅ | 状态栏 UI |
+| `/stickers` | ✅ | 贴纸 |
+| `/tasks` | ✅ | 任务管理 |
+| `/theme` | ✅ | 终端主题 |
+| `/think-back` | ✅ | 年度回顾 |
+| `/upgrade` | ✅ | 升级 CLI |
+| `/usage` | ✅ | 用量信息 |
+| `/insights` | ✅ | 使用分析报告 |
+| `/vim` | ✅ | Vim 模式 |
+
+### 斜杠命令 — Feature Flag 关闭
+
+| 命令 | Feature Flag |
+|------|-------------|
+| `/voice` | `VOICE_MODE` |
+| `/proactive` | `PROACTIVE` / `KAIROS` |
+| `/brief` | `KAIROS` / `KAIROS_BRIEF` |
+| `/assistant` | `KAIROS` |
+| `/bridge` | `BRIDGE_MODE` |
+| `/remote-control-server` | `DAEMON` + `BRIDGE_MODE` |
+| `/force-snip` | `HISTORY_SNIP` |
+| `/workflows` | `WORKFLOW_SCRIPTS` |
+| `/web-setup` | `CCR_REMOTE_SETUP` |
+| `/subscribe-pr` | `KAIROS_GITHUB_WEBHOOKS` |
+| `/ultraplan` | `ULTRAPLAN` |
+| `/torch` | `TORCH` |
+| `/peers` | `UDS_INBOX` |
+| `/fork` | `FORK_SUBAGENT` |
+| `/buddy` | `BUDDY` |
+
+### 斜杠命令 — ANT-ONLY(不可用)
+
+`/tag` `/backfill-sessions` `/break-cache` `/bughunter` `/commit` `/commit-push-pr` `/ctx_viz` `/good-claude` `/issue` `/init-verifiers` `/mock-limits` `/bridge-kick` `/version` `/reset-limits` `/onboarding` `/share` `/summary` `/teleport` `/ant-trace` `/perf-issue` `/env` `/oauth-refresh` `/debug-tool-call` `/agents-platform` `/autofix-pr`
+
+### CLI 子命令
+
+| 子命令 | 状态 | 说明 |
+|--------|------|------|
+| `claude`(默认) | ✅ | 主 REPL / 交互 / print 模式 |
+| `claude mcp serve/add/remove/list/get/...` | ✅ | MCP 服务管理(7 个子命令) |
+| `claude auth login/status/logout` | ✅ | 认证管理 |
+| `claude plugin validate/list/install/...` | ✅ | 插件管理(7 个子命令) |
+| `claude setup-token` | ✅ | 长效 Token 配置 |
+| `claude agents` | ✅ | 代理列表 |
+| `claude doctor` | ✅ | 健康检查 |
+| `claude update` / `upgrade` | ✅ | 自动更新 |
+| `claude install [target]` | ✅ | Native 安装 |
+| `claude server` | ❌ | `DIRECT_CONNECT` flag |
+| `claude ssh <host>` | ❌ | `SSH_REMOTE` flag |
+| `claude open <cc-url>` | ❌ | `DIRECT_CONNECT` flag |
+| `claude auto-mode` | ❌ | `TRANSCRIPT_CLASSIFIER` flag |
+| `claude remote-control` | ❌ | `BRIDGE_MODE` + `DAEMON` flag |
+| `claude assistant` | ❌ | `KAIROS` flag |
+| `claude up/rollback/log/error/export/task/completion` | ❌ | ANT-ONLY |
+
+### 服务层
+
+| 服务 | 状态 | 说明 |
+|------|------|------|
+| API 客户端 (`services/api/`) | ✅ | 3400+ 行,4 个 provider |
+| MCP (`services/mcp/`) | ✅ | 24 个文件,12000+ 行 |
+| OAuth (`services/oauth/`) | ✅ | 完整 OAuth 流程 |
+| 插件 (`services/plugins/`) | ✅ | 基础设施完整,无内置插件 |
+| LSP (`services/lsp/`) | ⚠️ | 实现存在,默认关闭 |
+| 压缩 (`services/compact/`) | ✅ | auto / micro / API 压缩 |
+| Hook 系统 (`services/tools/toolHooks.ts`) | ✅ | pre/post tool use hooks |
+| 会话记忆 (`services/SessionMemory/`) | ✅ | 会话记忆管理 |
+| 记忆提取 (`services/extractMemories/`) | ✅ | 自动记忆提取 |
+| Skill 搜索 (`services/skillSearch/`) | ✅ | 本地/远程 skill 搜索 |
+| 策略限制 (`services/policyLimits/`) | ✅ | 策略限制执行 |
+| 分析 / GrowthBook / Sentry | ⚠️ | 框架存在,实际 sink 为空 |
+| Voice (`services/voice.ts`) | ❌ | `VOICE_MODE` flag 关闭 |
+
+### 内部包 (`packages/`)
+
+| 包 | 状态 | 说明 |
+|------|------|------|
+| `color-diff-napi` | ✅ | 997 行完整 TypeScript 实现(语法高亮 diff) |
+| `audio-capture-napi` | ❌ | stub,`isNativeAudioAvailable()` 返回 false |
+| `image-processor-napi` | ❌ | stub,`getNativeModule()` 返回 null |
+| `modifiers-napi` | ❌ | stub,`isModifierPressed()` 返回 false |
+| `url-handler-napi` | ❌ | stub,`waitForUrlEvent()` 返回 null |
+| `@ant/claude-for-chrome-mcp` | ❌ | stub,`createServer()` 返回 null |
+| `@ant/computer-use-mcp` | ❌ | stub,`buildTools()` 返回 [] |
+| `@ant/computer-use-input` | ❌ | stub,仅类型声明 |
+| `@ant/computer-use-swift` | ❌ | stub,仅类型声明 |
+
+### Feature Flags(30 个,全部返回 `false`)
+
+`ABLATION_BASELINE` `AGENT_MEMORY_SNAPSHOT` `BG_SESSIONS` `BRIDGE_MODE` `BUDDY` `CCR_MIRROR` `CCR_REMOTE_SETUP` `CHICAGO_MCP` `COORDINATOR_MODE` `DAEMON` `DIRECT_CONNECT` `EXPERIMENTAL_SKILL_SEARCH` `FORK_SUBAGENT` `HARD_FAIL` `HISTORY_SNIP` `KAIROS` `KAIROS_BRIEF` `KAIROS_CHANNELS` `KAIROS_GITHUB_WEBHOOKS` `LODESTONE` `MCP_SKILLS` `PROACTIVE` `SSH_REMOTE` `TORCH` `TRANSCRIPT_CLASSIFIER` `UDS_INBOX` `ULTRAPLAN` `UPLOAD_USER_SETTINGS` `VOICE_MODE` `WEB_BROWSER_TOOL` `WORKFLOW_SCRIPTS`
 
 ## 快速开始
 

+ 6 - 3
package.json

@@ -1,15 +1,18 @@
 {
-    "name": "claude-code",
+    "name": "claude-js",
     "version": "1.0.0",
-    "private": true,
     "type": "module",
     "workspaces": [
         "packages/*",
         "packages/@ant/*"
     ],
+    "files": [
+        "dist"
+    ],
     "scripts": {
         "build": "bun build src/entrypoints/cli.tsx --outdir dist --target bun",
-        "dev": "bun run src/entrypoints/cli.tsx"
+        "dev": "bun run src/entrypoints/cli.tsx",
+        "prepublishOnly": "bun run build"
     },
     "dependencies": {
         "@alcalzone/ansi-tokenize": "^0.3.0",

+ 1 - 1
packages/color-diff-napi/src/index.ts

@@ -30,7 +30,7 @@ import { basename, extname } from 'path'
 // pushed later tests in the same shard into GC-pause territory and a
 // beforeEach/afterEach hook timeout (officialRegistry.test.ts, PR #24150).
 // Same lazy pattern the NAPI wrapper used for dlopen.
-type HLJSApi = typeof hljsNamespace
+type HLJSApi = typeof hljsNamespace.default
 let cachedHljs: HLJSApi | null = null
 function hljs(): HLJSApi {
   if (cachedHljs) return cachedHljs

+ 1 - 1
src/bootstrap/state.ts

@@ -1755,4 +1755,4 @@ export function getPromptId(): string | null {
 export function setPromptId(id: string | null): void {
   STATE.promptId = id
 }
-export type isReplBridgeActive = any;
+export function isReplBridgeActive(): boolean { return false; }

+ 2 - 2
src/cli/handlers/auth.ts

@@ -159,7 +159,7 @@ export async function authLogin({
 
       const orgResult = await validateForceLoginOrg()
       if (!orgResult.valid) {
-        process.stderr.write(orgResult.message + '\n')
+        process.stderr.write((orgResult as { valid: false; message: string }).message + '\n')
         process.exit(1)
       }
 
@@ -209,7 +209,7 @@ export async function authLogin({
 
     const orgResult = await validateForceLoginOrg()
     if (!orgResult.valid) {
-      process.stderr.write(orgResult.message + '\n')
+      process.stderr.write((orgResult as { valid: false; message: string }).message + '\n')
       process.exit(1)
     }
 

+ 2 - 2
src/cli/handlers/mcp.tsx

@@ -178,9 +178,9 @@ export async function mcpListHandler(): Promise<void> {
         // biome-ignore lint/suspicious/noConsole:: intentional console output
         console.log(`${name}: ${server.url} - ${status}`);
       } else if (!server.type || server.type === 'stdio') {
-        const args = Array.isArray(server.args) ? server.args : [];
+        const args = Array.isArray((server as any).args) ? (server as any).args : [];
         // biome-ignore lint/suspicious/noConsole:: intentional console output
-        console.log(`${name}: ${server.command} ${args.join(' ')} - ${status}`);
+        console.log(`${name}: ${(server as any).command} ${args.join(' ')} - ${status}`);
       }
     }
   }

+ 4 - 4
src/cli/print.ts

@@ -1243,7 +1243,7 @@ function runHeadlessStreaming(
           uuid: crumb.uuid,
           timestamp: crumb.timestamp,
           isReplay: true,
-        } satisfies SDKUserMessageReplay)
+        } as SDKUserMessageReplay)
       }
     }
   }
@@ -1980,7 +1980,7 @@ function runHeadlessStreaming(
                   parent_tool_use_id: null,
                   uuid: c.uuid as string,
                   isReplay: true,
-                } satisfies SDKUserMessageReplay)
+                } as SDKUserMessageReplay)
               }
             }
           }
@@ -2265,8 +2265,8 @@ function runHeadlessStreaming(
                 output.enqueue({
                   type: 'system' as const,
                   subtype: 'files_persisted' as const,
-                  files: result.persistedFiles,
-                  failed: result.failedFiles,
+                  files: (result as any).persistedFiles,
+                  failed: (result as any).failedFiles,
                   processed_at: new Date().toISOString(),
                   uuid: randomUUID(),
                   session_id: getSessionId(),

+ 3 - 3
src/cli/transports/ccrClient.ts

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

+ 2 - 2
src/commands/branch/branch.ts

@@ -38,7 +38,7 @@ type TranscriptEntry = TranscriptMessage & {
 export function deriveFirstPrompt(
   firstUserMessage: Extract<SerializedMessage, { type: 'user' }> | undefined,
 ): string {
-  const content = firstUserMessage?.message?.content
+  const content = (firstUserMessage as any)?.message?.content
   if (!content) return 'Branched conversation'
   const raw =
     typeof content === 'string'
@@ -240,7 +240,7 @@ export async function call(
     // Build LogOption for resume
     const now = new Date()
     const firstPrompt = deriveFirstPrompt(
-      serializedMessages.find(m => m.type === 'user'),
+      serializedMessages.find(m => m.type === 'user') as Extract<SerializedMessage, { type: 'user' }> | undefined,
     )
 
     // Save custom title - use provided title or firstPrompt as default

+ 3 - 3
src/commands/plugin/BrowseMarketplace.tsx

@@ -140,7 +140,7 @@ export function BrowseMarketplace({
         } of marketplaces_0) {
           if (marketplace) {
             // Count how many plugins from this marketplace are installed
-            const installedFromThisMarketplace = count(marketplace.plugins, plugin => isPluginInstalled(createPluginId(plugin.name, name)));
+            const installedFromThisMarketplace = count(marketplace.plugins, (plugin: any) => isPluginInstalled(createPluginId(plugin.name, name)));
             marketplaceInfos.push({
               name,
               totalPlugins: marketplace.plugins.length,
@@ -334,7 +334,7 @@ export function BrowseMarketplace({
         failureCount++;
         newFailedPlugins.push({
           name: plugin_1.entry.name,
-          reason: result.error
+          reason: (result as { success: false; error: string }).error
         });
       }
     }
@@ -397,7 +397,7 @@ export function BrowseMarketplace({
       });
     } else {
       setIsInstalling(false);
-      setInstallError(result_0.error);
+      setInstallError((result_0 as { success: false; error: string }).error);
     }
   };
 

+ 2 - 2
src/commands/plugin/DiscoverPlugins.tsx

@@ -248,7 +248,7 @@ export function DiscoverPlugins({
         failureCount++;
         newFailedPlugins.push({
           name: plugin_0.entry.name,
-          reason: result.error
+          reason: (result as { success: false; error: string }).error
         });
       }
     }
@@ -306,7 +306,7 @@ export function DiscoverPlugins({
       });
     } else {
       setIsInstalling(false);
-      setInstallError(result_0.error);
+      setInstallError((result_0 as { success: false; error: string }).error);
     }
   };
 

+ 3 - 2
src/commands/remote-setup/remote-setup.tsx

@@ -130,11 +130,12 @@ function Web({
     });
     const result = await importGithubToken(token);
     if (!result.ok) {
+      const importErr = (result as { ok: false; error: ImportTokenError }).error;
       logEvent('tengu_remote_setup_result', {
         result: 'import_failed' as SafeString,
-        error_kind: result.error.kind as SafeString
+        error_kind: importErr.kind as SafeString
       });
-      onDone(errorMessage(result.error, getCodeWebUrl()));
+      onDone(errorMessage(importErr, getCodeWebUrl()));
       return;
     }
 

+ 4 - 3
src/commands/reset-limits/index.ts

@@ -1,3 +1,4 @@
-// Auto-generated type stub — replace with real implementation
-export type resetLimits = any;
-export type resetLimitsNonInteractive = any;
+// Auto-generated stub — replace with real implementation
+const stub = { isEnabled: () => false, isHidden: true, name: 'stub' };
+export const resetLimits = stub;
+export const resetLimitsNonInteractive = stub;

+ 3 - 2
src/commands/resume/resume.tsx

@@ -154,11 +154,12 @@ function ResumeCommand({
       }
 
       // Different project - show command instead of resuming
-      const raw = await setClipboard(crossProjectCheck.command);
+      const crossCmd = (crossProjectCheck as { isCrossProject: true; isSameRepoWorktree: false; command: string }).command;
+      const raw = await setClipboard(crossCmd);
       if (raw) process.stdout.write(raw);
 
       // Format the output message
-      const message = ['', 'This conversation is from a different directory.', '', 'To resume, run:', `  ${crossProjectCheck.command}`, '', '(Command copied to clipboard)', ''].join('\n');
+      const message = ['', 'This conversation is from a different directory.', '', 'To resume, run:', `  ${crossCmd}`, '', '(Command copied to clipboard)', ''].join('\n');
       onDone(message, {
         display: 'user'
       });

+ 1 - 1
src/commands/review/reviewRemote.ts

@@ -136,7 +136,7 @@ export async function launchRemoteReview(
   // consume at session creation routes billing: first N zero-rate, then
   // anthropic:cccr org-service-key (overage-only).
   if (!eligibility.eligible) {
-    const blockers = eligibility.errors.filter(
+    const blockers = (eligibility as { eligible: false; errors: Array<{ type: string }> }).errors.filter(
       e => e.type !== 'no_remote_environment',
     )
     if (blockers.length > 0) {

+ 3 - 2
src/commands/ultraplan.tsx

@@ -314,11 +314,12 @@ async function launchDetached(opts: {
     const model = getUltraplanModel();
     const eligibility = await checkRemoteAgentEligibility();
     if (!eligibility.eligible) {
+      const eligErrors = (eligibility as { eligible: false; errors: Array<{ type: string }> }).errors;
       logEvent('tengu_ultraplan_create_failed', {
         reason: 'precondition' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
-        precondition_errors: eligibility.errors.map(e => e.type).join(',') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
+        precondition_errors: eligErrors.map(e => e.type).join(',') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
       });
-      const reasons = eligibility.errors.map(formatPreconditionError).join('\n');
+      const reasons = eligErrors.map(formatPreconditionError).join('\n');
       enqueuePendingNotification({
         value: `ultraplan: cannot launch remote session —\n${reasons}`,
         mode: 'task-notification'

+ 1 - 1
src/components/ConsoleOAuthFlow.tsx

@@ -235,7 +235,7 @@ export function ConsoleOAuthFlow({
         await installOAuthTokens(result);
         const orgResult = await validateForceLoginOrg();
         if (!orgResult.valid) {
-          throw new Error(orgResult.message);
+          throw new Error((orgResult as { valid: false; message: string }).message);
         }
         setOAuthStatus({
           state: 'success'

+ 5 - 2
src/components/Feedback.tsx

@@ -59,8 +59,11 @@ type FeedbackData = {
   description: string;
   platform: string;
   gitRepo: boolean;
+  terminal: string;
   version: string | null;
   transcript: Message[];
+  errors: unknown;
+  lastApiRequest: unknown;
   subagentTranscripts?: {
     [agentId: string]: Message[];
   };
@@ -203,8 +206,8 @@ export function Feedback({
       ...diskTranscripts,
       ...teammateTranscripts
     };
-    const reportData = {
-      latestAssistantMessageId: lastAssistantMessageId,
+    const reportData: FeedbackData = {
+      latestAssistantMessageId: lastAssistantMessageId as string | null,
       message_count: messages.length,
       datetime: new Date().toISOString(),
       description,

+ 1 - 1
src/components/FileEditToolDiff.tsx

@@ -50,7 +50,7 @@ export function FileEditToolDiff(props) {
   }
   return t2;
 }
-function DiffBody(t0) {
+function DiffBody(t0: { promise: Promise<DiffData>; file_path: string }) {
   const $ = _c(6);
   const {
     promise,

+ 1 - 1
src/components/HighlightedCode/Fallback.tsx

@@ -134,7 +134,7 @@ function Highlighted(t0) {
   } else {
     t1 = $[0];
   }
-  const hl = use(t1);
+  const hl = use(t1) as NonNullable<Awaited<ReturnType<typeof getCliHighlightPromise>>> | null;
   let t2;
   if ($[1] !== codeWithSpaces || $[2] !== hl || $[3] !== language) {
     bb0: {

+ 2 - 2
src/components/Markdown.tsx

@@ -129,7 +129,7 @@ function MarkdownBody(t0) {
   } = t0;
   const [theme] = useTheme();
   configureMarked();
-  let elements;
+  let elements: React.ReactNode[];
   if ($[0] !== children || $[1] !== dimColor || $[2] !== highlight || $[3] !== theme) {
     const tokens = cachedLexer(stripPromptXMLTags(children));
     elements = [];
@@ -156,7 +156,7 @@ function MarkdownBody(t0) {
     $[3] = theme;
     $[4] = elements;
   } else {
-    elements = $[4];
+    elements = $[4] as React.ReactNode[];
   }
   const elements_0 = elements;
   let t1;

+ 1 - 1
src/components/Message.tsx

@@ -606,7 +606,7 @@ export function areMessagePropsEqual(prev: Props, next: Props): boolean {
   // Only re-render on lastThinkingBlockId change if this message actually
   // has thinking content — otherwise every message in scrollback re-renders
   // whenever streaming thinking starts/stops (CC-941).
-  if (prev.lastThinkingBlockId !== next.lastThinkingBlockId && hasThinkingContent(next.message)) {
+  if (prev.lastThinkingBlockId !== next.lastThinkingBlockId && hasThinkingContent(next.message as any)) {
     return false;
   }
   // Verbose toggle changes thinking block visibility/expansion

+ 1 - 1
src/components/MessageSelector.tsx

@@ -334,7 +334,7 @@ export function MessageSelector({
             <Box flexDirection="column" paddingLeft={1} borderStyle="single" borderRight={false} borderTop={false} borderBottom={false} borderLeft={true} borderLeftDimColor>
               <UserMessageOption userMessage={messageToRestore} color="text" isCurrent={false} />
               <Text dimColor>
-                ({formatRelativeTimeAgo(new Date(messageToRestore.timestamp))})
+                ({formatRelativeTimeAgo(new Date(messageToRestore.timestamp as number))})
               </Text>
             </Box>
             <RestoreOptionDescription selectedRestoreOption={selectedRestoreOption} canRestoreCode={!!canRestoreCode_0} diffStatsForRestore={diffStatsForRestore} />

+ 2 - 1
src/components/Settings/Settings.tsx

@@ -95,7 +95,8 @@ export function Settings(t0) {
   }
   let t7;
   if ($[13] !== contentHeight) {
-    t7 = false ? [<Tab key="gates" title="Gates"><Gates onOwnsEscChange={setGatesOwnsEsc} contentHeight={contentHeight} /></Tab>] : [];
+    const GatesComponent = Gates as any;
+    t7 = false ? [<Tab key="gates" title="Gates"><GatesComponent onOwnsEscChange={setGatesOwnsEsc} contentHeight={contentHeight} /></Tab>] : [];
     $[13] = contentHeight;
     $[14] = t7;
   } else {

+ 1 - 1
src/components/Settings/Status.tsx

@@ -206,7 +206,7 @@ function Diagnostics(t0) {
   const {
     promise
   } = t0;
-  const diagnostics = use(promise);
+  const diagnostics = use(promise) as any[];
   if (diagnostics.length === 0) {
     return null;
   }

+ 4 - 4
src/components/Spinner/GlimmerMessage.tsx

@@ -272,10 +272,10 @@ export function GlimmerMessage(t0) {
     $[57] = shim;
     $[58] = colPos;
   } else {
-    before = $[55];
-    after = $[56];
-    shim = $[57];
-    colPos = $[58];
+    before = $[55] as string;
+    after = $[56] as string;
+    shim = $[57] as string;
+    colPos = $[58] as number;
   }
   let t3;
   if ($[59] !== before || $[60] !== messageColor) {

+ 1 - 1
src/components/StatusNotices.tsx

@@ -31,7 +31,7 @@ export function StatusNotices(t0) {
   const context = {
     config: t1,
     agentDefinitions,
-    memoryFiles: use(t2)
+    memoryFiles: use(t2) as any
   };
   const activeNotices = getActiveNotices(context);
   if (activeNotices.length === 0) {

+ 2 - 2
src/components/TextInput.tsx

@@ -45,9 +45,9 @@ export default function TextInput(props: Props): React.ReactNode {
   // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
   useVoiceState(s => s.voiceState) : 'idle' as const;
   const isVoiceRecording = voiceState === 'recording';
-  const audioLevels = feature('VOICE_MODE') ?
+  const audioLevels = (feature('VOICE_MODE') ?
   // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
-  useVoiceState(s_0 => s_0.voiceAudioLevels) : [];
+  useVoiceState(s_0 => s_0.voiceAudioLevels) : []) as number[];
   const smoothedRef = useRef<number[]>(new Array(CURSOR_WAVEFORM_WIDTH).fill(0));
   const needsAnimation = isVoiceRecording && !reducedMotion;
   const [animRef, animTime] = feature('VOICE_MODE') ?

+ 1 - 1
src/components/ThemePicker.tsx

@@ -72,7 +72,7 @@ export function ThemePicker(t0) {
   } = usePreviewTheme();
   const syntaxHighlightingDisabled = useAppState(_temp) ?? false;
   const setAppState = useSetAppState();
-  useRegisterKeybindingContext("ThemePicker");
+  useRegisterKeybindingContext("ThemePicker", undefined);
   const syntaxToggleShortcut = useShortcutDisplay("theme:toggleSyntaxHighlighting", "ThemePicker", "ctrl+t");
   let t8;
   if ($[3] !== setAppState || $[4] !== syntaxHighlightingDisabled) {

+ 3 - 3
src/components/VirtualMessageList.tsx

@@ -16,7 +16,7 @@ const HEADROOM = 3;
 import { logForDebugging } from '../utils/debug.js';
 import { sleep } from '../utils/sleep.js';
 import { renderableSearchText } from '../utils/transcriptSearch.js';
-import { isNavigableMessage, type MessageActionsNav, type MessageActionsState, type NavigableMessage, stripSystemReminders, toolCallOf } from './messageActions.js';
+import { isNavigableMessage, type MessageActionsNav, type MessageActionsState, type NavigableMessage, type NavigableType, stripSystemReminders, toolCallOf } from './messageActions.js';
 
 // Fallback extractor: lower + cache here for callers without the
 // Messages.tsx tool-lookup path (tests, static contexts). Messages.tsx
@@ -151,7 +151,7 @@ function computeStickyPromptText(msg: RenderableMessage): string | null {
     raw = block.text;
   } else if (msg.type === 'attachment' && msg.attachment.type === 'queued_command' && msg.attachment.commandMode !== 'task-notification' && !msg.attachment.isMeta) {
     const p = msg.attachment.prompt;
-    raw = typeof p === 'string' ? p : p.flatMap(b => b.type === 'text' ? [b.text] : []).join('\n');
+    raw = typeof p === 'string' ? p : (p as any[]).flatMap(b => b.type === 'text' ? [b.text] : []).join('\n');
   }
   if (raw === null) return null;
   const t = stripSystemReminders(raw);
@@ -345,7 +345,7 @@ export function VirtualMessageList({
   useImperativeHandle(cursorNavRef, (): MessageActionsNav => {
     const select = (m: NavigableMessage) => setCursor?.({
       uuid: m.uuid,
-      msgType: m.type,
+      msgType: m.type as NavigableType,
       expanded: false,
       toolName: toolCallOf(m)?.name
     });

+ 2 - 2
src/components/agents/ToolSelector.tsx

@@ -251,7 +251,7 @@ export function ToolSelector(t0) {
   let t9;
   if ($[22] !== selectedSet) {
     t9 = bucketTools => {
-      const selected = count(bucketTools, t_5 => selectedSet.has(t_5.name));
+      const selected = count(bucketTools, (t_5: any) => selectedSet.has(t_5.name));
       const needsSelection = selected < bucketTools.length;
       return () => {
         const toolNames_1 = bucketTools.map(_temp4);
@@ -321,7 +321,7 @@ export function ToolSelector(t0) {
       if (bucketTools_0.length === 0) {
         return;
       }
-      const selected_0 = count(bucketTools_0, t_8 => selectedSet.has(t_8.name));
+      const selected_0 = count(bucketTools_0, (t_8: any) => selectedSet.has(t_8.name));
       const isFullySelected = selected_0 === bucketTools_0.length;
       navigableItems.push({
         id,

+ 1 - 1
src/components/memory/MemoryFileSelector.tsx

@@ -47,7 +47,7 @@ export function MemoryFileSelector(t0) {
     onSelect,
     onCancel
   } = t0;
-  const existingMemoryFiles = use(getMemoryFiles());
+  const existingMemoryFiles = use(getMemoryFiles()) as MemoryFileInfo[];
   const userMemoryPath = join(getClaudeConfigHomeDir(), "CLAUDE.md");
   const projectMemoryPath = join(getOriginalCwd(), "CLAUDE.md");
   const hasUserMemory = existingMemoryFiles.some(f => f.path === userMemoryPath);

+ 1 - 1
src/components/messages/AssistantToolUseMessage.tsx

@@ -337,7 +337,7 @@ function renderToolUseProgressMessage(tool: Tool, tools: Tools, lookups: ReturnT
   columns: number;
   rows: number;
 }): React.ReactNode {
-  const toolProgressMessages = progressMessagesForMessage.filter((msg): msg is ProgressMessage<ToolProgressData> => msg.data.type !== 'hook_progress');
+  const toolProgressMessages = progressMessagesForMessage.filter((msg): msg is ProgressMessage<ToolProgressData> => (msg.data as { type?: string }).type !== 'hook_progress');
   try {
     const toolMessages = tool.renderToolUseProgressMessage?.(toolProgressMessages, {
       tools,

+ 1 - 3
src/components/messages/nullRenderingAttachments.ts

@@ -40,8 +40,6 @@ const NULL_RENDERING_TYPES = [
   'auto_mode',
   'auto_mode_exit',
   'output_token_usage',
-  'pen_mode_enter',
-  'pen_mode_exit',
   'verify_plan_reminder',
   'current_session_memory',
   'compaction_reminder',
@@ -65,6 +63,6 @@ export function isNullRenderingAttachment(
 ): boolean {
   return (
     msg.type === 'attachment' &&
-    NULL_RENDERING_ATTACHMENT_TYPES.has(msg.attachment.type)
+    NULL_RENDERING_ATTACHMENT_TYPES.has(msg.attachment.type as Attachment['type'])
   )
 }

+ 2 - 2
src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.tsx

@@ -140,7 +140,7 @@ function AskUserQuestionPermissionRequestBody(t0) {
     $[10] = theme;
     $[11] = maxHeight;
   } else {
-    maxHeight = $[11];
+    maxHeight = $[11] as number;
   }
   const t3 = Math.min(Math.max(maxHeight, MIN_CONTENT_HEIGHT), maxAllowedHeight);
   const t4 = Math.max(maxWidth, MIN_CONTENT_WIDTH);
@@ -177,7 +177,7 @@ function AskUserQuestionPermissionRequestBody(t0) {
       const pasteId = nextPasteIdRef.current;
       const newContent = {
         id: pasteId,
-        type: "image",
+        type: "image" as const,
         content: base64Image,
         mediaType: mediaType || "image/png",
         filename: filename || "Pasted image",

+ 1 - 1
src/components/permissions/ExitPlanModePermissionRequest/ExitPlanModePermissionRequest.tsx

@@ -151,7 +151,7 @@ export function ExitPlanModePermissionRequest({
   const options = useMemo(() => buildPlanApprovalOptions({
     showClearContext,
     showUltraplan,
-    usedPercent: showClearContext ? getContextUsedPercent(usage, mode) : null,
+    usedPercent: showClearContext ? getContextUsedPercent(usage as any, mode) : null,
     isAutoModeAvailable,
     isBypassPermissionsModeAvailable,
     onFeedbackChange: setPlanFeedback

+ 1 - 1
src/components/permissions/PermissionDecisionDebugInfo.tsx

@@ -62,7 +62,7 @@ function PermissionDecisionInfoItem(t0) {
             return <Box flexDirection="column">{Array.from(decisionReason.reasons.entries()).map(t2 => {
                 const [subcommand, result] = t2 as [string, { behavior: string; decisionReason?: { type: string }; suggestions?: unknown }];
                 const icon = result.behavior === "allow" ? color("success", theme)(figures.tick) : color("error", theme)(figures.cross);
-                return <Box flexDirection="column" key={subcommand}><Text>{icon} {subcommand}</Text>{result.decisionReason !== undefined && result.decisionReason.type !== "subcommandResults" && <Text><Text dimColor={true}>{"  "}⎿{"  "}</Text><Ansi>{decisionReasonDisplayString(result.decisionReason)}</Ansi></Text>}{result.behavior === "ask" && <SuggestedRules suggestions={result.suggestions} />}</Box>;
+                return <Box flexDirection="column" key={subcommand}><Text>{icon} {subcommand}</Text>{result.decisionReason !== undefined && result.decisionReason.type !== "subcommandResults" && <Text><Text dimColor={true}>{"  "}⎿{"  "}</Text><Ansi>{decisionReasonDisplayString(result.decisionReason as any)}</Ansi></Text>}{result.behavior === "ask" && <SuggestedRules suggestions={result.suggestions} />}</Box>;
               })}</Box>;
           }
         default:

+ 1 - 1
src/components/permissions/SedEditPermissionRequest/SedEditPermissionRequest.tsx

@@ -102,7 +102,7 @@ function SedEditPermissionRequestInner(t0) {
   const {
     oldContent,
     fileExists
-  } = use(contentPromise);
+  } = use(contentPromise) as any;
   let t1;
   if ($[4] !== oldContent || $[5] !== sedInfo) {
     t1 = applySedSubstitution(oldContent, sedInfo);

+ 1 - 1
src/components/tasks/ShellDetailDialog.tsx

@@ -310,7 +310,7 @@ function ShellOutputContent(t0) {
   const {
     content,
     bytesTotal
-  } = use(outputPromise);
+  } = use(outputPromise) as any;
   if (!content) {
     let t1;
     if ($[0] === Symbol.for("react.memo_cache_sentinel")) {

+ 3 - 3
src/hooks/useCanUseTool.tsx

@@ -132,11 +132,11 @@ function useCanUseTool(setToolUseConfirmQueue, setToolPermissionContext) {
                   if (ctx.resolveIfAborted(resolve)) {
                     return;
                   }
-                  if (raceResult.type === "result" && raceResult.result.matches && raceResult.result.confidence === "high" && feature("BASH_CLASSIFIER")) {
+                  if ((raceResult as any).type === "result" && (raceResult as any).result.matches && (raceResult as any).result.confidence === "high" && feature("BASH_CLASSIFIER")) {
                     consumeSpeculativeClassifierCheck((input as {
                       command: string;
                     }).command);
-                    const matchedRule = raceResult.result.matchedDescription ?? undefined;
+                    const matchedRule = (raceResult as any).result.matchedDescription ?? undefined;
                     if (matchedRule) {
                       setClassifierApproval(toolUseID, matchedRule);
                     }
@@ -150,7 +150,7 @@ function useCanUseTool(setToolUseConfirmQueue, setToolPermissionContext) {
                       decisionReason: {
                         type: "classifier" as const,
                         classifier: "bash_allow" as const,
-                        reason: `Allowed by prompt rule: "${raceResult.result.matchedDescription}"`
+                        reason: `Allowed by prompt rule: "${(raceResult as any).result.matchedDescription}"`
                       }
                     }));
                     return;

+ 2 - 2
src/hooks/useReplBridge.tsx

@@ -370,7 +370,7 @@ export function useReplBridge(messages: Message[], setMessages: (action: React.S
 
           // Dispatch incoming control_response messages to registered handlers
           function handlePermissionResponse(msg_0: SDKControlResponse): void {
-            const requestId = msg_0.response?.request_id;
+            const requestId = (msg_0 as any).response?.request_id;
             if (!requestId) return;
             const handler = pendingPermissionHandlers.get(requestId);
             if (!handler) {
@@ -379,7 +379,7 @@ export function useReplBridge(messages: Message[], setMessages: (action: React.S
             }
             pendingPermissionHandlers.delete(requestId);
             // Extract the permission decision from the control_response payload
-            const inner = msg_0.response;
+            const inner = (msg_0 as any).response;
             if (inner.subtype === 'success' && inner.response && isBridgePermissionResponse(inner.response)) {
               handler(inner.response);
             }

+ 2 - 2
src/hooks/useVoiceIntegration.tsx

@@ -225,9 +225,9 @@ export function useVoiceIntegration({
   const voiceState = feature('VOICE_MODE') ?
   // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
   useVoiceState(s => s.voiceState) : 'idle' as const;
-  const voiceInterimTranscript = feature('VOICE_MODE') ?
+  const voiceInterimTranscript: string = feature('VOICE_MODE') ?
   // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
-  useVoiceState(s_0 => s_0.voiceInterimTranscript) : '';
+  useVoiceState(s_0 => s_0.voiceInterimTranscript) as string : '';
 
   // Set the voice anchor for focus mode (where recording starts via terminal
   // focus, not key hold). Key-hold sets the anchor in stripTrailing.

+ 1 - 1
src/ink/Ansi.tsx

@@ -29,7 +29,7 @@ type SpanProps = {
  *
  * Memoized to prevent re-renders when parent changes but children string is the same.
  */
-export const Ansi = React.memo(function Ansi(t0) {
+export const Ansi = React.memo(function Ansi(t0: { children: React.ReactNode; dimColor?: boolean }) {
   const $ = _c(12);
   const {
     children,

+ 2 - 2
src/services/api/client.ts

@@ -159,7 +159,7 @@ export async function getAnthropicClient({
         ? process.env.ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION
         : getAWSRegion()
 
-    const bedrockArgs: ConstructorParameters<typeof AnthropicBedrock>[0] = {
+    const bedrockArgs: any = {
       ...ARGS,
       awsRegion,
       ...(isEnvTruthy(process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH) && {
@@ -290,7 +290,7 @@ export async function getAnthropicClient({
     const vertexArgs: ConstructorParameters<typeof AnthropicVertex>[0] = {
       ...ARGS,
       region: getVertexRegionForModel(model),
-      googleAuth,
+      googleAuth: googleAuth as any,
       ...(isDebugToStdErr() && { logger: createStderrLogger() }),
     }
     // we have always been lying about the return type - this doesn't support batching or models

+ 2 - 2
src/services/api/errors.ts

@@ -108,7 +108,7 @@ export function getPromptTooLongTokenGap(
     return undefined
   }
   const { actualTokens, limitTokens } = parsePromptTooLongTokenCounts(
-    msg.errorDetails,
+    msg.errorDetails as string,
   )
   if (actualTokens === undefined || limitTokens === undefined) {
     return undefined
@@ -148,7 +148,7 @@ export function isMediaSizeErrorMessage(msg: AssistantMessage): boolean {
   return (
     msg.isApiErrorMessage === true &&
     msg.errorDetails !== undefined &&
-    isMediaSizeError(msg.errorDetails)
+    isMediaSizeError(msg.errorDetails as string)
   )
 }
 export const CREDIT_BALANCE_TOO_LOW_ERROR_MESSAGE = 'Credit balance is too low'

+ 2 - 1
src/services/mcp/config.ts

@@ -1279,7 +1279,7 @@ export async function getAllMcpConfigs(): Promise<{
   // Keys never collide (`slack` vs `claude.ai Slack`) so the merge below
   // won't catch this — need content-based dedup by URL signature.
   const { servers: dedupedClaudeAi } = dedupClaudeAiMcpServers(
-    claudeaiMcpServers,
+    claudeaiMcpServers as Record<string, ScopedMcpServerConfig>,
     claudeCodeServers,
   )
 
@@ -1351,6 +1351,7 @@ export function parseMcpConfig(params: {
     if (
       getPlatform() === 'windows' &&
       (!configToCheck.type || configToCheck.type === 'stdio') &&
+      ('command' in configToCheck) &&
       (configToCheck.command === 'npx' ||
         configToCheck.command.endsWith('\\npx') ||
         configToCheck.command.endsWith('/npx'))

+ 3 - 3
src/services/tools/toolHooks.ts

@@ -99,7 +99,7 @@ export async function* runPostToolUseHooks<Input extends AnyObject, Output>(
             result.message.attachment.type === 'hook_blocking_error'
           )
         ) {
-          yield { message: result.message }
+          yield { message: result.message as AttachmentMessage | ProgressMessage<HookProgress> }
         }
 
         if (result.blockingError) {
@@ -251,7 +251,7 @@ export async function* runPostToolUseFailureHooks<Input extends AnyObject>(
             result.message.attachment.type === 'hook_blocking_error'
           )
         ) {
-          yield { message: result.message }
+          yield { message: result.message as AttachmentMessage | ProgressMessage<HookProgress> }
         }
 
         if (result.blockingError) {
@@ -476,7 +476,7 @@ export async function* runPreToolUseHooks(
     )) {
       try {
         if (result.message) {
-          yield { type: 'message', message: { message: result.message } }
+          yield { type: 'message', message: { message: result.message as AttachmentMessage | ProgressMessage<HookProgress> } }
         }
         if (result.blockingError) {
           const denialMessage = getPreToolHookBlockingMessage(

+ 6 - 6
src/services/vcr.ts

@@ -1,4 +1,4 @@
-import type { BetaContentBlock } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
+import type { BetaContentBlock, BetaUsage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
 import { createHash, randomUUID, type UUID } from 'crypto'
 import { mkdir, readFile, writeFile } from 'fs/promises'
 import isPlainObject from 'lodash-es/isPlainObject.js'
@@ -166,8 +166,8 @@ function addCachedCostToTotalSessionCost(
   if (message.type === 'stream_event') {
     return
   }
-  const model = message.message.model
-  const usage = message.message.usage
+  const model = (message as AssistantMessage).message.model as string
+  const usage = (message as AssistantMessage).message.usage as BetaUsage
   const costUSD = calculateUSDCost(model, usage)
   addToTotalSessionCost(costUSD, usage, model)
 }
@@ -251,7 +251,7 @@ function mapAssistantMessage(
     timestamp: message.timestamp,
     message: {
       ...message.message,
-      content: message.message.content
+      content: (message.message.content as BetaContentBlock[])
         .map(_ => {
           switch (_.type) {
             case 'text':
@@ -269,7 +269,7 @@ function mapAssistantMessage(
               return _ // Handle other block types unchanged
           }
         })
-        .filter(Boolean) as BetaContentBlock[],
+        .filter(Boolean) as any,
     },
     type: 'assistant',
   }
@@ -282,7 +282,7 @@ function mapMessage(
   uuid?: UUID,
 ): AssistantMessage | SystemAPIErrorMessage | StreamEvent {
   if (message.type === 'assistant') {
-    return mapAssistantMessage(message, f, index, uuid)
+    return mapAssistantMessage(message as AssistantMessage, f, index, uuid)
   } else {
     return message
   }

+ 2 - 2
src/tasks/LocalShellTask/LocalShellTask.tsx

@@ -107,7 +107,7 @@ function enqueueShellNotification(taskId: string, description: string, status: '
   // If the task was already marked as notified (e.g., by TaskStopTool), skip
   // enqueueing to avoid sending redundant messages to the model.
   let shouldEnqueue = false;
-  updateTaskState(taskId, setAppState, task => {
+  updateTaskState<LocalShellTaskState>(taskId, setAppState, task => {
     if (task.notified) {
       return task;
     }
@@ -479,7 +479,7 @@ export function backgroundExistingForegroundTask(taskId: string, shellCommand: S
  * carries the full output, so the <task_notification> would be redundant.
  */
 export function markTaskNotified(taskId: string, setAppState: SetAppState): void {
-  updateTaskState(taskId, setAppState, t => t.notified ? t : {
+  updateTaskState<LocalShellTaskState>(taskId, setAppState, t => t.notified ? t : {
     ...t,
     notified: true
   });

+ 6 - 6
src/tools/AgentTool/runAgent.ts

@@ -760,17 +760,17 @@ export async function* runAgent({
       // so TTFT/OTPS update during subagent execution.
       if (
         message.type === 'stream_event' &&
-        message.event.type === 'message_start' &&
-        message.ttftMs != null
+        (message as any).event.type === 'message_start' &&
+        (message as any).ttftMs != null
       ) {
-        toolUseContext.pushApiMetricsEntry?.(message.ttftMs)
+        toolUseContext.pushApiMetricsEntry?.((message as any).ttftMs)
         continue
       }
 
       // Yield attachment messages (e.g., structured_output) without recording them
       if (message.type === 'attachment') {
         // Handle max turns reached signal from query.ts
-        if (message.attachment.type === 'max_turns_reached') {
+        if ((message as any).attachment.type === 'max_turns_reached') {
           logForDebugging(
             `[Agent
 : $
@@ -779,13 +779,13 @@ export async function* runAgent({
 }
 ] Reached max turns limit ($
 {
-  message.attachment.maxTurns
+  (message as any).attachment.maxTurns
 }
 )`,
           )
           break
         }
-        yield message
+        yield message as Message
         continue
       }
 

+ 1 - 1
src/tools/FileEditTool/UI.tsx

@@ -222,7 +222,7 @@ function EditRejectionBody(t0) {
     patch,
     firstLine,
     fileContent
-  } = use(promise);
+  } = use(promise) as any;
   let t1;
   if ($[0] !== fileContent || $[1] !== filePath || $[2] !== firstLine || $[3] !== patch || $[4] !== style || $[5] !== verbose) {
     t1 = <FileEditToolUseRejectedMessage file_path={filePath} operation="update" patch={patch} firstLine={firstLine} fileContent={fileContent} style={style} verbose={verbose} />;

+ 6 - 6
src/tools/FileReadTool/FileReadTool.ts

@@ -899,11 +899,11 @@ async function callInner(
         parsedRange ?? undefined,
       )
       if (!extractResult.success) {
-        throw new Error(extractResult.error.message)
+        throw new Error((extractResult as any).error.message)
       }
       logEvent('tengu_pdf_page_extraction', {
         success: true,
-        pageCount: extractResult.data.file.count,
+        pageCount: (extractResult as any).data.file.count,
         fileSize: extractResult.data.file.originalSize,
         hasPageRange: true,
       })
@@ -970,7 +970,7 @@ async function callInner(
       } else {
         logEvent('tengu_pdf_page_extraction', {
           success: false,
-          available: extractResult.error.reason !== 'unavailable',
+          available: (extractResult as any).error.reason !== 'unavailable',
           fileSize: stats.size,
         })
       }
@@ -986,7 +986,7 @@ async function callInner(
 
     const readResult = await readPDF(resolvedFilePath)
     if (!readResult.success) {
-      throw new Error(readResult.error.message)
+      throw new Error((readResult as any).error.message)
     }
     const pdfData = readResult.data
     logFileOperation({
@@ -1158,12 +1158,12 @@ export async function readImageWithTokenBudget(
         const sharpModule = await import('sharp')
         const sharp =
           (
-            sharpModule as {
+            sharpModule as unknown as {
               default?: typeof sharpModule
             } & typeof sharpModule
           ).default || sharpModule
 
-        const fallbackBuffer = await sharp(imageBuffer)
+        const fallbackBuffer = await (sharp as any)(imageBuffer)
           .resize(400, 400, {
             fit: 'inside',
             withoutEnlargement: true,

+ 2 - 2
src/tools/SendMessageTool/SendMessageTool.ts

@@ -826,7 +826,7 @@ export const SendMessageTool: Tool<InputSchema, SendMessageToolOutput> =
                 prompt: input.message,
                 toolUseContext: context,
                 canUseTool,
-                invokingRequestId: assistantMessage?.requestId,
+                invokingRequestId: assistantMessage?.requestId as string | undefined,
               })
               return {
                 data: {
@@ -853,7 +853,7 @@ export const SendMessageTool: Tool<InputSchema, SendMessageToolOutput> =
                 prompt: input.message,
                 toolUseContext: context,
                 canUseTool,
-                invokingRequestId: assistantMessage?.requestId,
+                invokingRequestId: assistantMessage?.requestId as string | undefined,
               })
               return {
                 data: {

+ 11 - 0
src/types/global.d.ts

@@ -67,6 +67,17 @@ declare const BUILD_TARGET: string
 declare const BUILD_ENV: string
 declare const INTERFACE_TYPE: string
 
+// ============================================================================
+// Ink custom JSX intrinsic elements — used by the internal Ink framework
+declare namespace JSX {
+  interface IntrinsicElements {
+    'ink-box': any;
+    'ink-text': any;
+    'ink-link': any;
+    'ink-raw-ansi': any;
+  }
+}
+
 // ============================================================================
 // Bun text/file loaders — allow importing non-TS assets as strings
 declare module '*.md' {

+ 5 - 5
src/upstreamproxy/relay.ts

@@ -262,7 +262,7 @@ export async function startNodeRelay(
       },
       end: () => sock.end(),
     }
-    sock.on('data', data =>
+    sock.on('data', (data: Buffer) =>
       handleData(adapter, st, data, wsUrl, authHeader, wsAuthHeader),
     )
     sock.on('close', () => cleanupConn(states.get(sock)))
@@ -381,7 +381,7 @@ function openTunnel(
     // responds with its own "HTTP/1.1 200" over the tunnel; we just pipe it.
     const head =
       `${connectLine}\r\n` + `Proxy-Authorization: ${authHeader}\r\n` + `\r\n`
-    ws.send(encodeChunk(Buffer.from(head, 'utf8')))
+    ws.send(encodeChunk(new Uint8Array(Buffer.from(head, 'utf8'))) as any)
     // Flush anything that arrived while the WS handshake was in flight —
     // trailing bytes from the CONNECT packet and any data() callbacks that
     // fired before onopen.
@@ -429,15 +429,15 @@ function openTunnel(
 
 function sendKeepalive(ws: WebSocketLike): void {
   if (ws.readyState === WebSocket.OPEN) {
-    ws.send(encodeChunk(new Uint8Array(0)))
+    ws.send(encodeChunk(new Uint8Array(0)) as any)
   }
 }
 
 function forwardToWs(ws: WebSocketLike, data: Buffer): void {
   if (ws.readyState !== WebSocket.OPEN) return
   for (let off = 0; off < data.length; off += MAX_CHUNK_BYTES) {
-    const slice = data.subarray(off, off + MAX_CHUNK_BYTES)
-    ws.send(encodeChunk(slice))
+    const slice = new Uint8Array(data.subarray(off, off + MAX_CHUNK_BYTES))
+    ws.send(encodeChunk(slice) as any)
   }
 }
 

+ 2 - 2
src/utils/cronScheduler.ts

@@ -553,8 +553,8 @@ export function buildMissedTaskNotification(missed: CronTask[]): string {
     // Use a fence one longer than any backtick run in the prompt so a
     // prompt containing ``` cannot close the fence early and un-wrap the
     // trailing text (CommonMark fence-matching rule).
-    const longestRun = (t.prompt.match(/`+/g) ?? []).reduce(
-      (max, run) => Math.max(max, run.length),
+    const longestRun = (t.prompt.match(/`+/g) ?? ([] as string[])).reduce(
+      (max: number, run: string) => Math.max(max, run.length),
       0,
     )
     const fence = '`'.repeat(Math.max(3, longestRun + 1))

+ 8 - 8
src/utils/dxt/helpers.ts

@@ -1,4 +1,4 @@
-import type { McpbManifest } from '@anthropic-ai/mcpb'
+import type { McpbManifestAny } from '@anthropic-ai/mcpb'
 import { errorMessage } from '../errors.js'
 import { jsonParse } from '../slowOperations.js'
 
@@ -12,15 +12,15 @@ import { jsonParse } from '../slowOperations.js'
  */
 export async function validateManifest(
   manifestJson: unknown,
-): Promise<McpbManifest> {
-  const { McpbManifestSchema } = await import('@anthropic-ai/mcpb')
-  const parseResult = McpbManifestSchema.safeParse(manifestJson)
+): Promise<McpbManifestAny> {
+  const { vAny } = await import('@anthropic-ai/mcpb')
+  const parseResult = vAny.McpbManifestSchema.safeParse(manifestJson)
 
   if (!parseResult.success) {
     const errors = parseResult.error.flatten()
     const errorMessages = [
       ...Object.entries(errors.fieldErrors).map(
-        ([field, errs]) => `${field}: ${errs?.join(', ')}`,
+        ([field, errs]) => `${field}: ${(errs as any)?.join(', ')}`,
       ),
       ...(errors.formErrors || []),
     ]
@@ -38,7 +38,7 @@ export async function validateManifest(
  */
 export async function parseAndValidateManifestFromText(
   manifestText: string,
-): Promise<McpbManifest> {
+): Promise<McpbManifestAny> {
   let manifestJson: unknown
 
   try {
@@ -55,7 +55,7 @@ export async function parseAndValidateManifestFromText(
  */
 export async function parseAndValidateManifestFromBytes(
   manifestData: Uint8Array,
-): Promise<McpbManifest> {
+): Promise<McpbManifestAny> {
   const manifestText = new TextDecoder().decode(manifestData)
   return parseAndValidateManifestFromText(manifestText)
 }
@@ -65,7 +65,7 @@ export async function parseAndValidateManifestFromBytes(
  * Uses the same algorithm as the directory backend for consistency.
  */
 export function generateExtensionId(
-  manifest: McpbManifest,
+  manifest: McpbManifestAny,
   prefix?: 'local.unpacked' | 'local.dxt',
 ): string {
   const sanitize = (str: string) =>

+ 3 - 3
src/utils/filePersistence/outputsScanner.ts

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

+ 3 - 3
src/utils/forkedAgent.ts

@@ -558,10 +558,10 @@ export async function runForkedAgent({
       if (message.type === 'stream_event') {
         if (
           'event' in message &&
-          message.event?.type === 'message_delta' &&
-          message.event.usage
+          (message as any).event?.type === 'message_delta' &&
+          (message as any).event.usage
         ) {
-          const turnUsage = updateUsage({ ...EMPTY_USAGE }, message.event.usage)
+          const turnUsage = updateUsage({ ...EMPTY_USAGE }, (message as any).event.usage)
           totalUsage = accumulateUsage(totalUsage, turnUsage)
         }
         continue

+ 4 - 3
src/utils/permissions/filesystem.ts

@@ -1325,14 +1325,15 @@ export function checkWritePermissionForTool<Input extends AnyObject>(
           },
         ]
       : generateSuggestions(path, 'write', toolPermissionContext, pathsToCheck)
+    const failedCheck = safetyCheck as { safe: false; message: string; classifierApprovable: boolean }
     return {
       behavior: 'ask',
-      message: safetyCheck.message,
+      message: failedCheck.message,
       suggestions: safetySuggestions,
       decisionReason: {
         type: 'safetyCheck',
-        reason: safetyCheck.message,
-        classifierApprovable: safetyCheck.classifierApprovable,
+        reason: failedCheck.message,
+        classifierApprovable: failedCheck.classifierApprovable,
       },
     }
   }

+ 5 - 4
src/utils/permissions/pathValidation.ts

@@ -112,8 +112,8 @@ export function isPathInSandboxWriteAllowlist(resolvedPath: string): boolean {
   // their resolution to avoid N × config.length redundant syscalls per
   // command with N write targets (matching getResolvedWorkingDirPaths).
   const pathsToCheck = getPathsForPermissionCheck(resolvedPath)
-  const resolvedAllow = allowOnly.flatMap(getResolvedSandboxConfigPath)
-  const resolvedDeny = denyWithinAllow.flatMap(getResolvedSandboxConfigPath)
+  const resolvedAllow = allowOnly.flatMap(getResolvedSandboxConfigPath) as string[]
+  const resolvedDeny = denyWithinAllow.flatMap(getResolvedSandboxConfigPath) as string[]
   return pathsToCheck.every(p => {
     for (const denyPath of resolvedDeny) {
       if (pathInWorkingPath(p, denyPath)) return false
@@ -184,12 +184,13 @@ export function isPathAllowed(
       precomputedPathsToCheck,
     )
     if (!safetyCheck.safe) {
+      const failedCheck = safetyCheck as { safe: false; message: string; classifierApprovable: boolean }
       return {
         allowed: false,
         decisionReason: {
           type: 'safetyCheck',
-          reason: safetyCheck.message,
-          classifierApprovable: safetyCheck.classifierApprovable,
+          reason: failedCheck.message,
+          classifierApprovable: failedCheck.classifierApprovable,
         },
       }
     }

+ 3 - 3
src/utils/permissions/permissions.ts

@@ -412,7 +412,7 @@ async function runPermissionRequestHooksForHeadlessAgent(
       input,
       context,
       permissionMode,
-      suggestions,
+      suggestions as any,
       context.abortController.signal,
     )) {
       if (!hookResult.permissionRequestResult) {
@@ -423,12 +423,12 @@ async function runPermissionRequestHooksForHeadlessAgent(
         const finalInput = decision.updatedInput ?? input
         // Persist permission updates if provided
         if (decision.updatedPermissions?.length) {
-          persistPermissionUpdates(decision.updatedPermissions)
+          persistPermissionUpdates(decision.updatedPermissions as any)
           context.setAppState(prev => ({
             ...prev,
             toolPermissionContext: applyPermissionUpdates(
               prev.toolPermissionContext,
-              decision.updatedPermissions!,
+              decision.updatedPermissions as any,
             ),
           }))
         }

+ 3 - 3
src/utils/processUserInput/processUserInput.ts

@@ -251,12 +251,12 @@ export async function processUserInput({
             ...hookResult.message,
             attachment: {
               ...hookResult.message.attachment,
-              content: applyTruncation(hookResult.message.attachment.content),
+              content: applyTruncation(hookResult.message.attachment.content as string),
             },
-          })
+          } as AttachmentMessage)
           break
         default:
-          result.messages.push(hookResult.message)
+          result.messages.push(hookResult.message as AttachmentMessage)
           break
       }
     }

+ 3 - 3
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 : [],
+    m.type === 'assistant' ? (m.message.content as unknown as Array<{ type: string; [key: string]: unknown }>) : [],
   )
 
   if (assistantBlocks.length > 0) {
@@ -136,7 +136,7 @@ function extractSideQuestionResponse(messages: Message[]): string | null {
     // No text — check if the model tried to call a tool despite instructions.
     const toolUse = assistantBlocks.find(b => b.type === 'tool_use')
     if (toolUse) {
-      const toolName = 'name' in toolUse ? toolUse.name : 'a tool'
+      const toolName = 'name' in toolUse ? (toolUse as any).name : 'a tool'
       return `(The model tried to call ${toolName} instead of answering directly. Try rephrasing or ask in the main conversation.)`
     }
   }
@@ -148,7 +148,7 @@ function extractSideQuestionResponse(messages: Message[]): string | null {
       m.type === 'system' && 'subtype' in m && m.subtype === 'api_error',
   )
   if (apiErr) {
-    return `(API error: ${formatAPIError(apiErr.error)})`
+    return `(API error: ${formatAPIError(apiErr.error as any)})`
   }
 
   return null

+ 2 - 2
src/utils/sliceAnsi.ts

@@ -43,7 +43,7 @@ export default function sliceAnsi(
     // pass start/end in display cells (via stringWidth), so position must
     // track the same units.
     const width =
-      token.type === 'ansi' ? 0 : token.fullWidth ? 2 : stringWidth(token.value)
+      token.type === 'ansi' ? 0 : token.type === 'char' ? (token.fullWidth ? 2 : stringWidth(token.value)) : 0
 
     // Break AFTER trailing zero-width marks — a combining mark attaches to
     // the preceding base char, so "भा" (भ + ा, 1 display cell) sliced at
@@ -77,7 +77,7 @@ export default function sliceAnsi(
       }
 
       if (include) {
-        result += token.value
+        result += (token as any).value
       }
 
       position += width

+ 6 - 5
src/utils/teleport/gitBundle.ts

@@ -231,16 +231,17 @@ export async function createAndUploadGitBundle(
     )
 
     if (!bundle.ok) {
-      logForDebugging(`[gitBundle] ${bundle.error}`)
+      const failedBundle = bundle as { ok: false; error: string; failReason: BundleFailReason }
+      logForDebugging(`[gitBundle] ${failedBundle.error}`)
       logEvent('tengu_ccr_bundle_upload', {
         outcome:
-          bundle.failReason as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
+          failedBundle.failReason as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
         max_bytes: maxBytes,
       })
       return {
         success: false,
-        error: bundle.error,
-        failReason: bundle.failReason,
+        error: failedBundle.error,
+        failReason: failedBundle.failReason,
       }
     }
 
@@ -254,7 +255,7 @@ export async function createAndUploadGitBundle(
         outcome:
           'failed' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
       })
-      return { success: false, error: upload.error }
+      return { success: false, error: (upload as { success: false; error: string }).error }
     }
 
     logForDebugging(

+ 3 - 3
src/utils/toolSearch.ts

@@ -551,7 +551,7 @@ export function extractDiscoveredToolNames(messages: Message[]): Set<string> {
     // check rather than isCompactBoundaryMessage — utils/messages.ts imports
     // from this file, so importing back would be circular.
     if (msg.type === 'system' && msg.subtype === 'compact_boundary') {
-      const carried = msg.compactMetadata?.preCompactDiscoveredTools
+      const carried = (msg as any).compactMetadata?.preCompactDiscoveredTools as string[] | undefined
       if (carried) {
         for (const name of carried) discoveredTools.add(name)
         carriedFromBoundary += carried.length
@@ -658,8 +658,8 @@ export function getDeferredToolsDelta(
     attachmentTypesSeen.add(msg.attachment.type)
     if (msg.attachment.type !== 'deferred_tools_delta') continue
     dtdCount++
-    for (const n of msg.attachment.addedNames) announced.add(n)
-    for (const n of msg.attachment.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 deferred: Tool[] = tools.filter(isDeferredTool)