feat: 完成大部分操作
This commit is contained in:
parent
3d4cb096d1
commit
c4d92178b7
106
README.md
Normal file
106
README.md
Normal 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 MB,5300+ 模块)。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
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/ # stub(macOS 修饰键检测)
|
||||
│ ├── 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/) 所有。
|
||||
54
RECORD.md
54
RECORD.md
@ -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/` | stub(macOS 修饰键检测) |
|
||||
| `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)
|
||||
```
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
}
|
||||
|
||||
@ -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: () => {} };
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
// Auto-generated stub — replace with real implementation
|
||||
export {};
|
||||
export function useAntOrgWarningNotification(): void {}
|
||||
|
||||
@ -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>} />
|
||||
|
||||
@ -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
28
src/types/global.d.ts
vendored
@ -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
|
||||
}
|
||||
|
||||
@ -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' };
|
||||
|
||||
@ -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;
|
||||
|
||||
3
src/types/sdk-stubs.d.ts
vendored
3
src/types/sdk-stubs.d.ts
vendored
@ -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 }
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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',
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user