源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import type {
|
||||
AgentTurnSummaryPatchBatchVM,
|
||||
AgentTurnSummaryPatchChangeVM
|
||||
} from "../contracts/agentTurnSummaryRowVM";
|
||||
import {
|
||||
isInsideOrEqualWorkspaceFilePath,
|
||||
normalizeWorkspaceFilePath
|
||||
} from "../../../actions/workspaceFilePathCandidate";
|
||||
import { normalizeAgentPatchText } from "./agentPatchMetadata";
|
||||
import { isAgentUnifiedDiffText } from "./agentUnifiedDiffValidation";
|
||||
|
||||
export function buildAgentTurnSummaryPatchDiff(
|
||||
batch: AgentTurnSummaryPatchBatchVM
|
||||
): string {
|
||||
return batch.changes
|
||||
.map((change) => patchChangeToUnifiedDiff(change, batch.cwd))
|
||||
.filter((diff) => diff.trim().length > 0)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function patchChangeToUnifiedDiff(
|
||||
change: AgentTurnSummaryPatchChangeVM,
|
||||
cwd: string | null
|
||||
): string {
|
||||
if (patchChangeContainsBinaryContent(change)) {
|
||||
return "";
|
||||
}
|
||||
const path = patchPathRelativeToCwd(change.path, cwd);
|
||||
const rawDiff = normalizeAgentPatchText(change.unifiedDiff ?? "").trim();
|
||||
if (rawDiff && isAgentUnifiedDiffText(rawDiff)) {
|
||||
return ensureTrailingNewline(
|
||||
wrapUnifiedDiff(path, change.changeType, rawDiff, cwd)
|
||||
);
|
||||
}
|
||||
if (change.changeType === "created") {
|
||||
return fileContentPatch(
|
||||
path,
|
||||
"created",
|
||||
change.content ?? change.newString ?? rawDiff
|
||||
);
|
||||
}
|
||||
if (change.changeType === "deleted") {
|
||||
return fileContentPatch(
|
||||
path,
|
||||
"deleted",
|
||||
change.oldString ?? change.content ?? rawDiff
|
||||
);
|
||||
}
|
||||
if (change.oldString != null && change.newString != null) {
|
||||
return modifiedFilePatch(path, change.oldString, change.newString);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function wrapUnifiedDiff(
|
||||
path: string,
|
||||
changeType: AgentTurnSummaryPatchChangeVM["changeType"],
|
||||
diff: string,
|
||||
cwd: string | null
|
||||
): string {
|
||||
if (diff.startsWith("diff --git ")) {
|
||||
return rebaseUnifiedDiffPaths(diff, cwd);
|
||||
}
|
||||
const headers = [`diff --git a/${path} b/${path}`];
|
||||
if (changeType === "created") {
|
||||
headers.push("new file mode 100644", "--- /dev/null", `+++ b/${path}`);
|
||||
} else if (changeType === "deleted") {
|
||||
headers.push("deleted file mode 100644", `--- a/${path}`, "+++ /dev/null");
|
||||
} else if (!diff.startsWith("--- ") && !diff.includes("\n--- ")) {
|
||||
headers.push(`--- a/${path}`, `+++ b/${path}`);
|
||||
}
|
||||
return [...headers, diff].join("\n");
|
||||
}
|
||||
|
||||
function rebaseUnifiedDiffPaths(diff: string, cwd: string | null): string {
|
||||
let inHunk = false;
|
||||
return diff
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
if (line.startsWith("diff --git ")) {
|
||||
inHunk = false;
|
||||
} else if (line.startsWith("@@ ")) {
|
||||
inHunk = true;
|
||||
return line;
|
||||
} else if (inHunk) {
|
||||
return line;
|
||||
}
|
||||
|
||||
const gitHeader = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
|
||||
if (gitHeader?.[1] && gitHeader[2]) {
|
||||
return `diff --git a/${patchPathRelativeToCwd(
|
||||
gitHeader[1],
|
||||
cwd
|
||||
)} b/${patchPathRelativeToCwd(gitHeader[2], cwd)}`;
|
||||
}
|
||||
|
||||
const fileHeader = line.match(/^(---|\+\+\+) ((?:a\/|b\/)?)(.+)$/);
|
||||
const [, marker, prefix, filePath] = fileHeader ?? [];
|
||||
if (
|
||||
!marker ||
|
||||
prefix === undefined ||
|
||||
!filePath ||
|
||||
filePath === "/dev/null"
|
||||
) {
|
||||
return line;
|
||||
}
|
||||
return `${marker} ${prefix}${patchPathRelativeToCwd(filePath, cwd)}`;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function fileContentPatch(
|
||||
path: string,
|
||||
changeType: "created" | "deleted",
|
||||
content: string
|
||||
): string {
|
||||
if (content === "") {
|
||||
return ensureTrailingNewline(
|
||||
changeType === "created"
|
||||
? [
|
||||
`diff --git a/${path} b/${path}`,
|
||||
"new file mode 100644",
|
||||
"index 0000000..e69de29"
|
||||
].join("\n")
|
||||
: [
|
||||
`diff --git a/${path} b/${path}`,
|
||||
"deleted file mode 100644",
|
||||
"index e69de29..0000000"
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
const lines = splitPatchContentLines(content);
|
||||
const count = Math.max(lines.length, 1);
|
||||
const prefix = changeType === "created" ? "+" : "-";
|
||||
const body =
|
||||
lines.length > 0 ? lines.map((line) => `${prefix}${line}`) : [`${prefix}`];
|
||||
const header =
|
||||
changeType === "created"
|
||||
? [
|
||||
`diff --git a/${path} b/${path}`,
|
||||
"new file mode 100644",
|
||||
"--- /dev/null",
|
||||
`+++ b/${path}`,
|
||||
`@@ -0,0 +1,${count} @@`
|
||||
]
|
||||
: [
|
||||
`diff --git a/${path} b/${path}`,
|
||||
"deleted file mode 100644",
|
||||
`--- a/${path}`,
|
||||
"+++ /dev/null",
|
||||
`@@ -1,${count} +0,0 @@`
|
||||
];
|
||||
return ensureTrailingNewline([...header, ...body].join("\n"));
|
||||
}
|
||||
|
||||
function modifiedFilePatch(
|
||||
path: string,
|
||||
oldContent: string,
|
||||
newContent: string
|
||||
): string {
|
||||
const oldLines = splitPatchContentLines(oldContent);
|
||||
const newLines = splitPatchContentLines(newContent);
|
||||
const oldCount = oldLines.length;
|
||||
const newCount = newLines.length;
|
||||
const oldStart = oldCount === 0 ? 0 : 1;
|
||||
const newStart = newCount === 0 ? 0 : 1;
|
||||
return ensureTrailingNewline(
|
||||
[
|
||||
`diff --git a/${path} b/${path}`,
|
||||
`--- a/${path}`,
|
||||
`+++ b/${path}`,
|
||||
`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`,
|
||||
...oldLines.map((line) => `-${line}`),
|
||||
...newLines.map((line) => `+${line}`)
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
function patchChangeContainsBinaryContent(
|
||||
change: AgentTurnSummaryPatchChangeVM
|
||||
): boolean {
|
||||
return [
|
||||
change.unifiedDiff,
|
||||
change.content,
|
||||
change.oldString,
|
||||
change.newString
|
||||
].some((value) => value?.includes("\0") === true);
|
||||
}
|
||||
|
||||
function patchPathRelativeToCwd(path: string, cwd: string | null): string {
|
||||
const normalizedCwd = normalizeWorkspaceFilePath(cwd ?? "");
|
||||
const normalizedPath = normalizeWorkspaceFilePath(path, normalizedCwd);
|
||||
if (
|
||||
isAbsolutePatchPath(normalizedPath) &&
|
||||
normalizedCwd &&
|
||||
isInsideOrEqualWorkspaceFilePath(normalizedPath, normalizedCwd)
|
||||
) {
|
||||
const relativePath = normalizedPath
|
||||
.slice(normalizedCwd.length)
|
||||
.replace(/^\/+/, "");
|
||||
if (relativePath) {
|
||||
return relativePath;
|
||||
}
|
||||
}
|
||||
return normalizedPath.replace(/^\/+/, "");
|
||||
}
|
||||
|
||||
function isAbsolutePatchPath(path: string): boolean {
|
||||
return path.startsWith("/") || /^\/?[A-Za-z]:\//.test(path);
|
||||
}
|
||||
|
||||
function splitPatchContentLines(content: string): string[] {
|
||||
if (!content) {
|
||||
return [];
|
||||
}
|
||||
return content.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n");
|
||||
}
|
||||
|
||||
function ensureTrailingNewline(value: string): string {
|
||||
return value.endsWith("\n") ? value : `${value}\n`;
|
||||
}
|
||||
Reference in New Issue
Block a user