viveworker 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/viveworker-bridge.mjs +815 -109
- package/web/app.css +1 -0
- package/web/app.js +101 -23
- package/web/i18n.js +32 -18
|
@@ -283,7 +283,7 @@ function normalizeTimelineOutcome(value) {
|
|
|
283
283
|
|
|
284
284
|
function normalizeTimelineFileEventType(value) {
|
|
285
285
|
const normalized = cleanText(value || "").toLowerCase();
|
|
286
|
-
return ["read", "write", "create"].includes(normalized) ? normalized : "";
|
|
286
|
+
return ["read", "write", "create", "delete", "rename"].includes(normalized) ? normalized : "";
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
function fileEventTitle(locale, fileEventType) {
|
|
@@ -294,6 +294,10 @@ function fileEventTitle(locale, fileEventType) {
|
|
|
294
294
|
return t(locale, "fileEvent.write");
|
|
295
295
|
case "create":
|
|
296
296
|
return t(locale, "fileEvent.create");
|
|
297
|
+
case "delete":
|
|
298
|
+
return t(locale, "fileEvent.delete");
|
|
299
|
+
case "rename":
|
|
300
|
+
return t(locale, "fileEvent.rename");
|
|
297
301
|
default:
|
|
298
302
|
return t(locale, "common.fileEvent");
|
|
299
303
|
}
|
|
@@ -307,6 +311,10 @@ function fileEventDetailCopy(locale, fileEventType) {
|
|
|
307
311
|
return t(locale, "detail.fileEvent.write");
|
|
308
312
|
case "create":
|
|
309
313
|
return t(locale, "detail.fileEvent.create");
|
|
314
|
+
case "delete":
|
|
315
|
+
return t(locale, "detail.fileEvent.delete");
|
|
316
|
+
case "rename":
|
|
317
|
+
return t(locale, "detail.fileEvent.rename");
|
|
310
318
|
default:
|
|
311
319
|
return t(locale, "detail.detailUnavailable");
|
|
312
320
|
}
|
|
@@ -618,16 +626,82 @@ function extractReadFileRefsFromCommand(commandText) {
|
|
|
618
626
|
return [];
|
|
619
627
|
}
|
|
620
628
|
|
|
621
|
-
function extractUpdatedFileRefsByType(outputText) {
|
|
629
|
+
function extractUpdatedFileRefsByType(outputText, patchText = "") {
|
|
630
|
+
const parsedSections = parseApplyPatchSections(patchText);
|
|
631
|
+
if (parsedSections.length > 0) {
|
|
632
|
+
const createRefs = [];
|
|
633
|
+
const writeRefs = [];
|
|
634
|
+
const deleteRefs = [];
|
|
635
|
+
const renameRefs = [];
|
|
636
|
+
for (const section of parsedSections) {
|
|
637
|
+
const newFileRef = cleanTimelineFileRef(section?.newFileRef || section?.fileRef || "");
|
|
638
|
+
const oldFileRef = cleanTimelineFileRef(section?.oldFileRef || "");
|
|
639
|
+
switch (section?.kind) {
|
|
640
|
+
case "create":
|
|
641
|
+
if (newFileRef) {
|
|
642
|
+
createRefs.push(newFileRef);
|
|
643
|
+
}
|
|
644
|
+
break;
|
|
645
|
+
case "write":
|
|
646
|
+
if (newFileRef) {
|
|
647
|
+
writeRefs.push(newFileRef);
|
|
648
|
+
}
|
|
649
|
+
break;
|
|
650
|
+
case "delete":
|
|
651
|
+
if (oldFileRef || newFileRef) {
|
|
652
|
+
deleteRefs.push(oldFileRef || newFileRef);
|
|
653
|
+
}
|
|
654
|
+
break;
|
|
655
|
+
case "rename":
|
|
656
|
+
if (oldFileRef && newFileRef) {
|
|
657
|
+
renameRefs.push({
|
|
658
|
+
oldFileRef,
|
|
659
|
+
newFileRef,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
break;
|
|
663
|
+
default:
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
create: normalizeTimelineFileRefs(createRefs),
|
|
669
|
+
write: normalizeTimelineFileRefs(writeRefs),
|
|
670
|
+
delete: normalizeTimelineFileRefs(deleteRefs),
|
|
671
|
+
rename: renameRefs.filter(
|
|
672
|
+
(entry, index, array) =>
|
|
673
|
+
array.findIndex(
|
|
674
|
+
(candidate) =>
|
|
675
|
+
timelineFileRefsMatch(candidate.oldFileRef, entry.oldFileRef) &&
|
|
676
|
+
timelineFileRefsMatch(candidate.newFileRef, entry.newFileRef)
|
|
677
|
+
) === index
|
|
678
|
+
),
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
622
682
|
const parsed = safeJsonParse(outputText);
|
|
623
683
|
const sourceText = typeof parsed?.output === "string" ? parsed.output : String(outputText || "");
|
|
624
684
|
if (!/^Success\. Updated the following files:/mu.test(sourceText)) {
|
|
625
|
-
return { create: [], write: [] };
|
|
685
|
+
return { create: [], write: [], delete: [], rename: [] };
|
|
626
686
|
}
|
|
627
687
|
|
|
628
688
|
const createRefs = [];
|
|
629
689
|
const writeRefs = [];
|
|
690
|
+
const deleteRefs = [];
|
|
691
|
+
const renameRefs = [];
|
|
630
692
|
for (const line of sourceText.split("\n")) {
|
|
693
|
+
const renameMatch =
|
|
694
|
+
line.match(/^R\d*\s+(.+?)\s+->\s+(.+)$/u) ||
|
|
695
|
+
line.match(/^R\d*\s+(.+?)\t(.+)$/u);
|
|
696
|
+
if (renameMatch) {
|
|
697
|
+
const oldFileRef = cleanTimelineFileRef(renameMatch[1]);
|
|
698
|
+
const newFileRef = cleanTimelineFileRef(renameMatch[2]);
|
|
699
|
+
if (oldFileRef && newFileRef) {
|
|
700
|
+
renameRefs.push({ oldFileRef, newFileRef });
|
|
701
|
+
}
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
|
|
631
705
|
const match = line.match(/^([AMD])\s+(.+)$/u);
|
|
632
706
|
if (!match) {
|
|
633
707
|
continue;
|
|
@@ -640,12 +714,23 @@ function extractUpdatedFileRefsByType(outputText) {
|
|
|
640
714
|
createRefs.push(fileRef);
|
|
641
715
|
} else if (match[1] === "M") {
|
|
642
716
|
writeRefs.push(fileRef);
|
|
717
|
+
} else if (match[1] === "D") {
|
|
718
|
+
deleteRefs.push(fileRef);
|
|
643
719
|
}
|
|
644
720
|
}
|
|
645
721
|
|
|
646
722
|
return {
|
|
647
723
|
create: normalizeTimelineFileRefs(createRefs),
|
|
648
724
|
write: normalizeTimelineFileRefs(writeRefs),
|
|
725
|
+
delete: normalizeTimelineFileRefs(deleteRefs),
|
|
726
|
+
rename: renameRefs.filter(
|
|
727
|
+
(entry, index, array) =>
|
|
728
|
+
array.findIndex(
|
|
729
|
+
(candidate) =>
|
|
730
|
+
timelineFileRefsMatch(candidate.oldFileRef, entry.oldFileRef) &&
|
|
731
|
+
timelineFileRefsMatch(candidate.newFileRef, entry.newFileRef)
|
|
732
|
+
) === index
|
|
733
|
+
),
|
|
649
734
|
};
|
|
650
735
|
}
|
|
651
736
|
|
|
@@ -804,6 +889,8 @@ function parseApplyPatchSections(patchText) {
|
|
|
804
889
|
sections.push({
|
|
805
890
|
kind: current.kind,
|
|
806
891
|
fileRef: current.fileRef,
|
|
892
|
+
oldFileRef: current.oldFileRef || "",
|
|
893
|
+
newFileRef: current.newFileRef || current.fileRef,
|
|
807
894
|
bodyLines: [...current.bodyLines],
|
|
808
895
|
});
|
|
809
896
|
current = null;
|
|
@@ -816,6 +903,8 @@ function parseApplyPatchSections(patchText) {
|
|
|
816
903
|
current = {
|
|
817
904
|
kind: "create",
|
|
818
905
|
fileRef: cleanTimelineFileRef(addMatch[1]),
|
|
906
|
+
oldFileRef: "",
|
|
907
|
+
newFileRef: cleanTimelineFileRef(addMatch[1]),
|
|
819
908
|
bodyLines: [],
|
|
820
909
|
};
|
|
821
910
|
continue;
|
|
@@ -827,6 +916,8 @@ function parseApplyPatchSections(patchText) {
|
|
|
827
916
|
current = {
|
|
828
917
|
kind: "write",
|
|
829
918
|
fileRef: cleanTimelineFileRef(updateMatch[1]),
|
|
919
|
+
oldFileRef: cleanTimelineFileRef(updateMatch[1]),
|
|
920
|
+
newFileRef: cleanTimelineFileRef(updateMatch[1]),
|
|
830
921
|
bodyLines: [],
|
|
831
922
|
};
|
|
832
923
|
continue;
|
|
@@ -838,6 +929,8 @@ function parseApplyPatchSections(patchText) {
|
|
|
838
929
|
current = {
|
|
839
930
|
kind: "delete",
|
|
840
931
|
fileRef: cleanTimelineFileRef(deleteMatch[1]),
|
|
932
|
+
oldFileRef: cleanTimelineFileRef(deleteMatch[1]),
|
|
933
|
+
newFileRef: "",
|
|
841
934
|
bodyLines: [],
|
|
842
935
|
};
|
|
843
936
|
continue;
|
|
@@ -851,6 +944,9 @@ function parseApplyPatchSections(patchText) {
|
|
|
851
944
|
if (moveMatch) {
|
|
852
945
|
const movedFileRef = cleanTimelineFileRef(moveMatch[1]);
|
|
853
946
|
if (movedFileRef) {
|
|
947
|
+
current.kind = current.kind === "write" ? "rename" : current.kind;
|
|
948
|
+
current.oldFileRef = current.oldFileRef || current.fileRef;
|
|
949
|
+
current.newFileRef = movedFileRef;
|
|
854
950
|
current.fileRef = movedFileRef;
|
|
855
951
|
}
|
|
856
952
|
continue;
|
|
@@ -873,27 +969,29 @@ function parseApplyPatchSections(patchText) {
|
|
|
873
969
|
}
|
|
874
970
|
|
|
875
971
|
function buildUnifiedDiffFromApplyPatchSection(section) {
|
|
876
|
-
if (!section?.fileRef) {
|
|
972
|
+
if (!section?.fileRef && !section?.oldFileRef) {
|
|
877
973
|
return "";
|
|
878
974
|
}
|
|
879
975
|
|
|
880
976
|
const bodyLines = Array.isArray(section.bodyLines) ? section.bodyLines : [];
|
|
881
|
-
const fileRef = section.fileRef;
|
|
882
|
-
const
|
|
977
|
+
const fileRef = cleanTimelineFileRef(section.fileRef || section.newFileRef || "");
|
|
978
|
+
const oldFileRef = cleanTimelineFileRef(section.oldFileRef || fileRef);
|
|
979
|
+
const newFileRef = cleanTimelineFileRef(section.newFileRef || fileRef);
|
|
980
|
+
const diffLines = [`diff --git ${diffPathForSide(oldFileRef || fileRef, "a")} ${diffPathForSide(newFileRef || fileRef, "b")}`];
|
|
883
981
|
|
|
884
982
|
if (section.kind === "create") {
|
|
885
983
|
const addedCount = bodyLines.filter((line) => line.startsWith("+")).length;
|
|
886
984
|
diffLines.push("new file mode 100644");
|
|
887
985
|
diffLines.push("--- /dev/null");
|
|
888
|
-
diffLines.push(`+++ ${diffPathForSide(fileRef, "b")}`);
|
|
986
|
+
diffLines.push(`+++ ${diffPathForSide(newFileRef || fileRef, "b")}`);
|
|
889
987
|
diffLines.push(`@@ -0,0 +1,${Math.max(addedCount, 1)} @@`);
|
|
890
988
|
diffLines.push(...bodyLines);
|
|
891
989
|
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
892
990
|
}
|
|
893
991
|
|
|
894
992
|
if (section.kind === "write") {
|
|
895
|
-
diffLines.push(`--- ${diffPathForSide(fileRef, "a")}`);
|
|
896
|
-
diffLines.push(`+++ ${diffPathForSide(fileRef, "b")}`);
|
|
993
|
+
diffLines.push(`--- ${diffPathForSide(oldFileRef || fileRef, "a")}`);
|
|
994
|
+
diffLines.push(`+++ ${diffPathForSide(newFileRef || fileRef, "b")}`);
|
|
897
995
|
diffLines.push(...bodyLines);
|
|
898
996
|
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
899
997
|
}
|
|
@@ -901,26 +999,49 @@ function buildUnifiedDiffFromApplyPatchSection(section) {
|
|
|
901
999
|
if (section.kind === "delete") {
|
|
902
1000
|
const removedCount = bodyLines.filter((line) => line.startsWith("-")).length;
|
|
903
1001
|
diffLines.push("deleted file mode 100644");
|
|
904
|
-
diffLines.push(`--- ${diffPathForSide(fileRef, "a")}`);
|
|
1002
|
+
diffLines.push(`--- ${diffPathForSide(oldFileRef || fileRef, "a")}`);
|
|
905
1003
|
diffLines.push("+++ /dev/null");
|
|
906
1004
|
diffLines.push(`@@ -1,${Math.max(removedCount, 1)} +0,0 @@`);
|
|
907
1005
|
diffLines.push(...bodyLines);
|
|
908
1006
|
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
909
1007
|
}
|
|
910
1008
|
|
|
1009
|
+
if (section.kind === "rename") {
|
|
1010
|
+
diffLines.push(`rename from ${oldFileRef || fileRef}`);
|
|
1011
|
+
diffLines.push(`rename to ${newFileRef || fileRef}`);
|
|
1012
|
+
if (bodyLines.length > 0) {
|
|
1013
|
+
diffLines.push(`--- ${diffPathForSide(oldFileRef || fileRef, "a")}`);
|
|
1014
|
+
diffLines.push(`+++ ${diffPathForSide(newFileRef || fileRef, "b")}`);
|
|
1015
|
+
diffLines.push(...bodyLines);
|
|
1016
|
+
}
|
|
1017
|
+
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
1018
|
+
}
|
|
1019
|
+
|
|
911
1020
|
return "";
|
|
912
1021
|
}
|
|
913
1022
|
|
|
914
|
-
function buildApplyPatchDiffForFileRefs(patchText, fileRefs, fileEventType) {
|
|
1023
|
+
function buildApplyPatchDiffForFileRefs(patchText, fileRefs, fileEventType, previousFileRefs = []) {
|
|
915
1024
|
const normalizedRefs = normalizeTimelineFileRefs(fileRefs);
|
|
1025
|
+
const normalizedPreviousRefs = normalizeTimelineFileRefs(previousFileRefs);
|
|
916
1026
|
if (!normalizedRefs.length) {
|
|
917
|
-
|
|
1027
|
+
if (normalizeTimelineFileEventType(fileEventType) !== "rename" || !normalizedPreviousRefs.length) {
|
|
1028
|
+
return "";
|
|
1029
|
+
}
|
|
918
1030
|
}
|
|
919
1031
|
|
|
920
1032
|
const sections = parseApplyPatchSections(patchText).filter((section) => {
|
|
921
1033
|
if (!section?.fileRef || section.kind !== fileEventType) {
|
|
922
1034
|
return false;
|
|
923
1035
|
}
|
|
1036
|
+
if (fileEventType === "rename") {
|
|
1037
|
+
return (
|
|
1038
|
+
normalizedRefs.some((fileRef) => timelineFileRefsMatch(fileRef, section.newFileRef || section.fileRef)) ||
|
|
1039
|
+
normalizedPreviousRefs.some((fileRef) => timelineFileRefsMatch(fileRef, section.oldFileRef || ""))
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
if (fileEventType === "delete") {
|
|
1043
|
+
return normalizedRefs.some((fileRef) => timelineFileRefsMatch(fileRef, section.oldFileRef || section.fileRef));
|
|
1044
|
+
}
|
|
924
1045
|
return normalizedRefs.some((fileRef) => timelineFileRefsMatch(fileRef, section.fileRef));
|
|
925
1046
|
});
|
|
926
1047
|
|
|
@@ -958,7 +1079,7 @@ async function captureGitDiffText({ cwd, fileRefs }) {
|
|
|
958
1079
|
}
|
|
959
1080
|
|
|
960
1081
|
return new Promise((resolve) => {
|
|
961
|
-
const child = spawn("git", ["diff", "--no-ext-diff", "--no-color", "--", ...normalizedFileRefs], {
|
|
1082
|
+
const child = spawn("git", ["diff", "--no-ext-diff", "--no-color", "-M", "--find-renames", "--", ...normalizedFileRefs], {
|
|
962
1083
|
cwd: normalizedCwd,
|
|
963
1084
|
stdio: ["ignore", "pipe", "pipe"],
|
|
964
1085
|
});
|
|
@@ -998,9 +1119,16 @@ function gitPathspecForFileRef(cwd, fileRef) {
|
|
|
998
1119
|
return relativePath;
|
|
999
1120
|
}
|
|
1000
1121
|
|
|
1001
|
-
async function buildFileEventDiff({
|
|
1122
|
+
async function buildFileEventDiff({
|
|
1123
|
+
fileState,
|
|
1124
|
+
callId,
|
|
1125
|
+
fileRefs,
|
|
1126
|
+
fileEventType,
|
|
1127
|
+
previousFileRefs = [],
|
|
1128
|
+
rolloutFilePath = "",
|
|
1129
|
+
}) {
|
|
1002
1130
|
const normalizedFileEventType = normalizeTimelineFileEventType(fileEventType);
|
|
1003
|
-
if (!["write", "create"].includes(normalizedFileEventType)) {
|
|
1131
|
+
if (!["write", "create", "delete", "rename"].includes(normalizedFileEventType)) {
|
|
1004
1132
|
return {
|
|
1005
1133
|
diffText: "",
|
|
1006
1134
|
diffSource: "",
|
|
@@ -1015,13 +1143,18 @@ async function buildFileEventDiff({ fileState, callId, fileRefs, fileEventType,
|
|
|
1015
1143
|
callId,
|
|
1016
1144
|
rolloutFilePath,
|
|
1017
1145
|
});
|
|
1018
|
-
let diffText = buildApplyPatchDiffForFileRefs(
|
|
1146
|
+
let diffText = buildApplyPatchDiffForFileRefs(
|
|
1147
|
+
storedPatch?.inputText || "",
|
|
1148
|
+
fileRefs,
|
|
1149
|
+
normalizedFileEventType,
|
|
1150
|
+
previousFileRefs
|
|
1151
|
+
);
|
|
1019
1152
|
let diffSource = diffText ? "apply_patch" : "";
|
|
1020
1153
|
|
|
1021
1154
|
if (!diffText) {
|
|
1022
1155
|
diffText = await captureGitDiffText({
|
|
1023
1156
|
cwd: cleanText(storedPatch?.cwd || fileState?.cwd || ""),
|
|
1024
|
-
fileRefs,
|
|
1157
|
+
fileRefs: [...normalizeTimelineFileRefs(previousFileRefs), ...normalizeTimelineFileRefs(fileRefs)],
|
|
1025
1158
|
});
|
|
1026
1159
|
diffSource = diffText ? "git" : "";
|
|
1027
1160
|
}
|
|
@@ -1124,7 +1257,33 @@ function normalizeUnifiedDiffSectionFileRef(value) {
|
|
|
1124
1257
|
}
|
|
1125
1258
|
|
|
1126
1259
|
function extractUnifiedDiffSectionFileRef(sectionText) {
|
|
1260
|
+
const paths = extractUnifiedDiffSectionPaths(sectionText);
|
|
1261
|
+
if (paths.newFileRef) {
|
|
1262
|
+
return paths.newFileRef;
|
|
1263
|
+
}
|
|
1264
|
+
if (paths.oldFileRef) {
|
|
1265
|
+
return paths.oldFileRef;
|
|
1266
|
+
}
|
|
1267
|
+
return "";
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function extractUnifiedDiffSectionPaths(sectionText) {
|
|
1127
1271
|
const lines = String(sectionText || "").replace(/\r\n/gu, "\n").split("\n");
|
|
1272
|
+
let oldFileRef = "";
|
|
1273
|
+
let newFileRef = "";
|
|
1274
|
+
|
|
1275
|
+
for (const line of lines) {
|
|
1276
|
+
const diffMatch = line.match(/^diff --git\s+(\S+)\s+(\S+)$/u);
|
|
1277
|
+
if (!diffMatch) {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
oldFileRef = normalizeUnifiedDiffSectionFileRef(diffMatch[1]);
|
|
1281
|
+
newFileRef = normalizeUnifiedDiffSectionFileRef(diffMatch[2]);
|
|
1282
|
+
if (oldFileRef || newFileRef) {
|
|
1283
|
+
return { oldFileRef, newFileRef };
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1128
1287
|
const preferredPrefixes = ["+++ ", "--- "];
|
|
1129
1288
|
for (const prefix of preferredPrefixes) {
|
|
1130
1289
|
for (const line of lines) {
|
|
@@ -1133,11 +1292,16 @@ function extractUnifiedDiffSectionFileRef(sectionText) {
|
|
|
1133
1292
|
}
|
|
1134
1293
|
const fileRef = normalizeUnifiedDiffSectionFileRef(line.slice(prefix.length));
|
|
1135
1294
|
if (fileRef) {
|
|
1136
|
-
|
|
1295
|
+
if (prefix === "+++ ") {
|
|
1296
|
+
newFileRef = fileRef;
|
|
1297
|
+
} else {
|
|
1298
|
+
oldFileRef = fileRef;
|
|
1299
|
+
}
|
|
1137
1300
|
}
|
|
1138
1301
|
}
|
|
1139
1302
|
}
|
|
1140
|
-
|
|
1303
|
+
|
|
1304
|
+
return { oldFileRef, newFileRef };
|
|
1141
1305
|
}
|
|
1142
1306
|
|
|
1143
1307
|
function splitUnifiedDiffTextByFile(diffText) {
|
|
@@ -1160,6 +1324,7 @@ function splitUnifiedDiffTextByFile(diffText) {
|
|
|
1160
1324
|
return;
|
|
1161
1325
|
}
|
|
1162
1326
|
sections.push({
|
|
1327
|
+
...extractUnifiedDiffSectionPaths(sectionText),
|
|
1163
1328
|
fileRef: extractUnifiedDiffSectionFileRef(sectionText),
|
|
1164
1329
|
diffText: sectionText,
|
|
1165
1330
|
});
|
|
@@ -1183,6 +1348,414 @@ function splitUnifiedDiffTextByFile(diffText) {
|
|
|
1183
1348
|
return sections.filter((section) => section.diffText);
|
|
1184
1349
|
}
|
|
1185
1350
|
|
|
1351
|
+
function codeOwnershipKeyForFile(fileRef) {
|
|
1352
|
+
const normalized = cleanTimelineFileRef(fileRef);
|
|
1353
|
+
return normalized ? `file:${normalized}` : "";
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function codeOwnershipKeyForDelete(fileRef) {
|
|
1357
|
+
const normalized = cleanTimelineFileRef(fileRef);
|
|
1358
|
+
return normalized ? `delete:${normalized}` : "";
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function codeOwnershipKeyForRename(oldFileRef, newFileRef) {
|
|
1362
|
+
const normalizedOldFileRef = cleanTimelineFileRef(oldFileRef);
|
|
1363
|
+
const normalizedNewFileRef = cleanTimelineFileRef(newFileRef);
|
|
1364
|
+
return normalizedOldFileRef && normalizedNewFileRef
|
|
1365
|
+
? `rename:${normalizedOldFileRef}=>${normalizedNewFileRef}`
|
|
1366
|
+
: "";
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function normalizeRepoRelativeFileRef({ repoRoot, fileRef, cwd = "" }) {
|
|
1370
|
+
const normalizedRepoRoot = resolvePath(cleanText(repoRoot || ""));
|
|
1371
|
+
const normalizedFileRef = cleanTimelineFileRef(fileRef);
|
|
1372
|
+
const normalizedCwd = resolvePath(cleanText(cwd || ""));
|
|
1373
|
+
if (!normalizedRepoRoot || !normalizedFileRef) {
|
|
1374
|
+
return "";
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (path.isAbsolute(normalizedFileRef)) {
|
|
1378
|
+
const relativePath = path.relative(normalizedRepoRoot, resolvePath(normalizedFileRef));
|
|
1379
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
1380
|
+
return "";
|
|
1381
|
+
}
|
|
1382
|
+
return cleanTimelineFileRef(relativePath);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (normalizedCwd) {
|
|
1386
|
+
const absolutePath = path.resolve(normalizedCwd, normalizedFileRef);
|
|
1387
|
+
const relativePath = path.relative(normalizedRepoRoot, absolutePath);
|
|
1388
|
+
if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
1389
|
+
return cleanTimelineFileRef(relativePath);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
return normalizedFileRef;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
async function captureGitOutput({ cwd, args }) {
|
|
1397
|
+
const normalizedCwd = resolvePath(cleanText(cwd || ""));
|
|
1398
|
+
if (!normalizedCwd || !Array.isArray(args) || args.length === 0) {
|
|
1399
|
+
return "";
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
return new Promise((resolve) => {
|
|
1403
|
+
const child = spawn("git", args, {
|
|
1404
|
+
cwd: normalizedCwd,
|
|
1405
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
let stdout = "";
|
|
1409
|
+
child.stdout.on("data", (chunk) => {
|
|
1410
|
+
stdout += chunk.toString("utf8");
|
|
1411
|
+
});
|
|
1412
|
+
child.on("error", () => {
|
|
1413
|
+
resolve("");
|
|
1414
|
+
});
|
|
1415
|
+
child.on("close", (code) => {
|
|
1416
|
+
if (code !== 0) {
|
|
1417
|
+
resolve("");
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
resolve(String(stdout || "").replace(/\r\n/gu, "\n").trim());
|
|
1421
|
+
});
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
async function captureGitRepoRoot(searchDir) {
|
|
1426
|
+
return cleanText(
|
|
1427
|
+
await captureGitOutput({
|
|
1428
|
+
cwd: searchDir,
|
|
1429
|
+
args: ["rev-parse", "--show-toplevel"],
|
|
1430
|
+
})
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
async function resolveCodeEventRepoRoot(item, repoRootCache) {
|
|
1435
|
+
const searchDirs = [];
|
|
1436
|
+
const normalizedCwd = resolvePath(cleanText(item?.cwd || ""));
|
|
1437
|
+
if (normalizedCwd) {
|
|
1438
|
+
searchDirs.push(normalizedCwd);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
for (const fileRef of [
|
|
1442
|
+
...normalizeTimelineFileRefs(item?.fileRefs ?? []),
|
|
1443
|
+
...normalizeTimelineFileRefs(item?.previousFileRefs ?? []),
|
|
1444
|
+
]) {
|
|
1445
|
+
if (!path.isAbsolute(fileRef)) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
searchDirs.push(path.dirname(resolvePath(fileRef)));
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
for (const searchDir of [...new Set(searchDirs.filter(Boolean))]) {
|
|
1452
|
+
if (repoRootCache.has(searchDir)) {
|
|
1453
|
+
const cached = cleanText(repoRootCache.get(searchDir) || "");
|
|
1454
|
+
if (cached) {
|
|
1455
|
+
return cached;
|
|
1456
|
+
}
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
const repoRoot = await captureGitRepoRoot(searchDir);
|
|
1460
|
+
repoRootCache.set(searchDir, repoRoot);
|
|
1461
|
+
if (repoRoot) {
|
|
1462
|
+
return repoRoot;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
return "";
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function expandCodeEventOwnershipCandidates(item, repoRoot) {
|
|
1470
|
+
const fileEventType = normalizeTimelineFileEventType(item?.fileEventType || "");
|
|
1471
|
+
const createdAtMs = Number(item?.createdAtMs) || 0;
|
|
1472
|
+
const cwd = cleanText(item?.cwd || "");
|
|
1473
|
+
const threadId = cleanText(item?.threadId || "");
|
|
1474
|
+
const threadLabel = cleanText(item?.threadLabel || "");
|
|
1475
|
+
const fileRefs = normalizeTimelineFileRefs(item?.fileRefs ?? []).map((fileRef) =>
|
|
1476
|
+
normalizeRepoRelativeFileRef({ repoRoot, fileRef, cwd })
|
|
1477
|
+
);
|
|
1478
|
+
const previousFileRefs = normalizeTimelineFileRefs(item?.previousFileRefs ?? []).map((fileRef) =>
|
|
1479
|
+
normalizeRepoRelativeFileRef({ repoRoot, fileRef, cwd })
|
|
1480
|
+
);
|
|
1481
|
+
|
|
1482
|
+
if (fileEventType === "rename") {
|
|
1483
|
+
const renameCandidates = [];
|
|
1484
|
+
const pairCount = Math.max(fileRefs.length, previousFileRefs.length);
|
|
1485
|
+
for (let index = 0; index < pairCount; index += 1) {
|
|
1486
|
+
const newFileRef = cleanTimelineFileRef(fileRefs[index] || "");
|
|
1487
|
+
const oldFileRef = cleanTimelineFileRef(previousFileRefs[index] || "");
|
|
1488
|
+
if (!newFileRef && !oldFileRef) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
const ownershipKeys = [
|
|
1492
|
+
codeOwnershipKeyForRename(oldFileRef, newFileRef),
|
|
1493
|
+
codeOwnershipKeyForFile(newFileRef),
|
|
1494
|
+
].filter(Boolean);
|
|
1495
|
+
renameCandidates.push({
|
|
1496
|
+
threadId,
|
|
1497
|
+
threadLabel,
|
|
1498
|
+
createdAtMs,
|
|
1499
|
+
fileEventType,
|
|
1500
|
+
ownershipKeys,
|
|
1501
|
+
fileRef: newFileRef,
|
|
1502
|
+
previousFileRef: oldFileRef,
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
return renameCandidates;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
const normalizedRefs = fileRefs.filter(Boolean);
|
|
1509
|
+
return normalizedRefs.map((fileRef) => ({
|
|
1510
|
+
threadId,
|
|
1511
|
+
threadLabel,
|
|
1512
|
+
createdAtMs,
|
|
1513
|
+
fileEventType,
|
|
1514
|
+
ownershipKeys: [
|
|
1515
|
+
fileEventType === "delete" ? codeOwnershipKeyForDelete(fileRef) : codeOwnershipKeyForFile(fileRef),
|
|
1516
|
+
].filter(Boolean),
|
|
1517
|
+
fileRef,
|
|
1518
|
+
previousFileRef: "",
|
|
1519
|
+
}));
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
function collectCodeCandidatePathspecs(candidates) {
|
|
1523
|
+
const pathspecs = new Set();
|
|
1524
|
+
for (const candidate of candidates) {
|
|
1525
|
+
const fileRef = cleanTimelineFileRef(candidate?.fileRef || "");
|
|
1526
|
+
const previousFileRef = cleanTimelineFileRef(candidate?.previousFileRef || "");
|
|
1527
|
+
if (fileRef) {
|
|
1528
|
+
pathspecs.add(fileRef);
|
|
1529
|
+
}
|
|
1530
|
+
if (previousFileRef) {
|
|
1531
|
+
pathspecs.add(previousFileRef);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return [...pathspecs];
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
async function captureGitDiffNameStatus({ repoRoot, pathspecs }) {
|
|
1538
|
+
return captureGitOutput({
|
|
1539
|
+
cwd: repoRoot,
|
|
1540
|
+
args: ["diff", "--name-status", "-M", "--find-renames", "--", ...pathspecs],
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
async function captureGitStatusPorcelain({ repoRoot, pathspecs }) {
|
|
1545
|
+
return captureGitOutput({
|
|
1546
|
+
cwd: repoRoot,
|
|
1547
|
+
args: ["status", "--porcelain=v1", "--untracked-files=all", "--", ...pathspecs],
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
async function captureGitCurrentDiffText({ repoRoot, pathspecs }) {
|
|
1552
|
+
return captureGitOutput({
|
|
1553
|
+
cwd: repoRoot,
|
|
1554
|
+
args: ["diff", "--no-ext-diff", "--no-color", "-M", "--find-renames", "--", ...pathspecs],
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function parseGitNameStatusEntries(outputText) {
|
|
1559
|
+
const entries = [];
|
|
1560
|
+
for (const rawLine of String(outputText || "").split("\n")) {
|
|
1561
|
+
const line = rawLine.trim();
|
|
1562
|
+
if (!line) {
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
const parts = line.split("\t");
|
|
1566
|
+
const status = cleanText(parts[0] || "");
|
|
1567
|
+
if (!status) {
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (status.startsWith("R")) {
|
|
1571
|
+
const oldFileRef = cleanTimelineFileRef(parts[1] || "");
|
|
1572
|
+
const newFileRef = cleanTimelineFileRef(parts[2] || "");
|
|
1573
|
+
if (oldFileRef && newFileRef) {
|
|
1574
|
+
entries.push({
|
|
1575
|
+
changeType: "rename",
|
|
1576
|
+
fileRef: newFileRef,
|
|
1577
|
+
oldFileRef,
|
|
1578
|
+
newFileRef,
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
const fileRef = cleanTimelineFileRef(parts[1] || "");
|
|
1584
|
+
if (!fileRef) {
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
if (status === "M") {
|
|
1588
|
+
entries.push({
|
|
1589
|
+
changeType: "write",
|
|
1590
|
+
fileRef,
|
|
1591
|
+
oldFileRef: "",
|
|
1592
|
+
newFileRef: fileRef,
|
|
1593
|
+
});
|
|
1594
|
+
} else if (status === "D") {
|
|
1595
|
+
entries.push({
|
|
1596
|
+
changeType: "delete",
|
|
1597
|
+
fileRef,
|
|
1598
|
+
oldFileRef: fileRef,
|
|
1599
|
+
newFileRef: "",
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return entries;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function parseGitUntrackedEntries(outputText) {
|
|
1607
|
+
const fileRefs = [];
|
|
1608
|
+
for (const rawLine of String(outputText || "").split("\n")) {
|
|
1609
|
+
const line = String(rawLine || "");
|
|
1610
|
+
if (!line.startsWith("?? ")) {
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
const fileRef = cleanTimelineFileRef(line.slice(3));
|
|
1614
|
+
if (fileRef) {
|
|
1615
|
+
fileRefs.push(fileRef);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return normalizeTimelineFileRefs(fileRefs);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
async function buildUntrackedUnifiedDiff({ repoRoot, fileRef }) {
|
|
1622
|
+
const normalizedRepoRoot = resolvePath(cleanText(repoRoot || ""));
|
|
1623
|
+
const normalizedFileRef = cleanTimelineFileRef(fileRef);
|
|
1624
|
+
if (!normalizedRepoRoot || !normalizedFileRef) {
|
|
1625
|
+
return "";
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
const absolutePath = path.join(normalizedRepoRoot, normalizedFileRef);
|
|
1629
|
+
let content = "";
|
|
1630
|
+
try {
|
|
1631
|
+
content = await fs.readFile(absolutePath, "utf8");
|
|
1632
|
+
} catch {
|
|
1633
|
+
return "";
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
const lines = String(content || "").replace(/\r\n/gu, "\n").split("\n");
|
|
1637
|
+
const bodyLines = lines.length === 1 && lines[0] === "" ? [] : lines.map((line) => `+${line}`);
|
|
1638
|
+
const diffLines = [
|
|
1639
|
+
`diff --git ${diffPathForSide(normalizedFileRef, "a")} ${diffPathForSide(normalizedFileRef, "b")}`,
|
|
1640
|
+
"new file mode 100644",
|
|
1641
|
+
"--- /dev/null",
|
|
1642
|
+
`+++ ${diffPathForSide(normalizedFileRef, "b")}`,
|
|
1643
|
+
];
|
|
1644
|
+
if (bodyLines.length > 0) {
|
|
1645
|
+
diffLines.push(`@@ -0,0 +1,${Math.max(bodyLines.length, 1)} @@`);
|
|
1646
|
+
diffLines.push(...bodyLines);
|
|
1647
|
+
}
|
|
1648
|
+
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function findMatchingCurrentDiffSection(sections, change) {
|
|
1652
|
+
if (!Array.isArray(sections) || sections.length === 0 || !isPlainObject(change)) {
|
|
1653
|
+
return null;
|
|
1654
|
+
}
|
|
1655
|
+
const changeType = normalizeTimelineFileEventType(change.changeType || "");
|
|
1656
|
+
if (changeType === "rename") {
|
|
1657
|
+
return (
|
|
1658
|
+
sections.find(
|
|
1659
|
+
(section) =>
|
|
1660
|
+
timelineFileRefsMatch(section.oldFileRef || "", change.oldFileRef || "") &&
|
|
1661
|
+
timelineFileRefsMatch(section.newFileRef || section.fileRef || "", change.newFileRef || "")
|
|
1662
|
+
) ?? null
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
if (changeType === "delete") {
|
|
1666
|
+
return (
|
|
1667
|
+
sections.find((section) =>
|
|
1668
|
+
timelineFileRefsMatch(section.oldFileRef || section.fileRef || "", change.fileRef || change.oldFileRef || "")
|
|
1669
|
+
) ?? null
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
return (
|
|
1673
|
+
sections.find((section) =>
|
|
1674
|
+
timelineFileRefsMatch(section.newFileRef || section.fileRef || "", change.fileRef || change.newFileRef || "")
|
|
1675
|
+
) ?? null
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
async function buildCurrentUnstagedChangesForRepo({ repoRoot, candidates }) {
|
|
1680
|
+
const pathspecs = collectCodeCandidatePathspecs(candidates);
|
|
1681
|
+
if (!repoRoot || pathspecs.length === 0) {
|
|
1682
|
+
return [];
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
const [nameStatusText, statusText, diffText] = await Promise.all([
|
|
1686
|
+
captureGitDiffNameStatus({ repoRoot, pathspecs }),
|
|
1687
|
+
captureGitStatusPorcelain({ repoRoot, pathspecs }),
|
|
1688
|
+
captureGitCurrentDiffText({ repoRoot, pathspecs }),
|
|
1689
|
+
]);
|
|
1690
|
+
|
|
1691
|
+
const diffSections = splitUnifiedDiffTextByFile(diffText);
|
|
1692
|
+
const currentChanges = parseGitNameStatusEntries(nameStatusText).map((entry) => {
|
|
1693
|
+
const section = findMatchingCurrentDiffSection(diffSections, entry);
|
|
1694
|
+
const bestDiffText =
|
|
1695
|
+
normalizeTimelineDiffText(section?.diffText || "") ||
|
|
1696
|
+
(entry.changeType === "rename"
|
|
1697
|
+
? buildUnifiedDiffFromApplyPatchSection({
|
|
1698
|
+
kind: "rename",
|
|
1699
|
+
fileRef: entry.newFileRef || entry.fileRef,
|
|
1700
|
+
oldFileRef: entry.oldFileRef,
|
|
1701
|
+
newFileRef: entry.newFileRef || entry.fileRef,
|
|
1702
|
+
bodyLines: [],
|
|
1703
|
+
})
|
|
1704
|
+
: "");
|
|
1705
|
+
const counts = diffLineCounts(bestDiffText);
|
|
1706
|
+
return {
|
|
1707
|
+
changeType: normalizeTimelineFileEventType(entry.changeType || ""),
|
|
1708
|
+
fileRef: cleanTimelineFileRef(entry.fileRef || entry.newFileRef || ""),
|
|
1709
|
+
oldFileRef: cleanTimelineFileRef(entry.oldFileRef || ""),
|
|
1710
|
+
newFileRef: cleanTimelineFileRef(entry.newFileRef || entry.fileRef || ""),
|
|
1711
|
+
diffText: bestDiffText,
|
|
1712
|
+
diffAvailable: Boolean(bestDiffText),
|
|
1713
|
+
diffSource: bestDiffText ? "git" : "",
|
|
1714
|
+
diffAddedLines: counts.addedLines,
|
|
1715
|
+
diffRemovedLines: counts.removedLines,
|
|
1716
|
+
};
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
for (const fileRef of parseGitUntrackedEntries(statusText)) {
|
|
1720
|
+
const syntheticDiffText = await buildUntrackedUnifiedDiff({ repoRoot, fileRef });
|
|
1721
|
+
const counts = diffLineCounts(syntheticDiffText);
|
|
1722
|
+
currentChanges.push({
|
|
1723
|
+
changeType: "create",
|
|
1724
|
+
fileRef,
|
|
1725
|
+
oldFileRef: "",
|
|
1726
|
+
newFileRef: fileRef,
|
|
1727
|
+
diffText: syntheticDiffText,
|
|
1728
|
+
diffAvailable: Boolean(syntheticDiffText),
|
|
1729
|
+
diffSource: syntheticDiffText ? "git" : "",
|
|
1730
|
+
diffAddedLines: counts.addedLines,
|
|
1731
|
+
diffRemovedLines: counts.removedLines,
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
return currentChanges;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
function resolveCurrentChangeOwner(change, candidates) {
|
|
1739
|
+
const preferredKeys = [];
|
|
1740
|
+
const changeType = normalizeTimelineFileEventType(change?.changeType || "");
|
|
1741
|
+
if (changeType === "rename") {
|
|
1742
|
+
preferredKeys.push(codeOwnershipKeyForRename(change?.oldFileRef || "", change?.newFileRef || ""));
|
|
1743
|
+
preferredKeys.push(codeOwnershipKeyForFile(change?.newFileRef || change?.fileRef || ""));
|
|
1744
|
+
} else if (changeType === "delete") {
|
|
1745
|
+
preferredKeys.push(codeOwnershipKeyForDelete(change?.fileRef || change?.oldFileRef || ""));
|
|
1746
|
+
} else {
|
|
1747
|
+
preferredKeys.push(codeOwnershipKeyForFile(change?.fileRef || change?.newFileRef || ""));
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
for (const key of preferredKeys.filter(Boolean)) {
|
|
1751
|
+
const match = candidates.find((candidate) => Array.isArray(candidate?.ownershipKeys) && candidate.ownershipKeys.includes(key));
|
|
1752
|
+
if (match) {
|
|
1753
|
+
return match;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1186
1759
|
function handleSignal() {
|
|
1187
1760
|
runtime.stopping = true;
|
|
1188
1761
|
}
|
|
@@ -1294,7 +1867,7 @@ function isCodeEventEntry(raw) {
|
|
|
1294
1867
|
return false;
|
|
1295
1868
|
}
|
|
1296
1869
|
const fileEventType = normalizeTimelineFileEventType(raw.fileEventType ?? "");
|
|
1297
|
-
return
|
|
1870
|
+
return ["write", "create", "delete", "rename"].includes(fileEventType);
|
|
1298
1871
|
}
|
|
1299
1872
|
|
|
1300
1873
|
function normalizeCodeEvents(rawItems, maxItems) {
|
|
@@ -1377,6 +1950,7 @@ function normalizeTimelineEntry(raw) {
|
|
|
1377
1950
|
summary,
|
|
1378
1951
|
messageText,
|
|
1379
1952
|
fileEventType,
|
|
1953
|
+
previousFileRefs: normalizeTimelineFileRefs(raw.previousFileRefs ?? []),
|
|
1380
1954
|
imagePaths: normalizeTimelineImagePaths(raw.imagePaths ?? raw.localImagePaths ?? []),
|
|
1381
1955
|
fileRefs: normalizeTimelineFileRefs(raw.fileRefs ?? extractTimelineFileRefs(messageText)),
|
|
1382
1956
|
diffText,
|
|
@@ -1391,6 +1965,7 @@ function normalizeTimelineEntry(raw) {
|
|
|
1391
1965
|
readOnly: raw.readOnly !== false,
|
|
1392
1966
|
primaryLabel: cleanText(raw.primaryLabel ?? "") || "詳細",
|
|
1393
1967
|
tone: cleanText(raw.tone ?? "") || "secondary",
|
|
1968
|
+
cwd: resolvePath(cleanText(raw.cwd || "")),
|
|
1394
1969
|
};
|
|
1395
1970
|
}
|
|
1396
1971
|
|
|
@@ -1415,6 +1990,8 @@ function recordTimelineEntry({ config, runtime, state, entry }) {
|
|
|
1415
1990
|
item.diffAddedLines,
|
|
1416
1991
|
item.diffRemovedLines,
|
|
1417
1992
|
item.diffText,
|
|
1993
|
+
item.previousFileRefs,
|
|
1994
|
+
item.cwd,
|
|
1418
1995
|
])
|
|
1419
1996
|
) !==
|
|
1420
1997
|
JSON.stringify(
|
|
@@ -1427,6 +2004,8 @@ function recordTimelineEntry({ config, runtime, state, entry }) {
|
|
|
1427
2004
|
item.diffAddedLines,
|
|
1428
2005
|
item.diffRemovedLines,
|
|
1429
2006
|
item.diffText,
|
|
2007
|
+
item.previousFileRefs,
|
|
2008
|
+
item.cwd,
|
|
1430
2009
|
])
|
|
1431
2010
|
);
|
|
1432
2011
|
runtime.recentTimelineEntries = nextItems;
|
|
@@ -1458,6 +2037,8 @@ function recordCodeEvent({ config, runtime, state, entry }) {
|
|
|
1458
2037
|
item.diffAddedLines,
|
|
1459
2038
|
item.diffRemovedLines,
|
|
1460
2039
|
item.diffText,
|
|
2040
|
+
item.previousFileRefs,
|
|
2041
|
+
item.cwd,
|
|
1461
2042
|
])
|
|
1462
2043
|
) !==
|
|
1463
2044
|
JSON.stringify(
|
|
@@ -1470,6 +2051,8 @@ function recordCodeEvent({ config, runtime, state, entry }) {
|
|
|
1470
2051
|
item.diffAddedLines,
|
|
1471
2052
|
item.diffRemovedLines,
|
|
1472
2053
|
item.diffText,
|
|
2054
|
+
item.previousFileRefs,
|
|
2055
|
+
item.cwd,
|
|
1473
2056
|
])
|
|
1474
2057
|
);
|
|
1475
2058
|
runtime.recentCodeEvents = nextItems;
|
|
@@ -1504,6 +2087,8 @@ function syncRecentCodeEventsFromTimeline({ config, runtime, state }) {
|
|
|
1504
2087
|
item.diffAddedLines,
|
|
1505
2088
|
item.diffRemovedLines,
|
|
1506
2089
|
item.diffText,
|
|
2090
|
+
item.previousFileRefs,
|
|
2091
|
+
item.cwd,
|
|
1507
2092
|
])
|
|
1508
2093
|
) !==
|
|
1509
2094
|
JSON.stringify(
|
|
@@ -1517,6 +2102,8 @@ function syncRecentCodeEventsFromTimeline({ config, runtime, state }) {
|
|
|
1517
2102
|
item.diffAddedLines,
|
|
1518
2103
|
item.diffRemovedLines,
|
|
1519
2104
|
item.diffText,
|
|
2105
|
+
item.previousFileRefs,
|
|
2106
|
+
item.cwd,
|
|
1520
2107
|
])
|
|
1521
2108
|
);
|
|
1522
2109
|
runtime.recentCodeEvents = nextItems;
|
|
@@ -3028,7 +3615,12 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
|
|
|
3028
3615
|
}
|
|
3029
3616
|
|
|
3030
3617
|
if (payloadType === "custom_tool_call_output") {
|
|
3031
|
-
const
|
|
3618
|
+
const storedPatch = await findStoredApplyPatchInput({
|
|
3619
|
+
fileState,
|
|
3620
|
+
callId,
|
|
3621
|
+
rolloutFilePath,
|
|
3622
|
+
});
|
|
3623
|
+
const updates = extractUpdatedFileRefsByType(payload.output ?? "", storedPatch?.inputText || "");
|
|
3032
3624
|
const entries = [];
|
|
3033
3625
|
const createDiff = await buildFileEventDiff({
|
|
3034
3626
|
fileState,
|
|
@@ -3042,6 +3634,25 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
|
|
|
3042
3634
|
callId,
|
|
3043
3635
|
fileRefs: updates.write,
|
|
3044
3636
|
fileEventType: "write",
|
|
3637
|
+
previousFileRefs: [],
|
|
3638
|
+
rolloutFilePath,
|
|
3639
|
+
});
|
|
3640
|
+
const deleteDiff = await buildFileEventDiff({
|
|
3641
|
+
fileState,
|
|
3642
|
+
callId,
|
|
3643
|
+
fileRefs: updates.delete,
|
|
3644
|
+
fileEventType: "delete",
|
|
3645
|
+
previousFileRefs: [],
|
|
3646
|
+
rolloutFilePath,
|
|
3647
|
+
});
|
|
3648
|
+
const renameNewRefs = updates.rename.map((entry) => entry.newFileRef);
|
|
3649
|
+
const renameOldRefs = updates.rename.map((entry) => entry.oldFileRef);
|
|
3650
|
+
const renameDiff = await buildFileEventDiff({
|
|
3651
|
+
fileState,
|
|
3652
|
+
callId,
|
|
3653
|
+
fileRefs: renameNewRefs,
|
|
3654
|
+
fileEventType: "rename",
|
|
3655
|
+
previousFileRefs: renameOldRefs,
|
|
3045
3656
|
rolloutFilePath,
|
|
3046
3657
|
});
|
|
3047
3658
|
|
|
@@ -3063,6 +3674,7 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
|
|
|
3063
3674
|
diffAddedLines: createDiff.diffAddedLines,
|
|
3064
3675
|
diffRemovedLines: createDiff.diffRemovedLines,
|
|
3065
3676
|
createdAtMs,
|
|
3677
|
+
cwd: fileState.cwd || "",
|
|
3066
3678
|
readOnly: true,
|
|
3067
3679
|
})
|
|
3068
3680
|
);
|
|
@@ -3086,6 +3698,56 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
|
|
|
3086
3698
|
diffAddedLines: writeDiff.diffAddedLines,
|
|
3087
3699
|
diffRemovedLines: writeDiff.diffRemovedLines,
|
|
3088
3700
|
createdAtMs,
|
|
3701
|
+
cwd: fileState.cwd || "",
|
|
3702
|
+
readOnly: true,
|
|
3703
|
+
})
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
if (updates.delete.length > 0) {
|
|
3708
|
+
entries.push(
|
|
3709
|
+
normalizeTimelineEntry({
|
|
3710
|
+
stableId: `file_event:delete:${threadId}:${callId || historyToken(`${threadId}:${createdAtMs}:${updates.delete.join("|")}`)}`,
|
|
3711
|
+
token: historyToken(`file_event:delete:${threadId}:${callId || createdAtMs}`),
|
|
3712
|
+
kind: "file_event",
|
|
3713
|
+
fileEventType: "delete",
|
|
3714
|
+
threadId,
|
|
3715
|
+
threadLabel,
|
|
3716
|
+
title: fileEventTitle(DEFAULT_LOCALE, "delete"),
|
|
3717
|
+
summary: "",
|
|
3718
|
+
fileRefs: updates.delete,
|
|
3719
|
+
diffText: deleteDiff.diffText,
|
|
3720
|
+
diffSource: deleteDiff.diffSource,
|
|
3721
|
+
diffAvailable: deleteDiff.diffAvailable,
|
|
3722
|
+
diffAddedLines: deleteDiff.diffAddedLines,
|
|
3723
|
+
diffRemovedLines: deleteDiff.diffRemovedLines,
|
|
3724
|
+
createdAtMs,
|
|
3725
|
+
cwd: fileState.cwd || "",
|
|
3726
|
+
readOnly: true,
|
|
3727
|
+
})
|
|
3728
|
+
);
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
if (updates.rename.length > 0) {
|
|
3732
|
+
entries.push(
|
|
3733
|
+
normalizeTimelineEntry({
|
|
3734
|
+
stableId: `file_event:rename:${threadId}:${callId || historyToken(`${threadId}:${createdAtMs}:${renameOldRefs.join("|")}=>${renameNewRefs.join("|")}`)}`,
|
|
3735
|
+
token: historyToken(`file_event:rename:${threadId}:${callId || createdAtMs}`),
|
|
3736
|
+
kind: "file_event",
|
|
3737
|
+
fileEventType: "rename",
|
|
3738
|
+
threadId,
|
|
3739
|
+
threadLabel,
|
|
3740
|
+
title: fileEventTitle(DEFAULT_LOCALE, "rename"),
|
|
3741
|
+
summary: "",
|
|
3742
|
+
fileRefs: renameNewRefs,
|
|
3743
|
+
previousFileRefs: renameOldRefs,
|
|
3744
|
+
diffText: renameDiff.diffText,
|
|
3745
|
+
diffSource: renameDiff.diffSource,
|
|
3746
|
+
diffAvailable: renameDiff.diffAvailable,
|
|
3747
|
+
diffAddedLines: renameDiff.diffAddedLines,
|
|
3748
|
+
diffRemovedLines: renameDiff.diffRemovedLines,
|
|
3749
|
+
createdAtMs,
|
|
3750
|
+
cwd: fileState.cwd || "",
|
|
3089
3751
|
readOnly: true,
|
|
3090
3752
|
})
|
|
3091
3753
|
);
|
|
@@ -6158,16 +6820,16 @@ function formatNativeApprovalMessage(kind, params, locale = config?.defaultLocal
|
|
|
6158
6820
|
function formatCommandApprovalMessage(params, locale = config?.defaultLocale || DEFAULT_LOCALE) {
|
|
6159
6821
|
const parts = [];
|
|
6160
6822
|
const reason = truncate(cleanText(params.reason ?? params.justification ?? ""), 220);
|
|
6161
|
-
const command = truncate(cleanText(params.command ?? params.cmd ?? ""),
|
|
6823
|
+
const command = truncate(cleanText(params.command ?? params.cmd ?? ""), 1200);
|
|
6162
6824
|
if (reason) {
|
|
6163
6825
|
parts.push(reason);
|
|
6164
6826
|
} else {
|
|
6165
6827
|
parts.push(t(locale, "server.message.commandApprovalNeeded"));
|
|
6166
6828
|
}
|
|
6167
6829
|
if (command) {
|
|
6168
|
-
parts.push(t(locale, "server.message.
|
|
6830
|
+
parts.push(`${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${command}\n\`\`\``);
|
|
6169
6831
|
}
|
|
6170
|
-
return
|
|
6832
|
+
return parts.join("\n\n") || t(locale, "server.message.commandApprovalNeeded");
|
|
6171
6833
|
}
|
|
6172
6834
|
|
|
6173
6835
|
function formatFileApprovalMessage(params, locale = config?.defaultLocale || DEFAULT_LOCALE) {
|
|
@@ -7759,8 +8421,8 @@ function buildCompletedInboxItems(runtime, state, config, locale) {
|
|
|
7759
8421
|
}));
|
|
7760
8422
|
}
|
|
7761
8423
|
|
|
7762
|
-
function buildDiffInboxItems(runtime, state, config, locale) {
|
|
7763
|
-
return buildDiffThreadGroups(runtime, state, config).map((group) => ({
|
|
8424
|
+
async function buildDiffInboxItems(runtime, state, config, locale) {
|
|
8425
|
+
return (await buildDiffThreadGroups(runtime, state, config)).map((group) => ({
|
|
7764
8426
|
kind: "diff_thread",
|
|
7765
8427
|
token: group.token,
|
|
7766
8428
|
threadId: group.threadId,
|
|
@@ -7774,6 +8436,14 @@ function buildDiffInboxItems(runtime, state, config, locale) {
|
|
|
7774
8436
|
latestChangeFileRefs: normalizeTimelineFileRefs(group.latestChangeFileRefs ?? []),
|
|
7775
8437
|
diffAddedLines: group.diffAddedLines,
|
|
7776
8438
|
diffRemovedLines: group.diffRemovedLines,
|
|
8439
|
+
files: Array.isArray(group.files)
|
|
8440
|
+
? group.files.map((fileGroup) => ({
|
|
8441
|
+
fileRef: cleanTimelineFileRef(fileGroup.fileRef || ""),
|
|
8442
|
+
oldFileRef: cleanTimelineFileRef(fileGroup.oldFileRef || ""),
|
|
8443
|
+
newFileRef: cleanTimelineFileRef(fileGroup.newFileRef || ""),
|
|
8444
|
+
changeType: normalizeTimelineFileEventType(fileGroup.changeType || ""),
|
|
8445
|
+
}))
|
|
8446
|
+
: [],
|
|
7777
8447
|
primaryLabel: t(locale, "server.action.detail"),
|
|
7778
8448
|
createdAtMs: group.latestChangedAtMs,
|
|
7779
8449
|
}));
|
|
@@ -7785,116 +8455,144 @@ function diffThreadToken(threadId, threadLabel = "") {
|
|
|
7785
8455
|
return historyToken(`diff_thread:${normalizedThreadId || normalizedThreadLabel || "unknown"}`);
|
|
7786
8456
|
}
|
|
7787
8457
|
|
|
7788
|
-
function buildDiffThreadGroups(runtime, state, config) {
|
|
8458
|
+
async function buildDiffThreadGroups(runtime, state, config) {
|
|
7789
8459
|
const items = normalizeCodeEvents(
|
|
7790
8460
|
state.recentCodeEvents ?? runtime.recentCodeEvents,
|
|
7791
8461
|
config.maxCodeEvents
|
|
7792
8462
|
);
|
|
7793
8463
|
runtime.recentCodeEvents = items;
|
|
7794
8464
|
|
|
8465
|
+
const repoRootCache = new Map();
|
|
7795
8466
|
const relevantItems = items
|
|
7796
8467
|
.slice()
|
|
7797
|
-
.sort((left, right) => Number(
|
|
7798
|
-
|
|
7799
|
-
const groupsByThread = new Map();
|
|
8468
|
+
.sort((left, right) => Number(right.createdAtMs ?? 0) - Number(left.createdAtMs ?? 0));
|
|
8469
|
+
const candidatesByRepoRoot = new Map();
|
|
7800
8470
|
|
|
7801
8471
|
for (const item of relevantItems) {
|
|
7802
|
-
const
|
|
7803
|
-
|
|
7804
|
-
const threadKey = threadId || `unknown:${threadLabel || item.token}`;
|
|
7805
|
-
const fileRefs = normalizeTimelineFileRefs(item.fileRefs ?? []);
|
|
7806
|
-
if (fileRefs.length === 0) {
|
|
8472
|
+
const repoRoot = await resolveCodeEventRepoRoot(item, repoRootCache);
|
|
8473
|
+
if (!repoRoot) {
|
|
7807
8474
|
continue;
|
|
7808
8475
|
}
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
threadGroup = {
|
|
7813
|
-
kind: "diff_thread",
|
|
7814
|
-
token: diffThreadToken(threadId, threadLabel),
|
|
7815
|
-
threadId,
|
|
7816
|
-
threadLabel,
|
|
7817
|
-
changedFileCount: 0,
|
|
7818
|
-
latestChangedAtMs: 0,
|
|
7819
|
-
latestChangedAtMsForSummary: 0,
|
|
7820
|
-
latestChangeType: "",
|
|
7821
|
-
latestChangeFileRefs: [],
|
|
7822
|
-
diffAddedLines: 0,
|
|
7823
|
-
diffRemovedLines: 0,
|
|
7824
|
-
filesByRef: new Map(),
|
|
7825
|
-
};
|
|
7826
|
-
groupsByThread.set(threadKey, threadGroup);
|
|
7827
|
-
} else if (!threadGroup.threadLabel && threadLabel) {
|
|
7828
|
-
threadGroup.threadLabel = threadLabel;
|
|
8476
|
+
const candidates = expandCodeEventOwnershipCandidates(item, repoRoot);
|
|
8477
|
+
if (candidates.length === 0) {
|
|
8478
|
+
continue;
|
|
7829
8479
|
}
|
|
8480
|
+
const existing = candidatesByRepoRoot.get(repoRoot) ?? [];
|
|
8481
|
+
existing.push(...candidates);
|
|
8482
|
+
candidatesByRepoRoot.set(repoRoot, existing);
|
|
8483
|
+
}
|
|
7830
8484
|
|
|
7831
|
-
|
|
7832
|
-
const splitSections = splitUnifiedDiffTextByFile(item.diffText);
|
|
8485
|
+
const groupsByThread = new Map();
|
|
7833
8486
|
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
8487
|
+
for (const [repoRoot, candidates] of candidatesByRepoRoot.entries()) {
|
|
8488
|
+
const currentChanges = await buildCurrentUnstagedChangesForRepo({ repoRoot, candidates });
|
|
8489
|
+
for (const change of currentChanges) {
|
|
8490
|
+
const owner = resolveCurrentChangeOwner(change, candidates);
|
|
8491
|
+
if (!owner) {
|
|
7837
8492
|
continue;
|
|
7838
8493
|
}
|
|
7839
8494
|
|
|
7840
|
-
|
|
7841
|
-
const
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
8495
|
+
const threadId = cleanText(owner.threadId || "");
|
|
8496
|
+
const threadLabel = cleanText(owner.threadLabel || "");
|
|
8497
|
+
const threadKey = threadId || `unknown:${threadLabel || cleanText(change.fileRef || change.newFileRef || change.oldFileRef || "")}`;
|
|
8498
|
+
let threadGroup = groupsByThread.get(threadKey);
|
|
8499
|
+
if (!threadGroup) {
|
|
8500
|
+
threadGroup = {
|
|
8501
|
+
kind: "diff_thread",
|
|
8502
|
+
token: diffThreadToken(threadId, threadLabel),
|
|
8503
|
+
threadId,
|
|
8504
|
+
threadLabel,
|
|
8505
|
+
changedFileCount: 0,
|
|
8506
|
+
latestChangedAtMs: 0,
|
|
8507
|
+
latestChangedAtMsForSummary: 0,
|
|
8508
|
+
latestChangeType: "",
|
|
8509
|
+
latestChangeFileRefs: [],
|
|
8510
|
+
diffAddedLines: 0,
|
|
8511
|
+
diffRemovedLines: 0,
|
|
8512
|
+
filesByRef: new Map(),
|
|
8513
|
+
};
|
|
8514
|
+
groupsByThread.set(threadKey, threadGroup);
|
|
8515
|
+
} else if (!threadGroup.threadLabel && threadLabel) {
|
|
8516
|
+
threadGroup.threadLabel = threadLabel;
|
|
7846
8517
|
}
|
|
7847
8518
|
|
|
7848
|
-
const
|
|
7849
|
-
|
|
8519
|
+
const fileGroupKey =
|
|
8520
|
+
normalizeTimelineFileEventType(change.changeType) === "rename"
|
|
8521
|
+
? codeOwnershipKeyForRename(change.oldFileRef || "", change.newFileRef || change.fileRef || "")
|
|
8522
|
+
: `${normalizeTimelineFileEventType(change.changeType)}:${cleanTimelineFileRef(change.fileRef || change.newFileRef || change.oldFileRef || "")}`;
|
|
8523
|
+
if (!fileGroupKey) {
|
|
8524
|
+
continue;
|
|
8525
|
+
}
|
|
7850
8526
|
|
|
7851
|
-
|
|
8527
|
+
const latestOwnerChangeAtMs = Number(owner.createdAtMs) || 0;
|
|
8528
|
+
const normalizedFileRef = cleanTimelineFileRef(change.fileRef || change.newFileRef || change.oldFileRef || "");
|
|
8529
|
+
const normalizedOldFileRef = cleanTimelineFileRef(change.oldFileRef || "");
|
|
8530
|
+
const normalizedNewFileRef = cleanTimelineFileRef(change.newFileRef || normalizedFileRef);
|
|
8531
|
+
let fileGroup = threadGroup.filesByRef.get(fileGroupKey);
|
|
7852
8532
|
if (!fileGroup) {
|
|
7853
8533
|
fileGroup = {
|
|
7854
8534
|
fileRef: normalizedFileRef,
|
|
7855
|
-
|
|
8535
|
+
oldFileRef: normalizedOldFileRef,
|
|
8536
|
+
newFileRef: normalizedNewFileRef,
|
|
8537
|
+
fileLabel:
|
|
8538
|
+
path.basename(
|
|
8539
|
+
normalizeTimelineFileEventType(change.changeType) === "delete"
|
|
8540
|
+
? normalizedOldFileRef || normalizedFileRef
|
|
8541
|
+
: normalizedNewFileRef || normalizedFileRef
|
|
8542
|
+
) ||
|
|
8543
|
+
normalizedNewFileRef ||
|
|
8544
|
+
normalizedOldFileRef ||
|
|
8545
|
+
normalizedFileRef,
|
|
8546
|
+
changeType: normalizeTimelineFileEventType(change.changeType),
|
|
7856
8547
|
fileEventTypes: new Set(),
|
|
7857
8548
|
addedLines: 0,
|
|
7858
8549
|
removedLines: 0,
|
|
7859
|
-
latestChangedAtMs:
|
|
8550
|
+
latestChangedAtMs: latestOwnerChangeAtMs,
|
|
7860
8551
|
sections: [],
|
|
7861
8552
|
};
|
|
7862
|
-
threadGroup.filesByRef.set(
|
|
8553
|
+
threadGroup.filesByRef.set(fileGroupKey, fileGroup);
|
|
7863
8554
|
}
|
|
7864
8555
|
|
|
7865
|
-
|
|
7866
|
-
|
|
8556
|
+
const changeType = normalizeTimelineFileEventType(change.changeType);
|
|
8557
|
+
if (changeType) {
|
|
8558
|
+
fileGroup.fileEventTypes.add(changeType);
|
|
7867
8559
|
}
|
|
7868
|
-
fileGroup.
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
8560
|
+
fileGroup.changeType = changeType || fileGroup.changeType;
|
|
8561
|
+
fileGroup.addedLines = Math.max(0, Number(change.diffAddedLines) || 0);
|
|
8562
|
+
fileGroup.removedLines = Math.max(0, Number(change.diffRemovedLines) || 0);
|
|
8563
|
+
fileGroup.latestChangedAtMs = latestOwnerChangeAtMs;
|
|
8564
|
+
fileGroup.fileRef = normalizedFileRef;
|
|
8565
|
+
fileGroup.oldFileRef = normalizedOldFileRef;
|
|
8566
|
+
fileGroup.newFileRef = normalizedNewFileRef;
|
|
8567
|
+
fileGroup.sections = [
|
|
8568
|
+
{
|
|
8569
|
+
createdAtMs: latestOwnerChangeAtMs,
|
|
8570
|
+
diffText: normalizeTimelineDiffText(change.diffText ?? ""),
|
|
8571
|
+
diffAvailable: change.diffAvailable === true || Boolean(change.diffText),
|
|
8572
|
+
diffSource: normalizeTimelineDiffSource(change.diffSource ?? ""),
|
|
8573
|
+
addedLines: Math.max(0, Number(change.diffAddedLines) || 0),
|
|
8574
|
+
removedLines: Math.max(0, Number(change.diffRemovedLines) || 0),
|
|
8575
|
+
fileEventType: changeType,
|
|
8576
|
+
},
|
|
8577
|
+
];
|
|
8578
|
+
|
|
8579
|
+
threadGroup.latestChangedAtMs = Math.max(threadGroup.latestChangedAtMs, latestOwnerChangeAtMs);
|
|
8580
|
+
threadGroup.diffAddedLines += Math.max(0, Number(change.diffAddedLines) || 0);
|
|
8581
|
+
threadGroup.diffRemovedLines += Math.max(0, Number(change.diffRemovedLines) || 0);
|
|
8582
|
+
|
|
8583
|
+
if (latestOwnerChangeAtMs > threadGroup.latestChangedAtMsForSummary) {
|
|
8584
|
+
threadGroup.latestChangedAtMsForSummary = latestOwnerChangeAtMs;
|
|
8585
|
+
threadGroup.latestChangeType = changeType || "";
|
|
8586
|
+
threadGroup.latestChangeFileRefs = [normalizedFileRef || normalizedNewFileRef || normalizedOldFileRef];
|
|
8587
|
+
} else if (latestOwnerChangeAtMs === (threadGroup.latestChangedAtMsForSummary || 0)) {
|
|
8588
|
+
if (changeType && threadGroup.latestChangeType && threadGroup.latestChangeType !== changeType) {
|
|
7892
8589
|
threadGroup.latestChangeType = "";
|
|
7893
|
-
} else if (!threadGroup.latestChangeType &&
|
|
7894
|
-
threadGroup.latestChangeType =
|
|
8590
|
+
} else if (!threadGroup.latestChangeType && changeType && threadGroup.latestChangeFileRefs.length === 0) {
|
|
8591
|
+
threadGroup.latestChangeType = changeType;
|
|
7895
8592
|
}
|
|
7896
|
-
|
|
7897
|
-
|
|
8593
|
+
const latestFileRef = normalizedFileRef || normalizedNewFileRef || normalizedOldFileRef;
|
|
8594
|
+
if (latestFileRef && !threadGroup.latestChangeFileRefs.includes(latestFileRef)) {
|
|
8595
|
+
threadGroup.latestChangeFileRefs.push(latestFileRef);
|
|
7898
8596
|
}
|
|
7899
8597
|
}
|
|
7900
8598
|
}
|
|
@@ -7905,12 +8603,15 @@ function buildDiffThreadGroups(runtime, state, config) {
|
|
|
7905
8603
|
const files = [...group.filesByRef.values()]
|
|
7906
8604
|
.map((fileGroup) => ({
|
|
7907
8605
|
fileRef: fileGroup.fileRef,
|
|
8606
|
+
oldFileRef: fileGroup.oldFileRef,
|
|
8607
|
+
newFileRef: fileGroup.newFileRef,
|
|
7908
8608
|
fileLabel: fileGroup.fileLabel,
|
|
8609
|
+
changeType: normalizeTimelineFileEventType(fileGroup.changeType),
|
|
7909
8610
|
fileEventTypes: [...fileGroup.fileEventTypes.values()],
|
|
7910
8611
|
addedLines: fileGroup.addedLines,
|
|
7911
8612
|
removedLines: fileGroup.removedLines,
|
|
7912
8613
|
latestChangedAtMs: fileGroup.latestChangedAtMs,
|
|
7913
|
-
sections: fileGroup.sections
|
|
8614
|
+
sections: fileGroup.sections,
|
|
7914
8615
|
}))
|
|
7915
8616
|
.sort((left, right) => Number(right.latestChangedAtMs ?? 0) - Number(left.latestChangedAtMs ?? 0));
|
|
7916
8617
|
|
|
@@ -7932,10 +8633,10 @@ function buildDiffThreadGroups(runtime, state, config) {
|
|
|
7932
8633
|
.sort((left, right) => Number(right.latestChangedAtMs ?? 0) - Number(left.latestChangedAtMs ?? 0));
|
|
7933
8634
|
}
|
|
7934
8635
|
|
|
7935
|
-
function buildInboxResponse(runtime, state, config, locale) {
|
|
8636
|
+
async function buildInboxResponse(runtime, state, config, locale) {
|
|
7936
8637
|
return {
|
|
7937
8638
|
pending: buildPendingInboxItems(runtime, state, config, locale),
|
|
7938
|
-
diff: buildDiffInboxItems(runtime, state, config, locale),
|
|
8639
|
+
diff: await buildDiffInboxItems(runtime, state, config, locale),
|
|
7939
8640
|
completed: buildCompletedInboxItems(runtime, state, config, locale),
|
|
7940
8641
|
};
|
|
7941
8642
|
}
|
|
@@ -8129,6 +8830,7 @@ function buildPendingApprovalDetail(runtime, approval, locale) {
|
|
|
8129
8830
|
const previousContext = buildPreviousApprovalContext(runtime, approval);
|
|
8130
8831
|
return {
|
|
8131
8832
|
kind: "approval",
|
|
8833
|
+
approvalKind: cleanText(approval.kind || ""),
|
|
8132
8834
|
token: approval.token,
|
|
8133
8835
|
title: formatLocalizedTitle(locale, "server.title.approval", approval.threadLabel),
|
|
8134
8836
|
threadLabel: approval.threadLabel || "",
|
|
@@ -8431,6 +9133,7 @@ function buildTimelineFileEventDetail(entry, locale) {
|
|
|
8431
9133
|
createdAtMs: Number(entry.createdAtMs) || 0,
|
|
8432
9134
|
messageHtml: renderMessageHtml(fileEventDetailCopy(locale, fileEventType), `<p>${escapeHtml(t(locale, "detail.detailUnavailable"))}</p>`),
|
|
8433
9135
|
fileRefs: normalizeTimelineFileRefs(entry.fileRefs ?? []),
|
|
9136
|
+
previousFileRefs: normalizeTimelineFileRefs(entry.previousFileRefs ?? []),
|
|
8434
9137
|
diffAvailable: Boolean(entry.diffAvailable),
|
|
8435
9138
|
diffText: normalizeTimelineDiffText(entry.diffText ?? ""),
|
|
8436
9139
|
diffSource: normalizeTimelineDiffSource(entry.diffSource ?? ""),
|
|
@@ -8459,7 +9162,10 @@ function buildDiffThreadDetail(group, locale) {
|
|
|
8459
9162
|
files: Array.isArray(group.files)
|
|
8460
9163
|
? group.files.map((fileGroup) => ({
|
|
8461
9164
|
fileRef: cleanTimelineFileRef(fileGroup.fileRef),
|
|
9165
|
+
oldFileRef: cleanTimelineFileRef(fileGroup.oldFileRef || ""),
|
|
9166
|
+
newFileRef: cleanTimelineFileRef(fileGroup.newFileRef || fileGroup.fileRef || ""),
|
|
8462
9167
|
fileLabel: cleanText(fileGroup.fileLabel || "") || path.basename(cleanTimelineFileRef(fileGroup.fileRef)) || cleanTimelineFileRef(fileGroup.fileRef),
|
|
9168
|
+
changeType: normalizeTimelineFileEventType(fileGroup.changeType ?? ""),
|
|
8463
9169
|
fileEventTypes: Array.isArray(fileGroup.fileEventTypes)
|
|
8464
9170
|
? fileGroup.fileEventTypes.map((value) => normalizeTimelineFileEventType(value)).filter(Boolean)
|
|
8465
9171
|
: [],
|
|
@@ -9021,9 +9727,9 @@ async function handleNativeApprovalDecision({ config, runtime, state, approval,
|
|
|
9021
9727
|
console.log(`[native-decision] ${approval.requestKey} | ${decision}`);
|
|
9022
9728
|
}
|
|
9023
9729
|
|
|
9024
|
-
function buildApiItemDetail({ config, runtime, state, kind, token, locale }) {
|
|
9730
|
+
async function buildApiItemDetail({ config, runtime, state, kind, token, locale }) {
|
|
9025
9731
|
if (kind === "diff_thread") {
|
|
9026
|
-
const group = buildDiffThreadGroups(runtime, state, config).find((entry) => entry.token === token);
|
|
9732
|
+
const group = (await buildDiffThreadGroups(runtime, state, config)).find((entry) => entry.token === token);
|
|
9027
9733
|
return group ? buildDiffThreadDetail(group, locale) : null;
|
|
9028
9734
|
}
|
|
9029
9735
|
if (kind === "file_event") {
|
|
@@ -9551,7 +10257,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
9551
10257
|
return;
|
|
9552
10258
|
}
|
|
9553
10259
|
const locale = resolveDeviceLocaleInfo(config, state, session.deviceId).locale;
|
|
9554
|
-
return writeJson(res, 200, buildInboxResponse(runtime, state, config, locale));
|
|
10260
|
+
return writeJson(res, 200, await buildInboxResponse(runtime, state, config, locale));
|
|
9555
10261
|
}
|
|
9556
10262
|
|
|
9557
10263
|
if (url.pathname === "/api/timeline" && req.method === "GET") {
|
|
@@ -9600,7 +10306,7 @@ function createNativeApprovalServer({ config, runtime, state }) {
|
|
|
9600
10306
|
const kind = decodeURIComponent(apiItemMatch[1]);
|
|
9601
10307
|
const token = decodeURIComponent(apiItemMatch[2]);
|
|
9602
10308
|
const locale = resolveDeviceLocaleInfo(config, state, session.deviceId).locale;
|
|
9603
|
-
const detail = buildApiItemDetail({ config, runtime, state, kind, token, locale });
|
|
10309
|
+
const detail = await buildApiItemDetail({ config, runtime, state, kind, token, locale });
|
|
9604
10310
|
if (!detail) {
|
|
9605
10311
|
return writeJson(res, 404, { error: "item-not-found" });
|
|
9606
10312
|
}
|