import { createHash } from "node:crypto"; import { realpathSync, statSync } from "node:fs"; import { spawnSync } from "node:child_process"; export interface AgentRunTargetTaskRequest { readonly target: string; readonly targetWorkspace: string; readonly repository: string; readonly ref: string; readonly mdtodoId: string; } export interface AgentRunTargetTaskPreflight extends AgentRunTargetTaskRequest { readonly targetRoute: string; readonly projectId: string; readonly workspaceRoot: string; readonly currentBranch: string; readonly headCommit: string; readonly clean: true; readonly originRepository: string; readonly verifiedCommit: string; readonly matchedRemoteRef: string; readonly refRelation: "branch-and-head-match-remote-ref"; readonly sourceAuthority: { readonly kind: "target-workspace-origin"; readonly credentialScope: "target-owned-git-credential"; readonly valuesPrinted: false; }; } export class AgentRunTargetTaskPreflightError extends Error { readonly stage: string; readonly details: Record; constructor(stage: string, message: string, details: Record = {}) { super(message); this.name = "AgentRunTargetTaskPreflightError"; this.stage = stage; this.details = { ...details, mutation: false, valuesPrinted: false }; } } export function targetTaskRequestFromArgs(args: string[]): AgentRunTargetTaskRequest | null { const raw = { target: option(args, "target"), targetWorkspace: option(args, "target-workspace"), repository: option(args, "repo"), ref: option(args, "ref"), mdtodoId: option(args, "mdtodo-id"), }; const targetMode = raw.targetWorkspace !== null || raw.repository !== null || raw.ref !== null || raw.mdtodoId !== null; if (!targetMode) return null; const missing = Object.entries(raw).filter(([, value]) => value === null).map(([key]) => key); if (missing.length > 0) { throw new AgentRunTargetTaskPreflightError( "target-context", `Artificer Target 派单缺少 ${missing.join(", ")};请同时提供 --target、--target-workspace、--repo、--ref 和 --mdtodo-id。`, { missing }, ); } return { target: validateTarget(raw.target as string), targetWorkspace: validateTargetWorkspace(raw.targetWorkspace as string), repository: normalizeRepository(raw.repository as string), ref: validateRef(raw.ref as string), mdtodoId: validateMdtodoId(raw.mdtodoId as string), }; } export function preflightTargetTask(request: AgentRunTargetTaskRequest, markerEnv: string): AgentRunTargetTaskPreflight { if (process.env[markerEnv] !== request.target) { throw new AgentRunTargetTaskPreflightError( "target-execution", `Target 预检只能在外层 trans 重入 ${request.target} 后执行;当前缺少受控执行标记 ${markerEnv}。`, { target: request.target, markerEnv, targetRoute: `${request.target}:${request.targetWorkspace}` }, ); } let workspaceRoot: string; try { if (!statSync(request.targetWorkspace).isDirectory()) throw new Error("not a directory"); workspaceRoot = realpathSync(request.targetWorkspace); } catch { throw new AgentRunTargetTaskPreflightError( "workspace", `Target 工作区不存在或不是目录:${request.target}:${request.targetWorkspace}`, { target: request.target, targetWorkspace: request.targetWorkspace }, ); } const gitRoot = git(request, ["rev-parse", "--show-toplevel"], "workspace-git-root").trim(); let observedRoot: string; try { observedRoot = realpathSync(gitRoot); } catch { throw new AgentRunTargetTaskPreflightError( "workspace-git-root", `Target Git 返回了无效工作区根目录:${gitRoot}`, { targetWorkspace: request.targetWorkspace }, ); } if (observedRoot !== workspaceRoot) { throw new AgentRunTargetTaskPreflightError( "workspace-git-root", `--target-workspace 必须指向 Git 工作区根目录;当前根目录是 ${gitRoot}`, { targetWorkspace: request.targetWorkspace, observedWorkspaceRoot: gitRoot }, ); } const originUrl = git(request, ["remote", "get-url", "origin"], "origin").trim(); const originRepository = repositoryFromRemote(originUrl); if (originRepository === null || originRepository.toLowerCase() !== request.repository.toLowerCase()) { throw new AgentRunTargetTaskPreflightError( "origin", `Target 工作区 origin 与 --repo 不一致;期望 ${request.repository}。`, { expectedRepository: request.repository, observedRepository: originRepository, observedRemoteFingerprint: sha256(originUrl), }, ); } const status = git(request, ["status", "--porcelain=v1", "--untracked-files=normal"], "workspace-status"); const dirtyEntryCount = status.split(/\r?\n/u).filter(Boolean).length; if (dirtyEntryCount > 0) { throw new AgentRunTargetTaskPreflightError( "workspace-status", "Target 固定工作区存在未提交修改;请先保存并按语义提交并行改动,再重新派单。", { targetWorkspace: request.targetWorkspace, dirtyEntryCount }, ); } const currentBranch = git(request, ["branch", "--show-current"], "workspace-branch").trim() || "(detached)"; const headCommit = git(request, ["rev-parse", "HEAD"], "workspace-head").trim().toLowerCase(); const remote = git(request, ["ls-remote", "--exit-code", "origin", request.ref], "remote-ref"); const refs = remote.split(/\r?\n/u) .map((line) => line.trim().split(/\s+/u)) .filter((parts) => /^[0-9a-f]{40}$/u.test(parts[0] ?? "") && (parts[1] ?? "").length > 0) .map((parts) => ({ commit: parts[0] as string, ref: parts[1] as string })); const selected = refs.find((item) => item.ref === `refs/heads/${request.ref}`) ?? refs[0]; if (selected === undefined) { throw new AgentRunTargetTaskPreflightError( "remote-ref", `Target 工作区 origin 中找不到 --ref ${request.ref}。`, { repository: request.repository, ref: request.ref }, ); } if (currentBranch !== request.ref || headCommit !== selected.commit.toLowerCase()) { throw new AgentRunTargetTaskPreflightError( "workspace-ref-relation", "Target 任务 worktree 必须检出 --ref,且 HEAD 必须等于 origin 远端提交;请先完成分支切换、提交和推送。", { targetWorkspace: request.targetWorkspace, currentBranch, expectedBranch: request.ref, headCommit, remoteCommit: selected.commit, matchedRemoteRef: selected.ref, }, ); } return { ...request, targetRoute: `${request.target}:${request.targetWorkspace}`, projectId: request.repository, workspaceRoot, currentBranch, headCommit, clean: true, originRepository, verifiedCommit: selected.commit, matchedRemoteRef: selected.ref, refRelation: "branch-and-head-match-remote-ref", sourceAuthority: { kind: "target-workspace-origin", credentialScope: "target-owned-git-credential", valuesPrinted: false, }, }; } export function targetTaskPrompt(preflight: AgentRunTargetTaskPreflight, userPrompt: string | null): string { const context = [ "任务目标上下文(由 UniDesk YAML-first CLI 在创建 task 前经 Target trans 只读核验):", `- MDTODO ID: ${preflight.mdtodoId}`, `- Target: ${preflight.target}`, `- targetWorkspace: ${preflight.targetWorkspace}`, `- targetRoute: ${preflight.targetRoute}`, `- repository: ${preflight.repository}`, `- ref: ${preflight.ref}`, `- verifiedCommit: ${preflight.verifiedCommit}`, `- refRelation: ${preflight.refRelation}`, "- sourceAuthority: target-workspace-origin(凭据值不进入 task、prompt 或日志)", "", "执行边界:", `- 目标源码的探测、Git、编辑、验证和运行面观察全部使用 \`trans ${preflight.target}:${preflight.targetWorkspace} \` 或对应 k3s route。`, "- runner primary workspace 只承载 Artificer 默认受控 resource bundle、工具与 skill;不得把它当作目标源码,也不得用容器本地结果替代 Target 原入口证据。", `- 最终报告必须回链 MDTODO ${preflight.mdtodoId}。`, ].join("\n"); return userPrompt === null || userPrompt.trim().length === 0 ? context : `${context}\n\n${userPrompt}`; } export function targetTaskDisclosure(preflight: AgentRunTargetTaskPreflight): Record { return { mdtodoId: preflight.mdtodoId, target: preflight.target, targetWorkspace: preflight.targetWorkspace, targetRoute: preflight.targetRoute, repository: preflight.repository, ref: preflight.ref, projectId: preflight.projectId, verifiedCommit: preflight.verifiedCommit, matchedRemoteRef: preflight.matchedRemoteRef, refRelation: preflight.refRelation, workspace: { root: preflight.workspaceRoot, branch: preflight.currentBranch, headCommit: preflight.headCommit, clean: preflight.clean, }, sourceAuthority: preflight.sourceAuthority, mutation: false, valuesPrinted: false, }; } export function targetTaskIdempotencyKey(preflight: AgentRunTargetTaskPreflight): string { const identity = [ preflight.target, preflight.targetWorkspace, preflight.repository, preflight.ref, preflight.mdtodoId, preflight.verifiedCommit, ].join("\n"); return `artificer-target:${createHash("sha256").update(identity).digest("hex")}`; } function git(request: AgentRunTargetTaskRequest, args: string[], stage: string): string { const result = spawnSync("git", args, { cwd: request.targetWorkspace, encoding: "utf8", timeout: 15_000, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }, maxBuffer: 1024 * 1024, }); if (result.error !== undefined || result.status !== 0) { const stderr = String(result.stderr ?? "").trim().split(/\r?\n/u)[0] ?? ""; throw new AgentRunTargetTaskPreflightError( stage, `Target Git 只读预检失败(${stage});请先在 ${request.target}:${request.targetWorkspace} 修复 origin/ref/凭据可达性。`, { target: request.target, targetWorkspace: request.targetWorkspace, repository: request.repository, ref: request.ref, exitCode: result.status, timedOut: result.error?.name === "ETIMEDOUT", stderrSummary: redactGitError(stderr), }, ); } return String(result.stdout ?? ""); } function option(args: string[], name: string): string | null { const flag = `--${name}`; for (let index = 0; index < args.length; index += 1) { const arg = args[index] ?? ""; if (arg === flag) { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) { throw new AgentRunTargetTaskPreflightError("target-context", `${flag} requires a value`); } return value; } if (arg.startsWith(`${flag}=`)) return arg.slice(flag.length + 1); } return null; } function validateTarget(value: string): string { if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(value)) { throw new AgentRunTargetTaskPreflightError("target-context", "--target 必须是 YAML 声明的 Target ID"); } return value; } function validateTargetWorkspace(value: string): string { if (!value.startsWith("/") || value.includes("\0")) { throw new AgentRunTargetTaskPreflightError("target-context", "--target-workspace 必须是 Target 上的绝对路径"); } return value.replace(/\/+$/u, "") || "/"; } function normalizeRepository(value: string): string { const normalized = value .replace(/^https:\/\/github\.com\//u, "") .replace(/^git@github\.com:/u, "") .replace(/\.git$/u, ""); if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized)) { throw new AgentRunTargetTaskPreflightError("target-context", "--repo 必须使用 owner/repo 或等价 GitHub URL"); } return normalized; } function validateRef(value: string): string { const disallowed = /[\u0000-\u0020~^:?*\\\[]/u; if (value.length === 0 || value.length > 255 || value.startsWith("-") || disallowed.test(value) || value.includes("..") || value.endsWith(".") || value.endsWith("/") || value.includes("//")) { throw new AgentRunTargetTaskPreflightError("target-context", "--ref 不是安全的 Git ref"); } return value; } function validateMdtodoId(value: string): string { if (!/^R[1-9][0-9]*(?:\.[1-9][0-9]*)*$/u.test(value)) { throw new AgentRunTargetTaskPreflightError("target-context", "--mdtodo-id 必须使用 R1 或 R1.1 这类 MDTODO ID"); } return value; } function repositoryFromRemote(value: string): string | null { const normalized = value.replace(/\.git$/u, ""); const match = normalized.match(/^(?:git@github\.com:|https:\/\/github\.com\/)([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/u); return match?.[1] ?? null; } function redactGitError(value: string): string { return value .replace(/https?:\/\/[^@\s]+@/gu, "https://[redacted]@") .replace(/(token|password|authorization)[=:][^\s]+/giu, "$1=[redacted]") .slice(0, 240); } function sha256(value: string): string { return `sha256:${createHash("sha256").update(value).digest("hex")}`; }