mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
feat: 工具层及 mcp 大重构 (#252)
* feat: 第一版大重构 * fix: 修复类型问题 * chore: 更新版本到 1.3.2 * Add brave as alternative WebSearchTool * fix: 修正顺序 * fix: 修复对穷鬼模式的 auto dream 和 session memory 越过 * feat: 穷鬼模式去除 session-summary * feat: 创建 builtin-tools 包,搬运所有工具实现 将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/, 内部导入路径已更新为 src/ alias 模式。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/ - src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/ - 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock - tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射 - 新增 packages/builtin-tools/src 至 include Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀 所有包名及 import 路径统一添加 @claude-code-best/ 前缀: - builtin-tools → @claude-code-best/builtin-tools - mcp-client → @claude-code-best/mcp-client - agent-tools → @claude-code-best/agent-tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复 node 环境没有 bun 的问题 --------- Co-authored-by: Eric-Guo <eric.guocz@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { z } from 'zod/v4'
|
||||
import { getSessionId, setOriginalCwd } from 'src/bootstrap/state.js'
|
||||
import { clearSystemPromptSections } from 'src/constants/systemPromptSections.js'
|
||||
import { logEvent } from 'src/services/analytics/index.js'
|
||||
import type { Tool } from 'src/Tool.js'
|
||||
import { buildTool, type ToolDef } from 'src/Tool.js'
|
||||
import { clearMemoryFileCaches } from 'src/utils/claudemd.js'
|
||||
import { getCwd } from 'src/utils/cwd.js'
|
||||
import { findCanonicalGitRoot } from 'src/utils/git.js'
|
||||
import { lazySchema } from 'src/utils/lazySchema.js'
|
||||
import { getPlanSlug, getPlansDirectory } from 'src/utils/plans.js'
|
||||
import { setCwd } from 'src/utils/Shell.js'
|
||||
import { saveWorktreeState } from 'src/utils/sessionStorage.js'
|
||||
import {
|
||||
createWorktreeForSession,
|
||||
getCurrentWorktreeSession,
|
||||
validateWorktreeSlug,
|
||||
} from 'src/utils/worktree.js'
|
||||
import { ENTER_WORKTREE_TOOL_NAME } from './constants.js'
|
||||
import { getEnterWorktreeToolPrompt } from './prompt.js'
|
||||
import { renderToolResultMessage, renderToolUseMessage } from './UI.js'
|
||||
|
||||
const inputSchema = lazySchema(() =>
|
||||
z.strictObject({
|
||||
name: z
|
||||
.string()
|
||||
.superRefine((s, ctx) => {
|
||||
try {
|
||||
validateWorktreeSlug(s)
|
||||
} catch (e) {
|
||||
ctx.addIssue({ code: 'custom', message: (e as Error).message })
|
||||
}
|
||||
})
|
||||
.optional()
|
||||
.describe(
|
||||
'Optional name for the worktree. Each "/"-separated segment may contain only letters, digits, dots, underscores, and dashes; max 64 chars total. A random name is generated if not provided.',
|
||||
),
|
||||
}),
|
||||
)
|
||||
type InputSchema = ReturnType<typeof inputSchema>
|
||||
|
||||
const outputSchema = lazySchema(() =>
|
||||
z.object({
|
||||
worktreePath: z.string(),
|
||||
worktreeBranch: z.string().optional(),
|
||||
message: z.string(),
|
||||
}),
|
||||
)
|
||||
type OutputSchema = ReturnType<typeof outputSchema>
|
||||
export type Output = z.infer<OutputSchema>
|
||||
|
||||
export const EnterWorktreeTool: Tool<InputSchema, Output> = buildTool({
|
||||
name: ENTER_WORKTREE_TOOL_NAME,
|
||||
searchHint: 'create an isolated git worktree and switch into it',
|
||||
maxResultSizeChars: 100_000,
|
||||
async description() {
|
||||
return 'Creates an isolated worktree (via git or configured hooks) and switches the session into it'
|
||||
},
|
||||
async prompt() {
|
||||
return getEnterWorktreeToolPrompt()
|
||||
},
|
||||
get inputSchema(): InputSchema {
|
||||
return inputSchema()
|
||||
},
|
||||
get outputSchema(): OutputSchema {
|
||||
return outputSchema()
|
||||
},
|
||||
userFacingName() {
|
||||
return 'Creating worktree'
|
||||
},
|
||||
shouldDefer: true,
|
||||
toAutoClassifierInput(input) {
|
||||
return input.name ?? ''
|
||||
},
|
||||
renderToolUseMessage,
|
||||
renderToolResultMessage,
|
||||
async call(input) {
|
||||
// Validate not already in a worktree created by this session
|
||||
if (getCurrentWorktreeSession()) {
|
||||
throw new Error('Already in a worktree session')
|
||||
}
|
||||
|
||||
// Resolve to main repo root so worktree creation works from within a worktree
|
||||
const mainRepoRoot = findCanonicalGitRoot(getCwd())
|
||||
if (mainRepoRoot && mainRepoRoot !== getCwd()) {
|
||||
process.chdir(mainRepoRoot)
|
||||
setCwd(mainRepoRoot)
|
||||
}
|
||||
|
||||
const slug = input.name ?? getPlanSlug()
|
||||
|
||||
const worktreeSession = await createWorktreeForSession(getSessionId(), slug)
|
||||
|
||||
process.chdir(worktreeSession.worktreePath)
|
||||
setCwd(worktreeSession.worktreePath)
|
||||
setOriginalCwd(getCwd())
|
||||
saveWorktreeState(worktreeSession)
|
||||
// Clear cached system prompt sections so env_info_simple recomputes with worktree context
|
||||
clearSystemPromptSections()
|
||||
// Clear memoized caches that depend on CWD
|
||||
clearMemoryFileCaches()
|
||||
getPlansDirectory.cache.clear?.()
|
||||
|
||||
logEvent('tengu_worktree_created', {
|
||||
mid_session: true,
|
||||
})
|
||||
|
||||
const branchInfo = worktreeSession.worktreeBranch
|
||||
? ` on branch ${worktreeSession.worktreeBranch}`
|
||||
: ''
|
||||
|
||||
return {
|
||||
data: {
|
||||
worktreePath: worktreeSession.worktreePath,
|
||||
worktreeBranch: worktreeSession.worktreeBranch,
|
||||
message: `Created worktree at ${worktreeSession.worktreePath}${branchInfo}. The session is now working in the worktree. Use ExitWorktree to leave mid-session, or exit the session to be prompted.`,
|
||||
},
|
||||
}
|
||||
},
|
||||
mapToolResultToToolResultBlockParam({ message }, toolUseID) {
|
||||
return {
|
||||
type: 'tool_result',
|
||||
content: message,
|
||||
tool_use_id: toolUseID,
|
||||
}
|
||||
},
|
||||
} satisfies ToolDef<InputSchema, Output>)
|
||||
25
packages/builtin-tools/src/tools/EnterWorktreeTool/UI.tsx
Normal file
25
packages/builtin-tools/src/tools/EnterWorktreeTool/UI.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from 'react'
|
||||
import { Box, Text } from '@anthropic/ink'
|
||||
import type { ToolProgressData } from 'src/Tool.js'
|
||||
import type { ProgressMessage } from 'src/types/message.js'
|
||||
import type { ThemeName } from 'src/utils/theme.js'
|
||||
import type { Output } from './EnterWorktreeTool.js'
|
||||
|
||||
export function renderToolUseMessage(): React.ReactNode {
|
||||
return 'Creating worktree…'
|
||||
}
|
||||
|
||||
export function renderToolResultMessage(
|
||||
output: Output,
|
||||
_progressMessagesForMessage: ProgressMessage<ToolProgressData>[],
|
||||
_options: { theme: ThemeName },
|
||||
): React.ReactNode {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text>
|
||||
Switched to worktree on branch <Text bold>{output.worktreeBranch}</Text>
|
||||
</Text>
|
||||
<Text dimColor>{output.worktreePath}</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const ENTER_WORKTREE_TOOL_NAME = 'EnterWorktree'
|
||||
30
packages/builtin-tools/src/tools/EnterWorktreeTool/prompt.ts
Normal file
30
packages/builtin-tools/src/tools/EnterWorktreeTool/prompt.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export function getEnterWorktreeToolPrompt(): string {
|
||||
return `Use this tool ONLY when the user explicitly asks to work in a worktree. This tool creates an isolated git worktree and switches the current session into it.
|
||||
|
||||
## When to Use
|
||||
|
||||
- The user explicitly says "worktree" (e.g., "start a worktree", "work in a worktree", "create a worktree", "use a worktree")
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
- The user asks to create a branch, switch branches, or work on a different branch — use git commands instead
|
||||
- The user asks to fix a bug or work on a feature — use normal git workflow unless they specifically mention worktrees
|
||||
- Never use this tool unless the user explicitly mentions "worktree"
|
||||
|
||||
## Requirements
|
||||
|
||||
- Must be in a git repository, OR have WorktreeCreate/WorktreeRemove hooks configured in settings.json
|
||||
- Must not already be in a worktree
|
||||
|
||||
## Behavior
|
||||
|
||||
- In a git repository: creates a new git worktree inside \`.claude/worktrees/\` with a new branch based on HEAD
|
||||
- Outside a git repository: delegates to WorktreeCreate/WorktreeRemove hooks for VCS-agnostic isolation
|
||||
- Switches the session's working directory to the new worktree
|
||||
- Use ExitWorktree to leave the worktree mid-session (keep or remove). On session exit, if still in the worktree, the user will be prompted to keep or remove it
|
||||
|
||||
## Parameters
|
||||
|
||||
- \`name\` (optional): A name for the worktree. If not provided, a random name is generated.
|
||||
`
|
||||
}
|
||||
Reference in New Issue
Block a user