diff --git a/src/claude/executor.ts b/src/claude/executor.ts index 5031929e..a49d76e8 100644 --- a/src/claude/executor.ts +++ b/src/claude/executor.ts @@ -71,14 +71,15 @@ function buildWorkspaceSystemPrompt(): string { - mode="writable": 需要修改代码、提交变更时使用。会创建隔离工作区和 feature 分支。 **如何使用:** -- 远程仓库: 使用 repo_url 参数传入仓库 URL -- 本地仓库: 使用 local_path 参数传入仓库绝对路径 +- **优先使用 repo_url**: 当用户提供了仓库 URL(或你能从上下文推断出 URL),始终用 repo_url 参数。这会自动走缓存 + 隔离工作区流程 +- local_path 仅用于项目目录(\`${projectsDir}\`)下已有的仓库,且路径必须在该目录范围内 - 根据意图选择 mode (readonly 或 writable) - 可选指定 source_branch (源分支) 和 feature_branch (自定义分支名, 仅 writable) **无需使用的场景:** - 当前工作目录已经是目标仓库 - 用户只是查看/分析代码,且仓库已在项目目录中(直接 cd 或用绝对路径读取即可) +- 注意:如果用户要**修改**项目目录中已有的仓库代码,应使用 repo_url 创建隔离工作区,不要直接在原始仓库上改 **重要:调用 setup_workspace 后,系统将自动重启以加载项目配置(CLAUDE.md 等)。 请在调用后仅输出简短确认(如"工作区已就绪,正在重新加载项目配置..."),不要继续执行后续任务。** @@ -184,7 +185,8 @@ export class ClaudeExecutor { permissionMode: 'acceptEdits', canUseTool: async (toolName: string, inputObj: Record) => { logger.info({ toolName, inputKeys: Object.keys(inputObj) }, 'canUseTool called — auto allowing'); - return { behavior: 'allow' as const }; + // updatedInput 必须显式传回,否则 SDK 内部 Zod 校验会因 undefined 报错 + return { behavior: 'allow' as const, updatedInput: inputObj }; }, // 预算和限制 diff --git a/src/workspace/manager.ts b/src/workspace/manager.ts index 7bf9b3ab..00a3b950 100644 --- a/src/workspace/manager.ts +++ b/src/workspace/manager.ts @@ -42,6 +42,20 @@ export interface SetupWorkspaceResult { const SAFE_BRANCH_RE = /^[a-zA-Z0-9._\/-]+$/; /** git 远程 URL 协议前缀 */ const GIT_URL_RE = /^(https?:\/\/|git@|ssh:\/\/|git:\/\/)/; +/** SSH 简写格式: github.com:user/repo.git (缺少 git@ 前缀) */ +const SSH_SHORTHAND_RE = /^[\w.-]+\.\w{2,}:[\w./-]+$/; + +/** + * 归一化仓库 URL + * - SSH 简写 (github.com:user/repo) → git@github.com:user/repo + * - 其他格式原样返回 + */ +function normalizeRepoUrl(url: string): string { + if (!GIT_URL_RE.test(url) && SSH_SHORTHAND_RE.test(url)) { + return `git@${url}`; + } + return url; +} /** @@ -65,7 +79,9 @@ export function deriveRepoName(source: string): string { * 直接从本地路径 clone (不经过缓存层) */ export function setupWorkspace(options: SetupWorkspaceOptions): SetupWorkspaceResult { - const { repoUrl, localPath, mode = 'writable', sourceBranch, featureBranch } = options; + const { localPath, mode = 'writable', sourceBranch, featureBranch } = options; + // 归一化 URL: github.com:user/repo → git@github.com:user/repo + const repoUrl = options.repoUrl ? normalizeRepoUrl(options.repoUrl) : undefined; const source = repoUrl || localPath; if (!source) { diff --git a/src/workspace/tool.ts b/src/workspace/tool.ts index f483e5fd..198530aa 100644 --- a/src/workspace/tool.ts +++ b/src/workspace/tool.ts @@ -50,6 +50,7 @@ export function createWorkspaceMcpServer(onWorkspaceChanged?: SessionUpdater) { feature_branch: z.string().optional().describe('自定义 feature 分支名 (默认自动生成, 仅 writable 模式)'), }, async (args) => { + logger.info({ args: { repo_url: args.repo_url, local_path: args.local_path, mode: args.mode } }, 'setup_workspace tool invoked'); try { const result = setupWorkspace({ repoUrl: args.repo_url,