feat: 完成大部分操作

This commit is contained in:
claude-code-best 2026-03-31 21:40:37 +08:00
parent 3d4cb096d1
commit c4d92178b7
22 changed files with 561 additions and 98 deletions

106
README.md Normal file
View File

@ -0,0 +1,106 @@
# Claude Code (Reverse-Engineered)
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 关闭 |
## 快速开始
### 环境要求
- [Bun](https://bun.sh/) >= 1.0
- Node.js >= 18部分依赖需要
- 有效的 Anthropic API Key或 Bedrock / Vertex 凭据)
### 安装
```bash
bun install
```
### 运行
```bash
# 开发模式watch
bun run dev
# 直接运行
bun run src/entrypoints/cli.tsx
# 管道模式(-p
echo "say hello" | bun run src/entrypoints/cli.tsx -p
# 构建
bun run build
```
构建产物输出到 `dist/cli.js`~25 MB5300+ 模块)。
## 项目结构
```
claude-code/
├── src/
│ ├── entrypoints/
│ │ ├── cli.tsx # 入口文件(含 MACRO/feature polyfill
│ │ └── sdk/ # SDK 子模块 stub
│ ├── main.tsx # 主 CLI 逻辑Commander 定义)
│ └── types/
│ ├── global.d.ts # 全局变量/宏声明
│ └── internal-modules.d.ts # 内部 npm 包类型声明
├── packages/ # Monorepo workspace 包
│ ├── color-diff-napi/ # 完整实现(终端 color diff
│ ├── modifiers-napi/ # stubmacOS 修饰键检测)
│ ├── audio-capture-napi/ # stub
│ ├── image-processor-napi/# stub
│ ├── url-handler-napi/ # stub
│ └── @ant/ # Anthropic 内部包 stub
│ ├── claude-for-chrome-mcp/
│ ├── computer-use-mcp/
│ ├── computer-use-input/
│ └── computer-use-swift/
├── scripts/ # 自动化 stub 生成脚本
├── dist/ # 构建输出
└── package.json # Bun workspaces monorepo 配置
```
## 技术说明
### 运行时 Polyfill
入口文件 `src/entrypoints/cli.tsx` 顶部注入了必要的 polyfill
- `feature()` — 所有 feature flag 返回 `false`,跳过未实现分支
- `globalThis.MACRO` — 模拟构建时宏注入VERSION 等)
### 类型状态
仍有 ~1341 个 tsc 错误,均为反编译产生的源码级类型问题(`unknown` / `never` / `{}`**不影响 Bun 运行时**。
### Monorepo
项目采用 Bun workspaces 管理内部包。原先手工放在 `node_modules/` 下的 stub 已统一迁入 `packages/`,通过 `workspace:*` 解析。
## 许可证
本项目仅供学习研究用途。Claude Code 的所有权利归 [Anthropic](https://www.anthropic.com/) 所有。

View File

@ -103,3 +103,57 @@ const feature = (_name: string) => false; // 所有 feature flag 分支被跳
| `scripts/create-type-stubs.mjs` | 自动 stub 生成脚本 |
| `scripts/fix-default-stubs.mjs` | 修复默认导出 stub |
| `scripts/fix-missing-exports.mjs` | 补全缺失导出 |
---
## 五、Monorepo 改造2026-03-31
### 5.1 背景
`color-diff-napi` 原先是手工放在 `node_modules/` 下的 stub 文件,导出的是普通对象而非 class导致 `new ColorDiff(...)` 报错:
```
ERROR Object is not a constructor (evaluating 'new ColorDiff(patch, firstLine, filePath, fileContent)')
```
同时 `@ant/*`、其他 `*-napi` 包也只有 `declare module` 类型声明,无运行时实现。
### 5.2 方案
将项目改造为 **Bun workspaces monorepo**,所有内部包统一放在 `packages/` 下,通过 `workspace:*` 依赖解析。
### 5.3 创建的 workspace 包
| 包名 | 路径 | 类型 |
|------|------|------|
| `color-diff-napi` | `packages/color-diff-napi/` | 完整实现(~1000行 TS`src/native-ts/color-diff/` 移入) |
| `modifiers-napi` | `packages/modifiers-napi/` | stubmacOS 修饰键检测) |
| `audio-capture-napi` | `packages/audio-capture-napi/` | stub |
| `image-processor-napi` | `packages/image-processor-napi/` | stub |
| `url-handler-napi` | `packages/url-handler-napi/` | stub |
| `@ant/claude-for-chrome-mcp` | `packages/@ant/claude-for-chrome-mcp/` | stub |
| `@ant/computer-use-mcp` | `packages/@ant/computer-use-mcp/` | stub含 subpath exports: sentinelApps, types |
| `@ant/computer-use-input` | `packages/@ant/computer-use-input/` | stub |
| `@ant/computer-use-swift` | `packages/@ant/computer-use-swift/` | stub |
### 5.4 新增的 npm 依赖
| 包名 | 原因 |
|------|------|
| `@opentelemetry/semantic-conventions` | 构建报错缺失 |
| `fflate` | `src/utils/dxt/zip.ts` 动态 import |
| `vscode-jsonrpc` | `src/services/lsp/LSPClient.ts` import |
| `@aws-sdk/credential-provider-node` | `src/utils/proxy.ts` 动态 import |
### 5.5 关键变更
- `package.json`:添加 `workspaces`,添加所有 workspace 包和缺失 npm 依赖
- `src/types/internal-modules.d.ts`:删除已移入 monorepo 的 `declare module` 块,仅保留 `bun:bundle`、`bun:ffi`、`@anthropic-ai/mcpb`
- `src/native-ts/color-diff/``packages/color-diff-napi/src/`:移动并内联了对 `stringWidth``logError` 的依赖
- 删除 `node_modules/color-diff-napi/` 手工 stub
### 5.6 构建验证
```
$ bun run build
Bundled 5326 modules in 491ms
cli.js 25.74 MB (entry point)
```

View File

@ -9,7 +9,7 @@
],
"scripts": {
"build": "bun build src/entrypoints/cli.tsx --outdir dist --target bun",
"dev": "bun run --watch src/entrypoints/cli.tsx"
"dev": "bun run src/entrypoints/cli.tsx"
},
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.3.0",

