feat: 问就是封包

This commit is contained in:
claude-code-best 2026-03-31 23:32:58 +08:00
parent d7a729ca68
commit dd9cd782a7
67 changed files with 423 additions and 172 deletions

263
README.md
View File

@ -2,27 +2,254 @@
Anthropic 官方 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI 工具的源码反编译/逆向还原项目。目标是将 Claude Code 核心功能跑通,必要时删减次级能力。 Anthropic 官方 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI 工具的源码反编译/逆向还原项目。目标是将 Claude Code 核心功能跑通,必要时删减次级能力。
## 核心能力 ## 能力清单
- API 通信Anthropic SDK / Bedrock / Vertex > ✅ = 已实现 ⚠️ = 部分实现 / 条件启用 ❌ = stub / 移除 / feature flag 关闭
- Bash / FileRead / FileWrite / FileEdit 等核心工具
- REPL 交互界面ink 终端渲染)
- 对话历史与会话管理
- 权限系统
- Agent / 子代理系统
## 已删减模块 ### 核心系统
| 模块 | 处理方式 | | 能力 | 状态 | 说明 |
|------|----------| |------|------|------|
| Computer Use (`@ant/computer-use-*`) | stub | | REPL 交互界面Ink 终端渲染) | ✅ | 主屏幕 5000+ 行,完整交互 |
| Claude for Chrome MCP | stub | | API 通信 — Anthropic Direct | ✅ | 支持 API Key + OAuth |
| Magic Docs / Voice Mode / LSP Server | 移除 | | API 通信 — AWS Bedrock | ✅ | 支持凭据刷新、Bearer Token |
| Analytics / GrowthBook / Sentry | 空实现 | | API 通信 — Google Vertex | ✅ | 支持 GCP 凭据刷新 |
| Plugins / Marketplace / Desktop Upsell | 移除 | | API 通信 — Azure Foundry | ✅ | 支持 API Key + Azure AD |
| Ultraplan / Tungsten / Auto Dream | 移除 | | 流式对话与工具调用循环 (`query.ts`) | ✅ | 1700+ 行含自动压缩、token 追踪 |
| MCP OAuth/IDP | 简化 | | 会话引擎 (`QueryEngine.ts`) | ✅ | 1300+ 行,管理对话状态与归因 |
| DAEMON / BRIDGE / BG_SESSIONS / TEMPLATES 等 | feature flag 关闭 | | 上下文构建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 Flags30 个,全部返回 `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`
## 快速开始 ## 快速开始

View File

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

View File

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

View File

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

View File

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

View File

@ -178,9 +178,9 @@ export async function mcpListHandler(): Promise<void> {
// biome-ignore lint/suspicious/noConsole:: intentional console output // biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${name}: ${server.url} - ${status}`); console.log(`${name}: ${server.url} - ${status}`);
} else if (!server.type || server.type === 'stdio') { } 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 // 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}`);
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -130,11 +130,12 @@ function Web({
}); });
const result = await importGithubToken(token); const result = await importGithubToken(token);
if (!result.ok) { if (!result.ok) {
const importErr = (result as { ok: false; error: ImportTokenError }).error;
logEvent('tengu_remote_setup_result', { logEvent('tengu_remote_setup_result', {
result: 'import_failed' as SafeString, 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; return;
} }

View File

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

View File

@ -154,11 +154,12 @@ function ResumeCommand({
} }
// Different project - show command instead of resuming // 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); if (raw) process.stdout.write(raw);
// Format the output message // 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, { onDone(message, {
display: 'user' display: 'user'
}); });

View File

