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,157 @@
import { z } from 'zod/v4'
import { setScheduledTasksEnabled } from 'src/bootstrap/state.js'
import type { ValidationResult } from 'src/Tool.js'
import { buildTool, type ToolDef } from 'src/Tool.js'
import { cronToHuman, parseCronExpression } from 'src/utils/cron.js'
import {
addCronTask,
getCronFilePath,
listAllCronTasks,
nextCronRunMs,
} from 'src/utils/cronTasks.js'
import { lazySchema } from 'src/utils/lazySchema.js'
import { semanticBoolean } from 'src/utils/semanticBoolean.js'
import { getTeammateContext } from 'src/utils/teammateContext.js'
import {
buildCronCreateDescription,
buildCronCreatePrompt,
CRON_CREATE_TOOL_NAME,
DEFAULT_MAX_AGE_DAYS,
isDurableCronEnabled,
isKairosCronEnabled,
} from './prompt.js'
import { renderCreateResultMessage, renderCreateToolUseMessage } from './UI.js'
const MAX_JOBS = 50
const inputSchema = lazySchema(() =>
z.strictObject({
cron: z
.string()
.describe(
'Standard 5-field cron expression in local time: "M H DoM Mon DoW" (e.g. "*/5 * * * *" = every 5 minutes, "30 14 28 2 *" = Feb 28 at 2:30pm local once).',
),
prompt: z.string().describe('The prompt to enqueue at each fire time.'),
recurring: semanticBoolean(z.boolean().optional()).describe(
`true (default) = fire on every cron match until deleted or auto-expired after ${DEFAULT_MAX_AGE_DAYS} days. false = fire once at the next match, then auto-delete. Use false for "remind me at X" one-shot requests with pinned minute/hour/dom/month.`,
),
durable: semanticBoolean(z.boolean().optional()).describe(
'true = persist to .claude/scheduled_tasks.json and survive restarts. false (default) = in-memory only, dies when this Claude session ends. Use true only when the user asks the task to survive across sessions.',
),
}),
)
type InputSchema = ReturnType<typeof inputSchema>
const outputSchema = lazySchema(() =>
z.object({
id: z.string(),
humanSchedule: z.string(),
recurring: z.boolean(),
durable: z.boolean().optional(),
}),
)
type OutputSchema = ReturnType<typeof outputSchema>
export type CreateOutput = z.infer<OutputSchema>
export const CronCreateTool = buildTool({
name: CRON_CREATE_TOOL_NAME,
searchHint: 'schedule a recurring or one-shot prompt',
maxResultSizeChars: 100_000,
shouldDefer: true,
get inputSchema(): InputSchema {
return inputSchema()
},
get outputSchema(): OutputSchema {
return outputSchema()
},
isEnabled() {
return isKairosCronEnabled()
},
toAutoClassifierInput(input) {
return `${input.cron}: ${input.prompt}`
},
async description() {
return buildCronCreateDescription(isDurableCronEnabled())
},
async prompt() {
return buildCronCreatePrompt(isDurableCronEnabled())
},
getPath() {
return getCronFilePath()
},
async validateInput(input): Promise<ValidationResult> {
if (!parseCronExpression(input.cron)) {
return {
result: false,
message: `Invalid cron expression '${input.cron}'. Expected 5 fields: M H DoM Mon DoW.`,
errorCode: 1,
}
}
if (nextCronRunMs(input.cron, Date.now()) === null) {
return {
result: false,
message: `Cron expression '${input.cron}' does not match any calendar date in the next year.`,
errorCode: 2,
}
}
const tasks = await listAllCronTasks()
if (tasks.length >= MAX_JOBS) {
return {
result: false,
message: `Too many scheduled jobs (max ${MAX_JOBS}). Cancel one first.`,
errorCode: 3,
}
}
// Teammates don't persist across sessions, so a durable teammate cron
// would orphan on restart (agentId would point to a nonexistent teammate).
if (input.durable && getTeammateContext()) {
return {
result: false,
message:
'durable crons are not supported for teammates (teammates do not persist across sessions)',
errorCode: 4,
}
}
return { result: true }
},
async call({ cron, prompt, recurring = true, durable = false }) {
// Kill switch forces session-only; schema stays stable so the model sees
// no validation errors when the gate flips mid-session.
const effectiveDurable = durable && isDurableCronEnabled()
const id = await addCronTask(
cron,
prompt,
recurring,
effectiveDurable,
getTeammateContext()?.agentId,
)
// Enable the scheduler so the task fires in this session. The
// useScheduledTasks hook polls this flag and will start watching
// on the next tick. For durable: false tasks the file never changes
// — check() reads the session store directly — but the enable flag
// is still what starts the tick loop.
setScheduledTasksEnabled(true)
return {
data: {
id,
humanSchedule: cronToHuman(cron),
recurring,
durable: effectiveDurable,
},
}
},
mapToolResultToToolResultBlockParam(output, toolUseID) {
const where = output.durable
? 'Persisted to .claude/scheduled_tasks.json'
: 'Session-only (not written to disk, dies when Claude exits)'
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: output.recurring
? `Scheduled recurring job ${output.id} (${output.humanSchedule}). ${where}. Auto-expires after ${DEFAULT_MAX_AGE_DAYS} days. Use CronDelete to cancel sooner.`
: `Scheduled one-shot task ${output.id} (${output.humanSchedule}). ${where}. It will fire once then auto-delete.`,
}
},
renderToolUseMessage: renderCreateToolUseMessage,
renderToolResultMessage: renderCreateResultMessage,
} satisfies ToolDef<InputSchema, CreateOutput>)