View File

@ -1,2 +1,44 @@
export class ComputerUseInput {}
export class ComputerUseInputAPI {}
interface FrontmostAppInfo {
bundleId: string
appName: string
}
export class ComputerUseInputAPI {
declare moveMouse: (
x: number,
y: number,
animated: boolean,
) => Promise<void>
declare key: (
key: string,
action: 'press' | 'release',
) => Promise<void>
declare keys: (parts: string[]) => Promise<void>
declare mouseLocation: () => Promise<{ x: number; y: number }>
declare mouseButton: (
button: 'left' | 'right' | 'middle',
action: 'click' | 'press' | 'release',
count?: number,
) => Promise<void>
declare mouseScroll: (
amount: number,
direction: 'vertical' | 'horizontal',
) => Promise<void>
declare typeText: (text: string) => Promise<void>
declare getFrontmostAppInfo: () => FrontmostAppInfo | null
declare isSupported: true
}
interface ComputerUseInputUnsupported {
isSupported: false
}
export type ComputerUseInput = ComputerUseInputAPI | ComputerUseInputUnsupported

View File

@ -1 +1,112 @@
export class ComputerUseAPI {}
interface DisplayGeometry {
width: number
height: number
scaleFactor: number
displayId: number
}
interface PrepareDisplayResult {
activated: string
hidden: string[]
}
interface AppInfo {
bundleId: string
displayName: string
}
interface InstalledApp {
bundleId: string
displayName: string
path: string
iconDataUrl?: string
}
interface RunningApp {
bundleId: string
displayName: string
}
interface ScreenshotResult {
base64: string
width: number
height: number
}
interface ResolvePrepareCaptureResult {
base64: string
width: number
height: number
}
interface WindowDisplayInfo {
bundleId: string
displayIds: number[]
}
interface AppsAPI {
prepareDisplay(
allowlistBundleIds: string[],
surrogateHost: string,
displayId?: number,
): Promise<PrepareDisplayResult>
previewHideSet(
bundleIds: string[],
displayId?: number,
): Promise<Array<AppInfo>>
findWindowDisplays(
bundleIds: string[],
): Promise<Array<WindowDisplayInfo>>
appUnderPoint(
x: number,
y: number,
): Promise<AppInfo | null>
listInstalled(): Promise<InstalledApp[]>
iconDataUrl(path: string): string | null
listRunning(): RunningApp[]
open(bundleId: string): Promise<void>
unhide(bundleIds: string[]): Promise<void>
}
interface DisplayAPI {
getSize(displayId?: number): DisplayGeometry
listAll(): DisplayGeometry[]
}
interface ScreenshotAPI {
captureExcluding(
allowedBundleIds: string[],
quality: number,
targetW: number,
targetH: number,
displayId?: number,
): Promise<ScreenshotResult>
captureRegion(
allowedBundleIds: string[],
x: number,
y: number,
w: number,
h: number,
outW: number,
outH: number,
quality: number,
displayId?: number,
): Promise<ScreenshotResult>
}
export class ComputerUseAPI {
declare apps: AppsAPI
declare display: DisplayAPI
declare screenshot: ScreenshotAPI
declare resolvePrepareCapture: (
allowedBundleIds: string[],
surrogateHost: string,
quality: number,
targetW: number,
targetH: number,
displayId?: number,
autoResolve?: boolean,
doHide?: boolean,
) => Promise<ResolvePrepareCaptureResult>
}

View File

@ -1,2 +1,9 @@
// Auto-generated stub — replace with real implementation
export {};
export function useFrustrationDetection(
_messages: unknown[],
_isLoading: boolean,
_hasActivePrompt: boolean,
_otherSurveyOpen: boolean,
): { state: 'closed' | 'open'; handleTranscriptSelect: () => void } {
return { state: 'closed', handleTranscriptSelect: () => {} };
}

View File

