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:
claude-code-best
2026-04-13 09:52:05 +08:00
committed by GitHub
parent bbb8b613a9
commit 2fb1c9dcd8
559 changed files with 9346 additions and 1837 deletions

View File

@@ -0,0 +1,190 @@
import React from 'react'
import { Text } from '@anthropic/ink'
import { z } from 'zod/v4'
import { TOOL_SUMMARY_MAX_LENGTH } from 'src/constants/toolLimits.js'
import type { ToolResultBlockParam, ToolUseContext, ValidationResult } from 'src/Tool.js'
import { buildTool } from 'src/Tool.js'
import { spawnShellTask } from 'src/tasks/LocalShellTask/LocalShellTask.js'
import { bashToolHasPermission } from '../BashTool/bashPermissions.js'
import type { PermissionResult } from 'src/utils/permissions/PermissionResult.js'
import { lazySchema } from 'src/utils/lazySchema.js'
import { truncate } from 'src/utils/format.js'
import { exec } from 'src/utils/Shell.js'
import { getTaskOutputPath } from 'src/utils/task/diskOutput.js'
import { logEvent } from 'src/services/analytics/index.js'
const MONITOR_TOOL_NAME = 'Monitor'
const inputSchema = lazySchema(() =>
z.strictObject({
command: z
.string()
.describe(
'The shell command to run as a long-running monitor. Should produce streaming output (e.g., tail -f, watch, polling loops).',
),
description: z
.string()
.describe(
'Clear, concise description of what this monitor watches. Used as the label in the background tasks UI.',
),
}),
)
type InputSchema = ReturnType<typeof inputSchema>
export type MonitorInput = z.infer<InputSchema>
const outputSchema = lazySchema(() =>
z.object({
taskId: z.string(),
outputFile: z.string(),
}),
)
type OutputSchema = ReturnType<typeof outputSchema>
export type MonitorOutput = z.infer<OutputSchema>
export const MonitorTool = buildTool({
name: MONITOR_TOOL_NAME,
searchHint: 'start long-running background monitor for streaming events',
maxResultSizeChars: 10_000,
strict: true,
get inputSchema(): InputSchema {
return inputSchema()
},
get outputSchema(): OutputSchema {
return outputSchema()
},
async description() {
return 'Start a long-running background monitor'
},
async prompt() {
return `Use Monitor to start a long-running background process that streams output (watching logs, polling APIs, tailing files, etc.). The command runs in the background and you receive a notification when it exits. Use the Read tool with the output file path to check its output at any time.
Guidelines:
- Use Monitor for commands that produce ongoing streaming output: \`tail -f\`, log watchers, file watchers, API polling loops, \`watch\` commands
- Do NOT use Monitor for one-shot commands that finish quickly — use Bash for those
- Do NOT use Monitor for commands that need interactive input — they will hang
- The description should clearly explain what is being monitored
- You'll get a task notification when the monitor process exits (stream ends, script fails, or killed)
- To check output at any time, use Read on the output file path returned by this tool
Examples:
- Watching a log file: command="tail -f /var/log/app.log", description="Watch app log for errors"
- Polling an API: command="while true; do curl -s http://localhost:3000/health; sleep 5; done", description="Poll health endpoint every 5s"
- Watching for file changes: command="inotifywait -m -r ./src", description="Watch src directory for file changes"`
},
isConcurrencySafe() {
return true
},
isReadOnly() {
// Monitor executes shell commands which may have side effects
return false
},
toAutoClassifierInput(input: MonitorInput) {
return `Monitor: ${input.command}`
},
async checkPermissions(
input: MonitorInput,
context: ToolUseContext,
): Promise<PermissionResult> {
// Reuse bash permission checking for the underlying command
return bashToolHasPermission(
{ command: input.command },
context,
)
},
userFacingName() {
return MONITOR_TOOL_NAME
},
getActivityDescription(input: MonitorInput) {
if (!input?.description) {
return 'Starting monitor'
}
return `Monitoring: ${truncate(input.description, TOOL_SUMMARY_MAX_LENGTH)}`
},
async validateInput(input: MonitorInput): Promise<ValidationResult> {
if (!input.command || input.command.trim() === '') {
return {
result: false,
message: 'Monitor command cannot be empty.',
errorCode: 1,
}
}
if (!input.description || input.description.trim() === '') {
return {
result: false,
message: 'Monitor description cannot be empty.',
errorCode: 2,
}
}
return { result: true }
},
async call(input: MonitorInput, context: ToolUseContext) {
const { command, description } = input
const {
abortController,
setAppState,
toolUseId,
agentId,
} = context
logEvent('tengu_monitor_tool_used', {})
// Create the shell command via exec
const shellCommand = await exec(command, abortController.signal, 'bash')
// Spawn as a background task with kind: 'monitor'
const handle = await spawnShellTask(
{
command,
description,
shellCommand,
toolUseId: toolUseId,
agentId,
kind: 'monitor',
},
{
abortController,
getAppState: context.getAppState,
setAppState,
},
)
const outputFile = getTaskOutputPath(handle.taskId)
return {
data: {
taskId: handle.taskId,
outputFile,
},
}
},
renderToolUseMessage(input: MonitorInput, { verbose }) {
const desc = truncate(input.description || input.command, 80)
return `Monitor: ${desc}`
},
mapToolResultToToolResultBlockParam(
content: MonitorOutput,
toolUseId: string,
): ToolResultBlockParam {
return {
tool_use_id: toolUseId,
type: 'tool_result',
content: `Monitor started (task ${content.taskId}). Output file: ${content.outputFile}`,
}
},
renderToolResultMessage(output: MonitorOutput) {
return <Text>Monitor started (task {output.taskId}). Output: {output.outputFile}</Text>
},
})