@ -136,7 +136,7 @@ export async function launchRemoteReview(
// consume at session creation routes billing: first N zero-rate, then // consume at session creation routes billing: first N zero-rate, then
// anthropic:cccr org-service-key (overage-only). // anthropic:cccr org-service-key (overage-only).
if (!eligibility.eligible) { 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', e => e.type !== 'no_remote_environment',
) )
if (blockers.length > 0) { if (blockers.length > 0) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -95,7 +95,8 @@ export function Settings(t0) {
} }
let t7; let t7;
if ($[13] !== contentHeight) { 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; $[13] = contentHeight;
$[14] = t7; $[14] = t7;
} else { } else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -251,7 +251,7 @@ export function ToolSelector(t0) {
let t9; let t9;
if ($[22] !== selectedSet) { if ($[22] !== selectedSet) {
t9 = bucketTools => { 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; const needsSelection = selected < bucketTools.length;
return () => { return () => {
const toolNames_1 = bucketTools.map(_temp4); const toolNames_1 = bucketTools.map(_temp4);
@ -321,7 +321,7 @@ export function ToolSelector(t0) {
if (bucketTools_0.length === 0) { if (bucketTools_0.length === 0) {
return; 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; const isFullySelected = selected_0 === bucketTools_0.length;
navigableItems.push({ navigableItems.push({
id, id,

View File

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

View File

@ -337,7 +337,7 @@ function renderToolUseProgressMessage(tool: Tool, tools: Tools, lookups: ReturnT
columns: number; columns: number;
rows: number; rows: number;
}): React.ReactNode { }): 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 { try {
const toolMessages = tool.renderToolUseProgressMessage?.(toolProgressMessages, { const toolMessages = tool.renderToolUseProgressMessage?.(toolProgressMessages, {
tools, tools,

View File

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

View File

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

View File

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

View File

@ -62,7 +62,7 @@ function PermissionDecisionInfoItem(t0) {
return <Box flexDirection="column">{Array.from(decisionReason.reasons.entries()).map(t2 => { 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 [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); 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>; })}</Box>;
} }
default: default:

View File

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

View File

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

View File

@ -132,11 +132,11 @@ function useCanUseTool(setToolUseConfirmQueue, setToolPermissionContext) {
if (ctx.resolveIfAborted(resolve)) { if (ctx.resolveIfAborted(resolve)) {
return; 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 { consumeSpeculativeClassifierCheck((input as {
command: string; command: string;
}).command); }).command);
const matchedRule = raceResult.result.matchedDescription ?? undefined; const matchedRule = (raceResult as any).result.matchedDescription ?? undefined;
if (matchedRule) { if (matchedRule) {
setClassifierApproval(toolUseID, matchedRule); setClassifierApproval(toolUseID, matchedRule);
} }
@ -150,7 +150,7 @@ function useCanUseTool(setToolUseConfirmQueue, setToolPermissionContext) {
decisionReason: { decisionReason: {
type: "classifier" as const, type: "classifier" as const,
classifier: "bash_allow" 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; return;

View File

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

View File

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

View File

@ -29,7 +29,7 @@ type SpanProps = {
* *
* Memoized to prevent re-renders when parent changes but children string is the same. * 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 $ = _c(12);
const { const {
children, children,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

11
src/types/global.d.ts vendored
View File

@ -67,6 +67,17 @@ declare const BUILD_TARGET: string
declare const BUILD_ENV: string declare const BUILD_ENV: string
declare const INTERFACE_TYPE: 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 // Bun text/file loaders — allow importing non-TS assets as strings
declare module '*.md' { declare module '*.md' {

View File

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

View File

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

View File

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

View File

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

View File

@ -558,10 +558,10 @@ export async function runForkedAgent({
if (message.type === 'stream_event') { if (message.type === 'stream_event') {
if ( if (
'event' in message && 'event' in message &&
message.event?.type === 'message_delta' && (message as any).event?.type === 'message_delta' &&
message.event.usage (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) totalUsage = accumulateUsage(totalUsage, turnUsage)
} }
continue continue

View File

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

View File

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

View File

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

View File

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

View File

@ -125,7 +125,7 @@ ${question}`
function extractSideQuestionResponse(messages: Message[]): string | null { function extractSideQuestionResponse(messages: Message[]): string | null {
// Flatten all assistant content blocks across the per-block messages. // Flatten all assistant content blocks across the per-block messages.
const assistantBlocks = messages.flatMap(m => 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) { 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. // No text — check if the model tried to call a tool despite instructions.
const toolUse = assistantBlocks.find(b => b.type === 'tool_use') const toolUse = assistantBlocks.find(b => b.type === 'tool_use')
if (toolUse) { 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.)` 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', m.type === 'system' && 'subtype' in m && m.subtype === 'api_error',
) )
if (apiErr) { if (apiErr) {
return `(API error: ${formatAPIError(apiErr.error)})` return `(API error: ${formatAPIError(apiErr.error as any)})`
} }
return null return null

View File

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

View File

@ -231,16 +231,17 @@ export async function createAndUploadGitBundle(
) )
if (!bundle.ok) { 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', { logEvent('tengu_ccr_bundle_upload', {
outcome: 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, max_bytes: maxBytes,
}) })
return { return {
success: false, success: false,
error: bundle.error, error: failedBundle.error,
failReason: bundle.failReason, failReason: failedBundle.failReason,
} }
} }
@ -254,7 +255,7 @@ export async function createAndUploadGitBundle(
outcome: outcome:
'failed' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, '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( logForDebugging(

View File

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