@ -294,8 +294,8 @@ function PromptInput({
// otherwise bridge becomes an invisible selection stop.
const bridgeFooterVisible = replBridgeConnected && (replBridgeExplicit || replBridgeReconnecting);
// Tmux pill (ant-only) — visible when there's an active tungsten session
const hasTungstenSession = useAppState(s => "external" === 'ant' && s.tungstenActiveSession !== undefined);
const tmuxFooterVisible = "external" === 'ant' && hasTungstenSession;
const hasTungstenSession = useAppState(s => ("external" as string) === 'ant' && s.tungstenActiveSession !== undefined);
const tmuxFooterVisible = ("external" as string) === 'ant' && hasTungstenSession;
// WebBrowser pill — visible when a browser is open
const bagelFooterVisible = useAppState(s => false);
const teamContext = useAppState(s => s.teamContext);
@ -391,7 +391,7 @@ function PromptInput({
// exist. When only local_agent tasks are running (coordinator/fork mode), the
// pill is absent, so the -1 sentinel would leave nothing visually selected.
// In that case, skip -1 and treat 0 as the minimum selectable index.
const hasBgTaskPill = useMemo(() => Object.values(tasks).some(t => isBackgroundTask(t) && !("external" === 'ant' && isPanelAgentTask(t))), [tasks]);
const hasBgTaskPill = useMemo(() => Object.values(tasks).some(t => isBackgroundTask(t) && !(("external" as string) === 'ant' && isPanelAgentTask(t))), [tasks]);
const minCoordinatorIndex = hasBgTaskPill ? -1 : 0;
// Clamp index when tasks complete and the list shrinks beneath the cursor
useEffect(() => {
@ -455,7 +455,7 @@ function PromptInput({
// Panel shows retained-completed agents too (getVisibleAgentTasks), so the
// pill must stay navigable whenever the panel has rows — not just when
// something is running.
const tasksFooterVisible = (runningTaskCount > 0 || "external" === 'ant' && coordinatorTaskCount > 0) && !shouldHideTasksFooter(tasks, showSpinnerTree);
const tasksFooterVisible = (runningTaskCount > 0 || ("external" as string) === 'ant' && coordinatorTaskCount > 0) && !shouldHideTasksFooter(tasks, showSpinnerTree);
const teamsFooterVisible = cachedTeams.length > 0;
const footerItems = useMemo(() => [tasksFooterVisible && 'tasks', tmuxFooterVisible && 'tmux', bagelFooterVisible && 'bagel', teamsFooterVisible && 'teams', bridgeFooterVisible && 'bridge', companionFooterVisible && 'companion'].filter(Boolean) as FooterItem[], [tasksFooterVisible, tmuxFooterVisible, bagelFooterVisible, teamsFooterVisible, bridgeFooterVisible, companionFooterVisible]);
@ -1054,7 +1054,7 @@ function PromptInput({
clearBuffer();
resetHistory();
return;
} else if (result.error === 'no_team_context') {
} else if ('error' in result && result.error === 'no_team_context') {
// No team context - fall through to normal prompt submission
} else {
// Unknown recipient - fall through to normal prompt submission
@ -1742,7 +1742,7 @@ function PromptInput({
useKeybindings({
'footer:up': () => {
// ↑ scrolls within the coordinator task list before leaving the pill
if (tasksSelected && "external" === 'ant' && coordinatorTaskCount > 0 && coordinatorTaskIndex > minCoordinatorIndex) {
if (tasksSelected && ("external" as string) === 'ant' && coordinatorTaskCount > 0 && coordinatorTaskIndex > minCoordinatorIndex) {
setCoordinatorTaskIndex(prev => prev - 1);
return;
}
@ -1750,7 +1750,7 @@ function PromptInput({
},
'footer:down': () => {
// ↓ scrolls within the coordinator task list, never leaves the pill
if (tasksSelected && "external" === 'ant' && coordinatorTaskCount > 0) {
if (tasksSelected && ("external" as string) === 'ant' && coordinatorTaskCount > 0) {
if (coordinatorTaskIndex < coordinatorTaskCount - 1) {
setCoordinatorTaskIndex(prev => prev + 1);
}
@ -1813,7 +1813,7 @@ function PromptInput({
}
break;
case 'tmux':
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
setAppState(prev => prev.tungstenPanelAutoHidden ? {
...prev,
tungstenPanelAutoHidden: false
@ -2306,7 +2306,7 @@ function getInitialPasteId(messages: Message[]): number {
if (message.type === 'user') {
// Check image paste IDs
if (message.imagePasteIds) {
for (const id of message.imagePasteIds) {
for (const id of message.imagePasteIds as number[]) {
if (id > maxId) maxId = id;
}
}

View File

@ -143,11 +143,11 @@ function PromptInputFooter({
</Box>
<Box flexShrink={1} gap={1}>
{isFullscreen ? null : <Notifications apiKeyStatus={apiKeyStatus} autoUpdaterResult={autoUpdaterResult} debug={debug} isAutoUpdating={isAutoUpdating} verbose={verbose} messages={messages} onAutoUpdaterResult={onAutoUpdaterResult} onChangeIsUpdating={onChangeIsUpdating} ideSelection={ideSelection} mcpClients={mcpClients} isInputWrapped={isInputWrapped} isNarrow={isNarrow} />}
{"external" === 'ant' && isUndercover() && <Text dimColor>undercover</Text>}
{("external" as string) === 'ant' && isUndercover() && <Text dimColor>undercover</Text>}
<BridgeStatusIndicator bridgeSelected={bridgeSelected} />
</Box>
</Box>
{"external" === 'ant' && <CoordinatorTaskPanel />}
{("external" as string) === 'ant' && <CoordinatorTaskPanel />}
</>;
}
export default memo(PromptInputFooter);

View File

@ -260,7 +260,7 @@ function ModeIndicator({
const expandedView = useAppState(s_3 => s_3.expandedView);
const showSpinnerTree = expandedView === 'teammates';
const prStatus = usePrStatus(isLoading, isPrStatusEnabled());
const hasTmuxSession = useAppState(s_4 => "external" === 'ant' && s_4.tungstenActiveSession !== undefined);
const hasTmuxSession = useAppState(s_4 => ("external" as string) === 'ant' && s_4.tungstenActiveSession !== undefined);
const nextTickAt = useSyncExternalStore(proactiveModule?.subscribeToProactiveChanges ?? NO_OP_SUBSCRIBE, proactiveModule?.getNextTickAt ?? NULL, NULL);
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
const voiceEnabled = feature('VOICE_MODE') ? useVoiceEnabled() : false;
@ -274,7 +274,7 @@ function ModeIndicator({
const selGetState = useSelection().getState;
const hasNextTick = nextTickAt !== null;
const isCoordinator = feature('COORDINATOR_MODE') ? coordinatorModule?.isCoordinatorMode() === true : false;
const runningTaskCount = useMemo(() => count(Object.values(tasks), t => isBackgroundTask(t) && !("external" === 'ant' && isPanelAgentTask(t))), [tasks]);
const runningTaskCount = useMemo(() => count(Object.values(tasks), t => isBackgroundTask(t) && !(("external" as string) === 'ant' && isPanelAgentTask(t))), [tasks]);
const tasksV2 = useTasksV2();
const hasTaskItems = tasksV2 !== undefined && tasksV2.length > 0;
const escShortcut = useShortcutDisplay('chat:cancel', 'Chat', 'esc').toLowerCase();
@ -365,7 +365,7 @@ function ModeIndicator({
// its click-target Box isn't nested inside the <Text wrap="truncate">
// wrapper (reconciler throws on Box-in-Text).
// Tmux pill (ant-only) — appears right after tasks in nav order
...("external" === 'ant' && hasTmuxSession ? [<TungstenPill key="tmux" selected={tmuxSelected} />] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [<TeamStatus key="teams" teamsSelected={teamsSelected} showHint={showHint && !hasBackgroundTasks} />] : []), ...(shouldShowPrStatus ? [<PrBadge key="pr-status" number={prStatus.number!} url={prStatus.url!} reviewState={prStatus.reviewState!} />] : [])];
...(("external" as string) === 'ant' && hasTmuxSession ? [<TungstenPill key="tmux" selected={tmuxSelected} />] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [<TeamStatus key="teams" teamsSelected={teamsSelected} showHint={showHint && !hasBackgroundTasks} />] : []), ...(shouldShowPrStatus ? [<PrBadge key="pr-status" number={prStatus.number!} url={prStatus.url!} reviewState={prStatus.reviewState!} />] : [])];
// Check if any in-process teammates exist (for hint text cycling)
const hasAnyInProcessTeammates = Object.values(tasks).some(t_2 => t_2.type === 'in_process_teammate' && t_2.status === 'running');
@ -399,7 +399,7 @@ function ModeIndicator({
}
// Add "↓ to manage tasks" hint when panel has visible rows
const hasCoordinatorTasks = "external" === 'ant' && getVisibleAgentTasks(tasks).length > 0;
const hasCoordinatorTasks = ("external" as string) === 'ant' && getVisibleAgentTasks(tasks).length > 0;
// Tasks pill renders as a Box sibling (not a parts entry) so its
// click-target Box isn't nested inside <Text wrap="truncate"> — the

View File

@ -34,7 +34,7 @@ function getIcon(itemId: string): string {
function isUnifiedSuggestion(itemId: string): boolean {
return itemId.startsWith('file-') || itemId.startsWith('mcp-resource-') || itemId.startsWith('agent-');
}
const SuggestionItemRow = memo(function SuggestionItemRow(t0) {
const SuggestionItemRow = memo(function SuggestionItemRow(t0: { item: SuggestionItem; maxColumnWidth: number; isSelected: boolean }) {
const $ = _c(36);
const {
item,

View File

@ -74,8 +74,8 @@ export function HighlightedInput(t0) {
$[8] = lo;
$[9] = hi;
} else {
lo = $[8];
hi = $[9];
lo = $[8] as number;
hi = $[9] as number;
}
sweepStart = lo - 10;
cycleLength = hi - lo + 20;

View File

@ -11,6 +11,10 @@ if (typeof globalThis.MACRO === "undefined") {
VERSION_CHANGELOG: "",
};
}
// Build-time constants — normally replaced by Bun bundler at compile time
(globalThis as any).BUILD_TARGET = "external";
(globalThis as any).BUILD_ENV = "production";
(globalThis as any).INTERFACE_TYPE = "stdio";
// Bugfix for corepack auto-pinning, which adds yarnpkg to peoples' package.jsons
// eslint-disable-next-line custom-rules/no-top-level-side-effects

View File

@ -98,7 +98,8 @@ export type SDKMessage = { type: string; [key: string]: unknown }
export type SDKUserMessage = { type: "user"; content: unknown; uuid: string; [key: string]: unknown }
export type SDKUserMessageReplay = SDKUserMessage
export type SDKAssistantMessage = { type: "assistant"; content: unknown; [key: string]: unknown }
export type SDKAssistantMessageError = { type: "assistant_error"; error: unknown; [key: string]: unknown }
export type SDKAssistantErrorMessage = { type: "assistant_error"; error: unknown; [key: string]: unknown }
export type SDKAssistantMessageError = 'authentication_failed' | 'billing_error' | 'rate_limit' | 'invalid_request' | 'server_error' | 'unknown' | 'max_output_tokens'
export type SDKPartialAssistantMessage = { type: "partial_assistant"; [key: string]: unknown }
export type SDKResultMessage = { type: "result"; [key: string]: unknown }
export type SDKResultSuccess = { type: "result_success"; [key: string]: unknown }

View File

@ -1,2 +1,2 @@
// Auto-generated stub — replace with real implementation
export {};
export function useAntOrgWarningNotification(): void {}

View File

@ -104,13 +104,13 @@ const VoiceKeybindingHandler: typeof import('../hooks/useVoiceIntegration.js').V
// Frustration detection is ant-only (dogfooding). Conditional require so external
// builds eliminate the module entirely (including its two O(n) useMemos that run
// on every messages change, plus the GrowthBook fetch).
const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = "external" === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
const useFrustrationDetection: typeof import('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection = ("external" as string) === 'ant' ? require('../components/FeedbackSurvey/useFrustrationDetection.js').useFrustrationDetection : () => ({
state: 'closed',
handleTranscriptSelect: () => {}
});
// Ant-only org warning. Conditional require so the org UUID list is
// eliminated from external builds (one UUID is on excluded-strings).
const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = "external" === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => {};
const useAntOrgWarningNotification: typeof import('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification = ("external" as string) === 'ant' ? require('../hooks/notifs/useAntOrgWarningNotification.js').useAntOrgWarningNotification : () => {};
// Dead code elimination: conditional import for coordinator mode
const getCoordinatorUserContext: (mcpClients: ReadonlyArray<{
name: string;
@ -218,9 +218,9 @@ import { EffortCallout, shouldShowEffortCallout } from '../components/EffortCall
import type { EffortValue } from '../utils/effort.js';
import { RemoteCallout } from '../components/RemoteCallout.js';
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
const AntModelSwitchCallout = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null;
const shouldShowAntModelSwitch = "external" === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false;
const UndercoverAutoCallout = "external" === 'ant' ? require('../components/UndercoverAutoCallout.js').UndercoverAutoCallout : null;
const AntModelSwitchCallout = ("external" as string) === 'ant' ? require('../components/AntModelSwitchCallout.js').AntModelSwitchCallout : null;
const shouldShowAntModelSwitch = ("external" as string) === 'ant' ? require('../components/AntModelSwitchCallout.js').shouldShowModelSwitchCallout : (): boolean => false;
const UndercoverAutoCallout = ("external" as string) === 'ant' ? require('../components/UndercoverAutoCallout.js').UndercoverAutoCallout : null;
/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
import { activityManager } from '../utils/activityManager.js';
import { createAbortController } from '../utils/abortController.js';
@ -601,7 +601,7 @@ export function REPL({
// Env-var gates hoisted to mount-time — isEnvTruthy does toLowerCase+trim+
// includes, and these were on the render path (hot during PageUp spam).
const titleDisabled = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE), []);
const moreRightEnabled = useMemo(() => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
const moreRightEnabled = useMemo(() => ("external" as string) === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), []);
const disableVirtualScroll = useMemo(() => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_VIRTUAL_SCROLL), []);
const disableMessageActions = feature('MESSAGE_ACTIONS') ?
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
@ -733,7 +733,7 @@ export function REPL({
const [showIdeOnboarding, setShowIdeOnboarding] = useState(false);
// Dead code elimination: model switch callout state (ant-only)
const [showModelSwitchCallout, setShowModelSwitchCallout] = useState(() => {
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
return shouldShowAntModelSwitch();
}
return false;
@ -1012,7 +1012,7 @@ export function REPL({
}, []);
const [showUndercoverCallout, setShowUndercoverCallout] = useState(false);
useEffect(() => {
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
void (async () => {
// Wait for repo classification to settle (memoized, no-op if primed).
const {
@ -2041,10 +2041,10 @@ export function REPL({
if (allowDialogsWithAnimation && showIdeOnboarding) return 'ide-onboarding';
// Model switch callout (ant-only, eliminated from external builds)
if ("external" === 'ant' && allowDialogsWithAnimation && showModelSwitchCallout) return 'model-switch';
if (("external" as string) === 'ant' && allowDialogsWithAnimation && showModelSwitchCallout) return 'model-switch';
// Undercover auto-enable explainer (ant-only, eliminated from external builds)
if ("external" === 'ant' && allowDialogsWithAnimation && showUndercoverCallout) return 'undercover-callout';
if (("external" as string) === 'ant' && allowDialogsWithAnimation && showUndercoverCallout) return 'undercover-callout';
// Effort callout (shown once for Opus 4.6 users when effort is enabled)
if (allowDialogsWithAnimation && showEffortCallout) return 'effort-callout';
@ -2482,7 +2482,7 @@ export function REPL({
dynamicSkillDirTriggers: new Set<string>(),
discoveredSkillNames: discoveredSkillNamesRef.current,
setResponseLength,
pushApiMetricsEntry: "external" === 'ant' ? (ttftMs: number) => {
pushApiMetricsEntry: ("external" as string) === 'ant' ? (ttftMs: number) => {
const now = Date.now();
const baseline = responseLengthRef.current;
apiMetricsRef.current.push({
@ -2605,7 +2605,7 @@ export function REPL({
if (feature('PROACTIVE') || feature('KAIROS')) {
proactiveModule?.setContextBlocked(false);
}
} else if (newMessage.type === 'progress' && isEphemeralToolProgress(newMessage.data.type)) {
} else if ((newMessage as MessageType).type === 'progress' && isEphemeralToolProgress((newMessage as ProgressMessage<unknown>).data.type)) {
// Replace the previous ephemeral progress tick for the same tool
// call instead of appending. Sleep/Bash emit a tick per second and
// only the last one is rendered; appending blows up the messages
@ -2618,7 +2618,7 @@ export function REPL({
// "Initializing…" because it renders the full progress trail.
setMessages(oldMessages => {
const last = oldMessages.at(-1);
if (last?.type === 'progress' && last.parentToolUseID === newMessage.parentToolUseID && last.data.type === newMessage.data.type) {
if (last?.type === 'progress' && last.parentToolUseID === (newMessage as MessageType).parentToolUseID && last.data.type === (newMessage as ProgressMessage<unknown>).data.type) {
const copy = oldMessages.slice();
copy[copy.length - 1] = newMessage;
return copy;
@ -2804,14 +2804,14 @@ export function REPL({
if (feature('BUDDY')) {
void fireCompanionObserver(messagesRef.current, reaction => setAppState(prev => prev.companionReaction === reaction ? prev : {
...prev,
companionReaction: reaction
companionReaction: reaction as string | undefined
}));
}
queryCheckpoint('query_end');
// Capture ant-only API metrics before resetLoadingState clears the ref.
// For multi-request turns (tool use loops), compute P50 across all requests.
if ("external" === 'ant' && apiMetricsRef.current.length > 0) {
if (("external" as string) === 'ant' && apiMetricsRef.current.length > 0) {
const entries = apiMetricsRef.current;
const ttfts = entries.map(e => e.ttftMs);
// Compute per-request OTPS using only active streaming time and
@ -2939,7 +2939,7 @@ export function REPL({
// minutes — wiping the session made the pill disappear entirely, forcing
// the user to re-invoke Tmux just to peek. Skip on abort so the panel
// stays open for inspection (matches the turn-duration guard below).
if ("external" === 'ant' && !abortController.signal.aborted) {
if (("external" as string) === 'ant' && !abortController.signal.aborted) {
setAppState(prev => {
if (prev.tungstenActiveSession === undefined) return prev;
if (prev.tungstenPanelAutoHidden === true) return prev;
@ -3062,7 +3062,7 @@ export function REPL({
}
// Atomically: clear initial message, set permission mode and rules, and store plan for verification
const shouldStorePlanForVerification = initialMsg.message.planContent && "external" === 'ant' && isEnvTruthy(undefined);
const shouldStorePlanForVerification = initialMsg.message.planContent && ("external" as string) === 'ant' && isEnvTruthy(undefined);
setAppState(prev => {
// Build and apply permission updates (mode + allowedPrompts rules)
let updatedToolPermissionContext = initialMsg.mode ? applyPermissionUpdates(prev.toolPermissionContext, buildPermissionUpdates(initialMsg.mode, initialMsg.allowedPrompts)) : prev.toolPermissionContext;
@ -3595,7 +3595,7 @@ export function REPL({
// Handler for when user presses 1 on survey thanks screen to share details
const handleSurveyRequestFeedback = useCallback(() => {
const command = "external" === 'ant' ? '/issue' : '/feedback';
const command = ("external" as string) === 'ant' ? '/issue' : '/feedback';
onSubmit(command, {
setCursorOffset: () => {},
clearBuffer: () => {},
@ -4063,7 +4063,7 @@ export function REPL({
// - Workers receive permission responses via mailbox messages
// - Leaders receive permission requests via mailbox messages
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
// Tasks mode: watch for tasks and auto-process them
// eslint-disable-next-line react-hooks/rules-of-hooks
// biome-ignore lint/correctness/useHookAtTopLevel: conditional for dead code elimination in external builds
@ -4172,7 +4172,7 @@ export function REPL({
// Fall back to default behavior
const hookType = currentHooks[0]?.data.hookEvent === 'SubagentStop' ? 'subagent stop' : 'stop';
if ("external" === 'ant') {
if (("external" as string) === 'ant') {
const cmd = currentHooks[completedCount]?.data.command;
const label = cmd ? ` '${truncateToWidth(cmd, 40)}'` : '';
return total === 1 ? `running ${hookType} hook${label}` : `running ${hookType} hook${label}\u2026 ${completedCount}/${total}`;
@ -4581,7 +4581,7 @@ export function REPL({
{toolJSX && !(toolJSX.isLocalJSXCommand && toolJSX.isImmediate) && !toolJsxCentered && <Box flexDirection="column" width="100%">
{toolJSX.jsx}
</Box>}
{"external" === 'ant' && <TungstenLiveMonitor />}
{("external" as string) === 'ant' && <TungstenLiveMonitor />}
{feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && <WebBrowserPanelModule.WebBrowserPanel /> : null}
<Box flexGrow={1} />
{showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
@ -4804,7 +4804,7 @@ export function REPL({
});
}} />}
{focusedInputDialog === 'ide-onboarding' && <IdeOnboardingDialog onDone={() => setShowIdeOnboarding(false)} installationStatus={ideInstallationStatus} />}
{"external" === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
{("external" as string) === 'ant' && focusedInputDialog === 'model-switch' && AntModelSwitchCallout && <AntModelSwitchCallout onDone={(selection: string, modelAlias?: string) => {
setShowModelSwitchCallout(false);
if (selection === 'switch' && modelAlias) {
setAppState(prev => ({
@ -4814,7 +4814,7 @@ export function REPL({
}));
}
}} />}
{"external" === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
{("external" as string) === 'ant' && focusedInputDialog === 'undercover-callout' && UndercoverAutoCallout && <UndercoverAutoCallout onDone={() => setShowUndercoverCallout(false)} />}
{focusedInputDialog === 'effort-callout' && <EffortCallout model={mainLoopModel} onDone={selection => {
setShowEffortCallout(false);
if (selection !== 'dismiss') {
@ -4897,7 +4897,7 @@ export function REPL({
{/* Frustration-triggered transcript sharing prompt */}
{frustrationDetection.state !== 'closed' && <FeedbackSurvey state={frustrationDetection.state} lastResponse={null} handleSelect={() => {}} handleTranscriptSelect={frustrationDetection.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} />}
{/* Skill improvement survey - appears when improvements detected (ant-only) */}
{"external" === 'ant' && skillImprovementSurvey.suggestion && <SkillImprovementSurvey isOpen={skillImprovementSurvey.isOpen} skillName={skillImprovementSurvey.suggestion.skillName} updates={skillImprovementSurvey.suggestion.updates} handleSelect={skillImprovementSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} />}
{("external" as string) === 'ant' && skillImprovementSurvey.suggestion && <SkillImprovementSurvey isOpen={skillImprovementSurvey.isOpen} skillName={skillImprovementSurvey.suggestion.skillName} updates={skillImprovementSurvey.suggestion.updates} handleSelect={skillImprovementSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} />}
{showIssueFlagBanner && <IssueFlagBanner />}
{}
<PromptInput debug={debug} ideSelection={ideSelection} hasSuppressedDialogs={!!hasSuppressedDialogs} isLocalJSXCommandActive={isShowingLocalJSXCommand} getToolUseContext={getToolUseContext} toolPermissionContext={toolPermissionContext} setToolPermissionContext={setToolPermissionContext} apiKeyStatus={apiKeyStatus} commands={commands} agents={agentDefinitions.activeAgents} isLoading={isLoading} onExit={handleExit} verbose={verbose} messages={messages} onAutoUpdaterResult={setAutoUpdaterResult} autoUpdaterResult={autoUpdaterResult} input={inputValue} onInputChange={setInputValue} mode={inputMode} onModeChange={setInputMode} stashedPrompt={stashedPrompt} setStashedPrompt={setStashedPrompt} submitCount={submitCount} onShowMessageSelector={handleShowMessageSelector} onMessageActionsEnter={
@ -4990,7 +4990,7 @@ export function REPL({
setIsMessageSelectorVisible(false);
setMessageSelectorPreselect(undefined);
}} />}
{"external" === 'ant' && <DevBar />}
{("external" as string) === 'ant' && <DevBar />}
</Box>
{feature('BUDDY') && !(companionNarrow && isFullscreenEnvEnabled()) && companionVisible ? <CompanionSprite /> : null}
</Box>} />

View File

@ -139,7 +139,7 @@ function useAppStore(): AppStateStore {
* const { text, promptId } = useAppState(s => s.promptSuggestion) // good
* ```
*/
export function useAppState(selector) {
export function useAppState<R>(selector: (state: AppState) => R): R {
const $ = _c(3);
const store = useAppStore();
let t0;
@ -183,7 +183,7 @@ const NOOP_SUBSCRIBE = () => () => {};
* Safe version of useAppState that returns undefined if called outside of AppStateProvider.
* Useful for components that may be rendered in contexts where AppStateProvider isn't available.
*/
export function useAppStateMaybeOutsideOfProvider(selector) {
export function useAppStateMaybeOutsideOfProvider<R>(selector: (state: AppState) => R): R | undefined {
const $ = _c(3);
const store = useContext(AppStoreContext);
let t0;

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

@ -58,4 +58,30 @@ declare function launchUltraplan(...args: unknown[]): void
declare type T = any
// Tungsten (internal)
declare function TungstenPill(): JSX.Element | null
declare function TungstenPill(props?: { key?: string; selected?: boolean }): JSX.Element | null
// ============================================================================
// Build-time constants — replaced by Bun bundler, polyfilled at runtime
// Using `string` (not literal types) so comparisons don't produce TS2367
declare const BUILD_TARGET: string
declare const BUILD_ENV: string
declare const INTERFACE_TYPE: string
// ============================================================================
// Bun text/file loaders — allow importing non-TS assets as strings
declare module '*.md' {
const content: string
export default content
}
declare module '*.txt' {
const content: string
export default content
}
declare module '*.html' {
const content: string
export default content
}
declare module '*.css' {
const content: string
export default content
}

View File

@ -1,40 +1,62 @@
// Auto-generated stub — replace with real implementation
export type Message = any;
export type AssistantMessage = any;
export type AttachmentMessage<T = any> = any;
export type ProgressMessage<T = any> = any;
export type SystemLocalCommandMessage = any;
export type SystemMessage = any;
export type UserMessage = any;
export type NormalizedUserMessage = any;
export type RequestStartEvent = any;
export type StreamEvent = any;
export type SystemCompactBoundaryMessage = any;
export type TombstoneMessage = any;
export type ToolUseSummaryMessage = any;
export type MessageOrigin = any;
export type CompactMetadata = any;
export type SystemAPIErrorMessage = any;
export type SystemFileSnapshotMessage = any;
export type NormalizedAssistantMessage<T = any> = any;
export type NormalizedMessage = any;
export type PartialCompactDirection = any;
export type StopHookInfo = any;
export type SystemAgentsKilledMessage = any;
export type SystemApiMetricsMessage = any;
export type SystemAwaySummaryMessage = any;
export type SystemBridgeStatusMessage = any;
export type SystemInformationalMessage = any;
export type SystemMemorySavedMessage = any;
export type SystemMessageLevel = any;
export type SystemMicrocompactBoundaryMessage = any;
export type SystemPermissionRetryMessage = any;
export type SystemScheduledTaskFireMessage = any;
export type SystemStopHookSummaryMessage = any;
export type SystemTurnDurationMessage = any;
export type GroupedToolUseMessage = any;
export type RenderableMessage = any;
export type CollapsedReadSearchGroup = any;
export type CollapsibleMessage = any;
export type HookResultMessage = any;
export type SystemThinkingMessage = any;
import type { UUID } from 'crypto'
/**
* Base message type with discriminant `type` field and common properties.
* Individual message subtypes (UserMessage, AssistantMessage, etc.) extend
* this with narrower `type` literals and additional fields.
*/
export type MessageType = 'user' | 'assistant' | 'system' | 'attachment' | 'progress'
export type Message = {
type: MessageType
uuid: UUID
isMeta?: boolean
isCompactSummary?: boolean
toolUseResult?: unknown
isVisibleInTranscriptOnly?: boolean
message?: {
role?: string
content?: string | Array<{ type: string; text?: string; [key: string]: unknown }>
usage?: Record<string, unknown>
[key: string]: unknown
}
[key: string]: unknown
}
export type AssistantMessage = Message & { type: 'assistant' };
export type AttachmentMessage<T = unknown> = Message & { type: 'attachment' };
export type ProgressMessage<T = unknown> = Message & { type: 'progress' };
export type SystemLocalCommandMessage = Message & { type: 'system' };
export type SystemMessage = Message & { type: 'system' };
export type UserMessage = Message & { type: 'user' };
export type NormalizedUserMessage = UserMessage;
export type RequestStartEvent = { type: string; [key: string]: unknown };
export type StreamEvent = { type: string; [key: string]: unknown };
export type SystemCompactBoundaryMessage = Message & { type: 'system' };
export type TombstoneMessage = Message;
export type ToolUseSummaryMessage = Message;
export type MessageOrigin = string;
export type CompactMetadata = Record<string, unknown>;
export type SystemAPIErrorMessage = Message & { type: 'system' };
export type SystemFileSnapshotMessage = Message & { type: 'system' };
export type NormalizedAssistantMessage<T = unknown> = AssistantMessage;
export type NormalizedMessage = Message;
export type PartialCompactDirection = string;
export type StopHookInfo = Record<string, unknown>;
export type SystemAgentsKilledMessage = Message & { type: 'system' };
export type SystemApiMetricsMessage = Message & { type: 'system' };
export type SystemAwaySummaryMessage = Message & { type: 'system' };
export type SystemBridgeStatusMessage = Message & { type: 'system' };
export type SystemInformationalMessage = Message & { type: 'system' };
export type SystemMemorySavedMessage = Message & { type: 'system' };
export type SystemMessageLevel = string;
export type SystemMicrocompactBoundaryMessage = Message & { type: 'system' };
export type SystemPermissionRetryMessage = Message & { type: 'system' };
export type SystemScheduledTaskFireMessage = Message & { type: 'system' };
export type SystemStopHookSummaryMessage = Message & { type: 'system' };
export type SystemTurnDurationMessage = Message & { type: 'system' };
export type GroupedToolUseMessage = Message;
export type RenderableMessage = Message;
export type CollapsedReadSearchGroup = Message;
export type CollapsibleMessage = Message;
export type HookResultMessage = Message;
export type SystemThinkingMessage = Message & { type: 'system' };

View File

@ -1,3 +1,10 @@
// Auto-generated stub — replace with real implementation
export type QueueOperationMessage = any;
export type QueueOperation = any;
export type QueueOperationMessage = {
type: 'queue-operation'
operation: QueueOperation
timestamp: string
sessionId: string
content?: string
[key: string]: unknown
}
export type QueueOperation = 'enqueue' | 'dequeue' | 'remove' | string;

View File

@ -94,7 +94,8 @@ declare module "*/sdk/coreTypes.generated.js" {
export type SDKUserMessage = { type: "user"; content: unknown; uuid: string; [key: string]: unknown }
export type SDKUserMessageReplay = SDKUserMessage
export type SDKAssistantMessage = { type: "assistant"; content: unknown; [key: string]: unknown }
export type SDKAssistantMessageError = { type: "assistant_error"; error: unknown; [key: string]: unknown }
export type SDKAssistantErrorMessage = { type: "assistant_error"; error: unknown; [key: string]: unknown }
export type SDKAssistantMessageError = 'authentication_failed' | 'billing_error' | 'rate_limit' | 'invalid_request' | 'server_error' | 'unknown' | 'max_output_tokens'
export type SDKPartialAssistantMessage = { type: "partial_assistant"; [key: string]: unknown }
export type SDKResultMessage = { type: "result"; [key: string]: unknown }
export type SDKResultSuccess = { type: "result_success"; [key: string]: unknown }

View File

@ -486,8 +486,87 @@ function parseHttpHookOutput(body: string): {
}
}
/** Typed representation of sync hook JSON output, matching the syncHookResponseSchema Zod schema. */
interface TypedSyncHookOutput {
continue?: boolean
suppressOutput?: boolean
stopReason?: string
decision?: 'approve' | 'block'
reason?: string
systemMessage?: string
hookSpecificOutput?:
| {
hookEventName: 'PreToolUse'
permissionDecision?: 'ask' | 'deny' | 'allow' | 'passthrough'
permissionDecisionReason?: string
updatedInput?: Record<string, unknown>
additionalContext?: string
}
| {
hookEventName: 'UserPromptSubmit'
additionalContext?: string
}
| {
hookEventName: 'SessionStart'
additionalContext?: string
initialUserMessage?: string
watchPaths?: string[]
}
| {
hookEventName: 'Setup'
additionalContext?: string
}
| {
hookEventName: 'SubagentStart'
additionalContext?: string
}
| {
hookEventName: 'PostToolUse'
additionalContext?: string
updatedMCPToolOutput?: unknown
}
| {
hookEventName: 'PostToolUseFailure'
additionalContext?: string
}
| {
hookEventName: 'PermissionDenied'
retry?: boolean
}
| {
hookEventName: 'Notification'
additionalContext?: string
}
| {
hookEventName: 'PermissionRequest'
decision?: PermissionRequestResult
}
| {
hookEventName: 'Elicitation'
action?: 'accept' | 'decline' | 'cancel'
content?: Record<string, unknown>
}
| {
hookEventName: 'ElicitationResult'
action?: 'accept' | 'decline' | 'cancel'
content?: Record<string, unknown>
}
| {
hookEventName: 'CwdChanged'
watchPaths?: string[]
}
| {
hookEventName: 'FileChanged'
watchPaths?: string[]
}
| {
hookEventName: 'WorktreeCreate'
worktreePath: string
}
}
function processHookJSONOutput({
json,
json: rawJson,
command,
hookName,
toolUseID,
@ -511,6 +590,9 @@ function processHookJSONOutput({
}): Partial<HookResult> {
const result: Partial<HookResult> = {}
// Cast to typed interface for type-safe property access
const json = rawJson as TypedSyncHookOutput
// At this point we know it's a sync response
const syncJson = json

View File

@ -1033,7 +1033,7 @@ class Project {
'sourceToolAssistantUUID' in message &&
message.sourceToolAssistantUUID
) {
effectiveParentUuid = message.sourceToolAssistantUUID
effectiveParentUuid = message.sourceToolAssistantUUID as UUID
}
const transcriptMessage: TranscriptMessage = {
@ -2120,7 +2120,7 @@ function recoverOrphanedParallelToolResults(
chain: TranscriptMessage[],
seen: Set<UUID>,
): TranscriptMessage[] {
type ChainAssistant = Extract<TranscriptMessage, { type: 'assistant' }>
type ChainAssistant = TranscriptMessage & { type: 'assistant' }
const chainAssistants = chain.filter(
(m): m is ChainAssistant => m.type === 'assistant',
)