viveworker 0.1.6 → 0.1.7
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 +2 -2
- package/scripts/viveworker-bridge.mjs +681 -27
- package/web/app.css +59 -4
- package/web/app.js +62 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Local iPhone companion for Codex Desktop approvals, plan checks, questions, and notifications on your LAN.",
|
|
5
5
|
"author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"companion-app",
|
|
19
19
|
"vivecoding"
|
|
20
20
|
],
|
|
21
|
-
"homepage": "https://
|
|
21
|
+
"homepage": "https://viveworker.com/",
|
|
22
22
|
"repository": "viveworker-dev/viveworker",
|
|
23
23
|
"type": "module",
|
|
24
24
|
"bin": {
|
|
@@ -64,6 +64,7 @@ const runtime = {
|
|
|
64
64
|
threadOwnerClientIds: new Map(),
|
|
65
65
|
nativeApprovalsByToken: new Map(),
|
|
66
66
|
nativeApprovalsByRequestKey: new Map(),
|
|
67
|
+
fileApprovalDeltasById: new Map(),
|
|
67
68
|
planRequestsByToken: new Map(),
|
|
68
69
|
planRequestsByRequestKey: new Map(),
|
|
69
70
|
planRequestsByTurnKey: new Map(),
|
|
@@ -897,6 +898,16 @@ function buildUnifiedDiffFromApplyPatchSection(section) {
|
|
|
897
898
|
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
898
899
|
}
|
|
899
900
|
|
|
901
|
+
if (section.kind === "delete") {
|
|
902
|
+
const removedCount = bodyLines.filter((line) => line.startsWith("-")).length;
|
|
903
|
+
diffLines.push("deleted file mode 100644");
|
|
904
|
+
diffLines.push(`--- ${diffPathForSide(fileRef, "a")}`);
|
|
905
|
+
diffLines.push("+++ /dev/null");
|
|
906
|
+
diffLines.push(`@@ -1,${Math.max(removedCount, 1)} +0,0 @@`);
|
|
907
|
+
diffLines.push(...bodyLines);
|
|
908
|
+
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
909
|
+
}
|
|
910
|
+
|
|
900
911
|
return "";
|
|
901
912
|
}
|
|
902
913
|
|
|
@@ -1025,6 +1036,82 @@ async function buildFileEventDiff({ fileState, callId, fileRefs, fileEventType,
|
|
|
1025
1036
|
};
|
|
1026
1037
|
}
|
|
1027
1038
|
|
|
1039
|
+
function approvalRequestCallId(params) {
|
|
1040
|
+
if (!isPlainObject(params)) {
|
|
1041
|
+
return "";
|
|
1042
|
+
}
|
|
1043
|
+
return cleanText(params.itemId ?? params.item_id ?? params.callId ?? params.call_id ?? "");
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
async function ensureRolloutFileState(runtime, threadId, rolloutFilePath) {
|
|
1047
|
+
const normalizedRolloutFilePath = cleanText(rolloutFilePath || "");
|
|
1048
|
+
if (!normalizedRolloutFilePath) {
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
let fileState = runtime.fileStates.get(normalizedRolloutFilePath);
|
|
1053
|
+
if (fileState) {
|
|
1054
|
+
return fileState;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
fileState = {
|
|
1058
|
+
offset: 0,
|
|
1059
|
+
remainder: "",
|
|
1060
|
+
threadId: cleanText(threadId || ""),
|
|
1061
|
+
cwd: await findRolloutThreadCwd(runtime, threadId || ""),
|
|
1062
|
+
applyPatchInputsByCallId: new Map(),
|
|
1063
|
+
startupCutoffMs: 0,
|
|
1064
|
+
skipPartialLine: false,
|
|
1065
|
+
};
|
|
1066
|
+
runtime.fileStates.set(normalizedRolloutFilePath, fileState);
|
|
1067
|
+
return fileState;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
async function buildApprovalPayloadDeltaFromRollout({ runtime, conversationId, params }) {
|
|
1071
|
+
const callId = approvalRequestCallId(params);
|
|
1072
|
+
const threadId = cleanText(conversationId || params?.threadId || params?.thread_id || "");
|
|
1073
|
+
if (!callId || !threadId) {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const rolloutFilePath = findRolloutFileForThread(runtime, threadId);
|
|
1078
|
+
if (!rolloutFilePath) {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
const fileState = await ensureRolloutFileState(runtime, threadId, rolloutFilePath);
|
|
1083
|
+
if (!fileState) {
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const storedPatch = await findStoredApplyPatchInput({
|
|
1088
|
+
fileState,
|
|
1089
|
+
callId,
|
|
1090
|
+
rolloutFilePath,
|
|
1091
|
+
});
|
|
1092
|
+
if (!storedPatch?.inputText) {
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const sections = parseApplyPatchSections(storedPatch.inputText);
|
|
1097
|
+
const fileRefs = normalizeTimelineFileRefs(sections.map((section) => section?.fileRef).filter(Boolean));
|
|
1098
|
+
const diffText = normalizeTimelineDiffText(
|
|
1099
|
+
sections
|
|
1100
|
+
.map((section) => buildUnifiedDiffFromApplyPatchSection(section))
|
|
1101
|
+
.filter(Boolean)
|
|
1102
|
+
.join("\n\n")
|
|
1103
|
+
);
|
|
1104
|
+
const counts = diffLineCounts(diffText);
|
|
1105
|
+
return {
|
|
1106
|
+
fileRefs,
|
|
1107
|
+
diffText,
|
|
1108
|
+
diffAvailable: Boolean(diffText),
|
|
1109
|
+
diffSource: diffText ? "apply_patch" : "",
|
|
1110
|
+
diffAddedLines: counts.addedLines,
|
|
1111
|
+
diffRemovedLines: counts.removedLines,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1028
1115
|
function normalizeUnifiedDiffSectionFileRef(value) {
|
|
1029
1116
|
const normalized = cleanText(value || "");
|
|
1030
1117
|
if (!normalized || normalized === "/dev/null") {
|
|
@@ -1152,6 +1239,11 @@ function normalizeHistoryItem(raw) {
|
|
|
1152
1239
|
messageText,
|
|
1153
1240
|
imagePaths: normalizeTimelineImagePaths(raw.imagePaths ?? raw.localImagePaths ?? []),
|
|
1154
1241
|
fileRefs: normalizeTimelineFileRefs(raw.fileRefs ?? extractTimelineFileRefs(messageText)),
|
|
1242
|
+
diffText: normalizeTimelineDiffText(raw.diffText ?? ""),
|
|
1243
|
+
diffSource: normalizeTimelineDiffSource(raw.diffSource ?? ""),
|
|
1244
|
+
diffAvailable: raw.diffAvailable === true || Boolean(raw.diffText),
|
|
1245
|
+
diffAddedLines: Math.max(0, Number(raw.diffAddedLines) || 0),
|
|
1246
|
+
diffRemovedLines: Math.max(0, Number(raw.diffRemovedLines) || 0),
|
|
1155
1247
|
outcome,
|
|
1156
1248
|
createdAtMs,
|
|
1157
1249
|
readOnly: raw.readOnly !== false,
|
|
@@ -1505,6 +1597,12 @@ function recordActionHistoryItem({
|
|
|
1505
1597
|
threadLabel = "",
|
|
1506
1598
|
messageText,
|
|
1507
1599
|
summary,
|
|
1600
|
+
fileRefs = [],
|
|
1601
|
+
diffText = "",
|
|
1602
|
+
diffSource = "",
|
|
1603
|
+
diffAvailable = false,
|
|
1604
|
+
diffAddedLines = 0,
|
|
1605
|
+
diffRemovedLines = 0,
|
|
1508
1606
|
outcome = "",
|
|
1509
1607
|
}) {
|
|
1510
1608
|
const item = {
|
|
@@ -1516,6 +1614,12 @@ function recordActionHistoryItem({
|
|
|
1516
1614
|
threadLabel,
|
|
1517
1615
|
summary,
|
|
1518
1616
|
messageText,
|
|
1617
|
+
fileRefs,
|
|
1618
|
+
diffText,
|
|
1619
|
+
diffSource,
|
|
1620
|
+
diffAvailable,
|
|
1621
|
+
diffAddedLines,
|
|
1622
|
+
diffRemovedLines,
|
|
1519
1623
|
outcome,
|
|
1520
1624
|
createdAtMs: Date.now(),
|
|
1521
1625
|
readOnly: true,
|
|
@@ -3361,18 +3465,23 @@ async function syncNativeApprovals({ config, runtime, state, conversationId, pre
|
|
|
3361
3465
|
for (const [requestKey, request] of nextKeys) {
|
|
3362
3466
|
const existing = runtime.nativeApprovalsByRequestKey.get(requestKey);
|
|
3363
3467
|
if (existing) {
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3468
|
+
const changed = await refreshNativeApprovalFromRequest({
|
|
3469
|
+
config,
|
|
3470
|
+
runtime,
|
|
3471
|
+
conversationId,
|
|
3472
|
+
request,
|
|
3473
|
+
approval: existing,
|
|
3474
|
+
});
|
|
3475
|
+
if (changed) {
|
|
3476
|
+
const fileKeys = isPlainObject(existing.rawParams) ? Object.keys(existing.rawParams).join(",") : "";
|
|
3477
|
+
console.log(
|
|
3478
|
+
`[native-approval-refresh] ${requestKey} | files=${normalizeTimelineFileRefs(existing.fileRefs ?? []).length} | diff=${existing.diffAvailable || Boolean(existing.diffText) ? "yes" : "no"} | keys=${fileKeys || "none"}`
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3372
3481
|
continue;
|
|
3373
3482
|
}
|
|
3374
3483
|
|
|
3375
|
-
const approval = createNativeApproval({
|
|
3484
|
+
const approval = await createNativeApproval({
|
|
3376
3485
|
config,
|
|
3377
3486
|
runtime,
|
|
3378
3487
|
conversationId,
|
|
@@ -3385,6 +3494,14 @@ async function syncNativeApprovals({ config, runtime, state, conversationId, pre
|
|
|
3385
3494
|
runtime.nativeApprovalsByRequestKey.set(requestKey, approval);
|
|
3386
3495
|
runtime.nativeApprovalsByToken.set(approval.token, approval);
|
|
3387
3496
|
|
|
3497
|
+
if (previousKeys.has(requestKey)) {
|
|
3498
|
+
const fileKeys = isPlainObject(approval.rawParams) ? Object.keys(approval.rawParams).join(",") : "";
|
|
3499
|
+
console.log(
|
|
3500
|
+
`[native-approval-recovered] ${requestKey} | files=${normalizeTimelineFileRefs(approval.fileRefs ?? []).length} | diff=${approval.diffAvailable || Boolean(approval.diffText) ? "yes" : "no"} | keys=${fileKeys || "none"}`
|
|
3501
|
+
);
|
|
3502
|
+
continue;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3388
3505
|
try {
|
|
3389
3506
|
await publishNtfy(config, {
|
|
3390
3507
|
kind: "native_approval",
|
|
@@ -3395,7 +3512,10 @@ async function syncNativeApprovals({ config, runtime, state, conversationId, pre
|
|
|
3395
3512
|
clickUrl: approval.reviewUrl,
|
|
3396
3513
|
actions: buildNativeApprovalActions(approval.reviewUrl, config.defaultLocale),
|
|
3397
3514
|
});
|
|
3398
|
-
|
|
3515
|
+
const fileKeys = isPlainObject(approval.rawParams) ? Object.keys(approval.rawParams).join(",") : "";
|
|
3516
|
+
console.log(
|
|
3517
|
+
`[native-approval] ${requestKey} | ${approval.title} | files=${normalizeTimelineFileRefs(approval.fileRefs ?? []).length} | diff=${approval.diffAvailable || Boolean(approval.diffText) ? "yes" : "no"} | keys=${fileKeys || "none"}`
|
|
3518
|
+
);
|
|
3399
3519
|
} catch (error) {
|
|
3400
3520
|
console.error(`[native-approval-error] ${requestKey} | ${error.message}`);
|
|
3401
3521
|
}
|
|
@@ -3773,7 +3893,29 @@ async function syncGenericUserInputRequests({
|
|
|
3773
3893
|
}
|
|
3774
3894
|
}
|
|
3775
3895
|
|
|
3776
|
-
function createNativeApproval({ config, runtime, conversationId, request, now = Date.now() }) {
|
|
3896
|
+
async function createNativeApproval({ config, runtime, conversationId, request, now = Date.now() }) {
|
|
3897
|
+
const token = crypto.randomBytes(18).toString("hex");
|
|
3898
|
+
const payload = await buildNativeApprovalPayload({
|
|
3899
|
+
config,
|
|
3900
|
+
runtime,
|
|
3901
|
+
conversationId,
|
|
3902
|
+
request,
|
|
3903
|
+
token,
|
|
3904
|
+
});
|
|
3905
|
+
if (!payload) {
|
|
3906
|
+
return null;
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
return {
|
|
3910
|
+
token,
|
|
3911
|
+
...payload,
|
|
3912
|
+
createdAtMs: now,
|
|
3913
|
+
resolved: false,
|
|
3914
|
+
resolving: false,
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
async function buildNativeApprovalPayload({ config, runtime, conversationId, request, token }) {
|
|
3777
3919
|
const kind = nativeApprovalKind(request.method);
|
|
3778
3920
|
if (!kind) {
|
|
3779
3921
|
return null;
|
|
@@ -3783,23 +3925,40 @@ function createNativeApproval({ config, runtime, conversationId, request, now =
|
|
|
3783
3925
|
if (requestId == null) {
|
|
3784
3926
|
return null;
|
|
3785
3927
|
}
|
|
3928
|
+
const requestKey = nativeRequestKey(conversationId, requestId);
|
|
3786
3929
|
|
|
3787
3930
|
const threadLabel = getNativeThreadLabel({
|
|
3788
3931
|
runtime,
|
|
3789
3932
|
conversationId,
|
|
3790
3933
|
cwd: request.params?.cwd ?? request.params?.grantRoot ?? "",
|
|
3791
3934
|
});
|
|
3792
|
-
const token = crypto.randomBytes(18).toString("hex");
|
|
3793
3935
|
const reviewUrl = `${config.nativeApprovalPublicBaseUrl}/native-approvals/${token}`;
|
|
3794
3936
|
const title = formatTitle(config.approvalTitle, threadLabel);
|
|
3795
3937
|
const rawParams = isPlainObject(request.params) ? cloneJson(request.params) : {};
|
|
3796
|
-
const
|
|
3797
|
-
const
|
|
3798
|
-
const
|
|
3938
|
+
const approvalIds = kind === "file" ? collectFileApprovalCorrelationIds(rawParams, requestId) : [];
|
|
3939
|
+
const requestDelta = kind === "file" ? extractApprovalPayloadDelta(rawParams, "approval_request") : null;
|
|
3940
|
+
const cachedDelta =
|
|
3941
|
+
kind === "file"
|
|
3942
|
+
? approvalIds.reduce(
|
|
3943
|
+
(merged, approvalId) => mergeApprovalPayloadDelta(merged, runtime.fileApprovalDeltasById.get(approvalId) ?? null),
|
|
3944
|
+
null
|
|
3945
|
+
)
|
|
3946
|
+
: null;
|
|
3947
|
+
const rolloutDelta =
|
|
3948
|
+
kind === "file"
|
|
3949
|
+
? await buildApprovalPayloadDeltaFromRollout({
|
|
3950
|
+
runtime,
|
|
3951
|
+
conversationId,
|
|
3952
|
+
params: rawParams,
|
|
3953
|
+
})
|
|
3954
|
+
: null;
|
|
3955
|
+
const mergedDelta =
|
|
3956
|
+
kind === "file"
|
|
3957
|
+
? mergeApprovalPayloadDelta(mergeApprovalPayloadDelta(requestDelta, cachedDelta), rolloutDelta)
|
|
3958
|
+
: null;
|
|
3799
3959
|
const messageText = formatNativeApprovalMessage(kind, rawParams, config.defaultLocale);
|
|
3800
3960
|
|
|
3801
3961
|
return {
|
|
3802
|
-
token,
|
|
3803
3962
|
kind,
|
|
3804
3963
|
title,
|
|
3805
3964
|
threadLabel,
|
|
@@ -3807,21 +3966,95 @@ function createNativeApproval({ config, runtime, conversationId, request, now =
|
|
|
3807
3966
|
reviewUrl,
|
|
3808
3967
|
conversationId,
|
|
3809
3968
|
requestId,
|
|
3810
|
-
requestKey
|
|
3969
|
+
requestKey,
|
|
3811
3970
|
ownerClientId: runtime.threadOwnerClientIds.get(conversationId) ?? null,
|
|
3971
|
+
approvalIds,
|
|
3812
3972
|
rawParams,
|
|
3813
|
-
fileRefs,
|
|
3814
|
-
diffText,
|
|
3815
|
-
diffAvailable: Boolean(
|
|
3816
|
-
diffSource:
|
|
3817
|
-
diffAddedLines:
|
|
3818
|
-
diffRemovedLines:
|
|
3819
|
-
createdAtMs: now,
|
|
3820
|
-
resolved: false,
|
|
3821
|
-
resolving: false,
|
|
3973
|
+
fileRefs: normalizeTimelineFileRefs(mergedDelta?.fileRefs ?? []),
|
|
3974
|
+
diffText: normalizeTimelineDiffText(mergedDelta?.diffText ?? ""),
|
|
3975
|
+
diffAvailable: Boolean(mergedDelta?.diffAvailable),
|
|
3976
|
+
diffSource: normalizeTimelineDiffSource(mergedDelta?.diffSource ?? ""),
|
|
3977
|
+
diffAddedLines: Math.max(0, Number(mergedDelta?.diffAddedLines) || 0),
|
|
3978
|
+
diffRemovedLines: Math.max(0, Number(mergedDelta?.diffRemovedLines) || 0),
|
|
3822
3979
|
};
|
|
3823
3980
|
}
|
|
3824
3981
|
|
|
3982
|
+
async function refreshNativeApprovalFromRequest({ config, runtime, conversationId, request, approval }) {
|
|
3983
|
+
if (!approval?.token) {
|
|
3984
|
+
return false;
|
|
3985
|
+
}
|
|
3986
|
+
const payload = await buildNativeApprovalPayload({
|
|
3987
|
+
config,
|
|
3988
|
+
runtime,
|
|
3989
|
+
conversationId,
|
|
3990
|
+
request,
|
|
3991
|
+
token: approval.token,
|
|
3992
|
+
});
|
|
3993
|
+
if (!payload) {
|
|
3994
|
+
return false;
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
const before = JSON.stringify({
|
|
3998
|
+
kind: approval.kind,
|
|
3999
|
+
title: approval.title,
|
|
4000
|
+
threadLabel: approval.threadLabel,
|
|
4001
|
+
messageText: approval.messageText,
|
|
4002
|
+
reviewUrl: approval.reviewUrl,
|
|
4003
|
+
conversationId: approval.conversationId,
|
|
4004
|
+
requestId: approval.requestId,
|
|
4005
|
+
requestKey: approval.requestKey,
|
|
4006
|
+
ownerClientId: approval.ownerClientId,
|
|
4007
|
+
approvalIds: approval.approvalIds,
|
|
4008
|
+
rawParams: approval.rawParams,
|
|
4009
|
+
fileRefs: normalizeTimelineFileRefs(approval.fileRefs ?? []),
|
|
4010
|
+
diffText: normalizeTimelineDiffText(approval.diffText ?? ""),
|
|
4011
|
+
diffAvailable: Boolean(approval.diffAvailable),
|
|
4012
|
+
diffSource: normalizeTimelineDiffSource(approval.diffSource ?? ""),
|
|
4013
|
+
diffAddedLines: Math.max(0, Number(approval.diffAddedLines) || 0),
|
|
4014
|
+
diffRemovedLines: Math.max(0, Number(approval.diffRemovedLines) || 0),
|
|
4015
|
+
});
|
|
4016
|
+
|
|
4017
|
+
approval.kind = payload.kind;
|
|
4018
|
+
approval.title = payload.title;
|
|
4019
|
+
approval.threadLabel = payload.threadLabel;
|
|
4020
|
+
approval.messageText = payload.messageText;
|
|
4021
|
+
approval.reviewUrl = payload.reviewUrl;
|
|
4022
|
+
approval.conversationId = payload.conversationId;
|
|
4023
|
+
approval.requestId = payload.requestId;
|
|
4024
|
+
approval.requestKey = payload.requestKey;
|
|
4025
|
+
approval.ownerClientId = payload.ownerClientId;
|
|
4026
|
+
approval.approvalIds = payload.approvalIds;
|
|
4027
|
+
approval.rawParams = payload.rawParams;
|
|
4028
|
+
approval.fileRefs = payload.fileRefs;
|
|
4029
|
+
approval.diffText = payload.diffText;
|
|
4030
|
+
approval.diffAvailable = payload.diffAvailable;
|
|
4031
|
+
approval.diffSource = payload.diffSource;
|
|
4032
|
+
approval.diffAddedLines = payload.diffAddedLines;
|
|
4033
|
+
approval.diffRemovedLines = payload.diffRemovedLines;
|
|
4034
|
+
|
|
4035
|
+
const after = JSON.stringify({
|
|
4036
|
+
kind: approval.kind,
|
|
4037
|
+
title: approval.title,
|
|
4038
|
+
threadLabel: approval.threadLabel,
|
|
4039
|
+
messageText: approval.messageText,
|
|
4040
|
+
reviewUrl: approval.reviewUrl,
|
|
4041
|
+
conversationId: approval.conversationId,
|
|
4042
|
+
requestId: approval.requestId,
|
|
4043
|
+
requestKey: approval.requestKey,
|
|
4044
|
+
ownerClientId: approval.ownerClientId,
|
|
4045
|
+
approvalIds: approval.approvalIds,
|
|
4046
|
+
rawParams: approval.rawParams,
|
|
4047
|
+
fileRefs: normalizeTimelineFileRefs(approval.fileRefs ?? []),
|
|
4048
|
+
diffText: normalizeTimelineDiffText(approval.diffText ?? ""),
|
|
4049
|
+
diffAvailable: Boolean(approval.diffAvailable),
|
|
4050
|
+
diffSource: normalizeTimelineDiffSource(approval.diffSource ?? ""),
|
|
4051
|
+
diffAddedLines: Math.max(0, Number(approval.diffAddedLines) || 0),
|
|
4052
|
+
diffRemovedLines: Math.max(0, Number(approval.diffRemovedLines) || 0),
|
|
4053
|
+
});
|
|
4054
|
+
|
|
4055
|
+
return before !== after;
|
|
4056
|
+
}
|
|
4057
|
+
|
|
3825
4058
|
function createPlanQuestionRequest({ runtime, conversationId, request, sourceClientId, now = Date.now() }) {
|
|
3826
4059
|
if (!isPlanQuestionRequest(request)) {
|
|
3827
4060
|
return null;
|
|
@@ -5482,6 +5715,11 @@ class NativeIpcClient {
|
|
|
5482
5715
|
return;
|
|
5483
5716
|
}
|
|
5484
5717
|
|
|
5718
|
+
if (message.method === "item/fileChange/outputDelta") {
|
|
5719
|
+
this.handleFileChangeOutputDelta(message);
|
|
5720
|
+
return;
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5485
5723
|
if (isUserInputRequestedBroadcastMethod(message.method)) {
|
|
5486
5724
|
await this.handleUserInputRequested(message);
|
|
5487
5725
|
}
|
|
@@ -5540,6 +5778,15 @@ class NativeIpcClient {
|
|
|
5540
5778
|
await this.onUserInputRequested(normalized);
|
|
5541
5779
|
}
|
|
5542
5780
|
|
|
5781
|
+
handleFileChangeOutputDelta(message) {
|
|
5782
|
+
const params = isPlainObject(message?.params) ? message.params : {};
|
|
5783
|
+
if (!rememberFileApprovalDelta(this.runtime, params)) {
|
|
5784
|
+
return;
|
|
5785
|
+
}
|
|
5786
|
+
const approvalIds = collectFileApprovalCorrelationIds(params);
|
|
5787
|
+
console.log(`[ipc-file-change-delta] approvalIds=${approvalIds.join(",") || "unknown"}`);
|
|
5788
|
+
}
|
|
5789
|
+
|
|
5543
5790
|
write(message) {
|
|
5544
5791
|
if (!this.socket) {
|
|
5545
5792
|
return;
|
|
@@ -5948,10 +6195,20 @@ function extractApprovalFileRefs(params) {
|
|
|
5948
6195
|
const candidateKeys = new Set([
|
|
5949
6196
|
"file",
|
|
5950
6197
|
"files",
|
|
6198
|
+
"fileChange",
|
|
6199
|
+
"fileChanges",
|
|
6200
|
+
"change",
|
|
6201
|
+
"changes",
|
|
5951
6202
|
"path",
|
|
5952
6203
|
"paths",
|
|
5953
6204
|
"filePath",
|
|
5954
6205
|
"filePaths",
|
|
6206
|
+
"filename",
|
|
6207
|
+
"filenames",
|
|
6208
|
+
"fileName",
|
|
6209
|
+
"fileNames",
|
|
6210
|
+
"file_name",
|
|
6211
|
+
"file_names",
|
|
5955
6212
|
"fileRef",
|
|
5956
6213
|
"fileRefs",
|
|
5957
6214
|
"updatedFile",
|
|
@@ -5962,6 +6219,16 @@ function extractApprovalFileRefs(params) {
|
|
|
5962
6219
|
"targetFiles",
|
|
5963
6220
|
"touchedFile",
|
|
5964
6221
|
"touchedFiles",
|
|
6222
|
+
"relativePath",
|
|
6223
|
+
"relativePaths",
|
|
6224
|
+
"oldPath",
|
|
6225
|
+
"newPath",
|
|
6226
|
+
"old_path",
|
|
6227
|
+
"new_path",
|
|
6228
|
+
"sourcePath",
|
|
6229
|
+
"sourcePaths",
|
|
6230
|
+
"destinationPath",
|
|
6231
|
+
"destinationPaths",
|
|
5965
6232
|
]);
|
|
5966
6233
|
|
|
5967
6234
|
function visit(value, parentKey = "", depth = 0) {
|
|
@@ -5989,7 +6256,23 @@ function extractApprovalFileRefs(params) {
|
|
|
5989
6256
|
}
|
|
5990
6257
|
|
|
5991
6258
|
if (candidateKeys.has(normalizedParentKey)) {
|
|
5992
|
-
const directRef = cleanText(
|
|
6259
|
+
const directRef = cleanText(
|
|
6260
|
+
value.fileRef ??
|
|
6261
|
+
value.filePath ??
|
|
6262
|
+
value.path ??
|
|
6263
|
+
value.filename ??
|
|
6264
|
+
value.fileName ??
|
|
6265
|
+
value.file_name ??
|
|
6266
|
+
value.relativePath ??
|
|
6267
|
+
value.oldPath ??
|
|
6268
|
+
value.newPath ??
|
|
6269
|
+
value.old_path ??
|
|
6270
|
+
value.new_path ??
|
|
6271
|
+
value.sourcePath ??
|
|
6272
|
+
value.destinationPath ??
|
|
6273
|
+
value.name ??
|
|
6274
|
+
""
|
|
6275
|
+
);
|
|
5993
6276
|
if (directRef) {
|
|
5994
6277
|
refs.push(directRef);
|
|
5995
6278
|
}
|
|
@@ -6007,6 +6290,144 @@ function extractApprovalFileRefs(params) {
|
|
|
6007
6290
|
return normalizeTimelineFileRefs(refs);
|
|
6008
6291
|
}
|
|
6009
6292
|
|
|
6293
|
+
function buildUnifiedDiffFromBeforeAfter({ fileRef = "", beforeText = "", afterText = "" }) {
|
|
6294
|
+
const normalizedFileRef = cleanTimelineFileRef(fileRef);
|
|
6295
|
+
if (!normalizedFileRef) {
|
|
6296
|
+
return "";
|
|
6297
|
+
}
|
|
6298
|
+
const before = String(beforeText ?? "");
|
|
6299
|
+
const after = String(afterText ?? "");
|
|
6300
|
+
if (!before && !after) {
|
|
6301
|
+
return "";
|
|
6302
|
+
}
|
|
6303
|
+
const beforeLines = before.replace(/\r\n/gu, "\n").split("\n");
|
|
6304
|
+
const afterLines = after.replace(/\r\n/gu, "\n").split("\n");
|
|
6305
|
+
const diffLines = [`diff --git ${diffPathForSide(normalizedFileRef, "a")} ${diffPathForSide(normalizedFileRef, "b")}`];
|
|
6306
|
+
|
|
6307
|
+
if (!before && after) {
|
|
6308
|
+
diffLines.push("new file mode 100644");
|
|
6309
|
+
diffLines.push("--- /dev/null");
|
|
6310
|
+
diffLines.push(`+++ ${diffPathForSide(normalizedFileRef, "b")}`);
|
|
6311
|
+
diffLines.push(`@@ -0,0 +1,${Math.max(afterLines.length, 1)} @@`);
|
|
6312
|
+
diffLines.push(...afterLines.map((line) => `+${line}`));
|
|
6313
|
+
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
diffLines.push(`--- ${diffPathForSide(normalizedFileRef, "a")}`);
|
|
6317
|
+
diffLines.push(`+++ ${diffPathForSide(normalizedFileRef, "b")}`);
|
|
6318
|
+
diffLines.push(`@@ -1,${Math.max(beforeLines.length, 1)} +1,${Math.max(afterLines.length, 1)} @@`);
|
|
6319
|
+
diffLines.push(...beforeLines.map((line) => `-${line}`));
|
|
6320
|
+
diffLines.push(...afterLines.map((line) => `+${line}`));
|
|
6321
|
+
return normalizeTimelineDiffText(diffLines.join("\n"));
|
|
6322
|
+
}
|
|
6323
|
+
|
|
6324
|
+
function extractStructuredApprovalDiffText(value, fallbackFileRefs = []) {
|
|
6325
|
+
const normalizedFallbackRefs = normalizeTimelineFileRefs(fallbackFileRefs);
|
|
6326
|
+
if (typeof value === "string") {
|
|
6327
|
+
return normalizeTimelineDiffText(value);
|
|
6328
|
+
}
|
|
6329
|
+
|
|
6330
|
+
if (Array.isArray(value)) {
|
|
6331
|
+
const sections = value
|
|
6332
|
+
.map((item) => extractStructuredApprovalDiffText(item, normalizedFallbackRefs))
|
|
6333
|
+
.filter(Boolean);
|
|
6334
|
+
return normalizeTimelineDiffText(sections.join("\n\n"));
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6337
|
+
if (!isPlainObject(value)) {
|
|
6338
|
+
return "";
|
|
6339
|
+
}
|
|
6340
|
+
|
|
6341
|
+
const explicitDiff =
|
|
6342
|
+
extractStructuredApprovalDiffText(
|
|
6343
|
+
value.diff ??
|
|
6344
|
+
value.patch ??
|
|
6345
|
+
value.patchText ??
|
|
6346
|
+
value.diffText ??
|
|
6347
|
+
value.unifiedDiff ??
|
|
6348
|
+
value.unified_diff ??
|
|
6349
|
+
value.unifiedPatch ??
|
|
6350
|
+
value.unified_patch ??
|
|
6351
|
+
value.unifiedPatchText ??
|
|
6352
|
+
value.unified_patch_text ??
|
|
6353
|
+
value.text ??
|
|
6354
|
+
value.value ??
|
|
6355
|
+
value.content ??
|
|
6356
|
+
value.delta ??
|
|
6357
|
+
value.output ??
|
|
6358
|
+
value.fileChanges ??
|
|
6359
|
+
value.fileChange ??
|
|
6360
|
+
value.changes ??
|
|
6361
|
+
value.change ??
|
|
6362
|
+
null,
|
|
6363
|
+
normalizedFallbackRefs
|
|
6364
|
+
) || "";
|
|
6365
|
+
if (explicitDiff) {
|
|
6366
|
+
return explicitDiff;
|
|
6367
|
+
}
|
|
6368
|
+
|
|
6369
|
+
const fileRef =
|
|
6370
|
+
cleanTimelineFileRef(
|
|
6371
|
+
value.fileRef ??
|
|
6372
|
+
value.filePath ??
|
|
6373
|
+
value.path ??
|
|
6374
|
+
value.filename ??
|
|
6375
|
+
value.fileName ??
|
|
6376
|
+
value.file_name ??
|
|
6377
|
+
value.relativePath ??
|
|
6378
|
+
value.name ??
|
|
6379
|
+
value.targetFile ??
|
|
6380
|
+
value.newPath ??
|
|
6381
|
+
value.oldPath ??
|
|
6382
|
+
value.new_path ??
|
|
6383
|
+
value.old_path ??
|
|
6384
|
+
value.sourcePath ??
|
|
6385
|
+
value.destinationPath ??
|
|
6386
|
+
normalizedFallbackRefs[0] ??
|
|
6387
|
+
""
|
|
6388
|
+
) || normalizedFallbackRefs[0] || "";
|
|
6389
|
+
const beforeText =
|
|
6390
|
+
value.before ??
|
|
6391
|
+
value.beforeText ??
|
|
6392
|
+
value.oldText ??
|
|
6393
|
+
value.old_text ??
|
|
6394
|
+
value.originalText ??
|
|
6395
|
+
value.original_text ??
|
|
6396
|
+
value.previousText ??
|
|
6397
|
+
value.previous_text ??
|
|
6398
|
+
value.contentBefore ??
|
|
6399
|
+
value.beforeContent ??
|
|
6400
|
+
value.oldContent ??
|
|
6401
|
+
value.old_content ??
|
|
6402
|
+
"";
|
|
6403
|
+
const afterText =
|
|
6404
|
+
value.after ??
|
|
6405
|
+
value.afterText ??
|
|
6406
|
+
value.newText ??
|
|
6407
|
+
value.new_text ??
|
|
6408
|
+
value.updatedText ??
|
|
6409
|
+
value.updated_text ??
|
|
6410
|
+
value.currentText ??
|
|
6411
|
+
value.current_text ??
|
|
6412
|
+
value.contentAfter ??
|
|
6413
|
+
value.afterContent ??
|
|
6414
|
+
value.newContent ??
|
|
6415
|
+
value.new_content ??
|
|
6416
|
+
"";
|
|
6417
|
+
const beforeAfterDiff = buildUnifiedDiffFromBeforeAfter({ fileRef, beforeText, afterText });
|
|
6418
|
+
if (beforeAfterDiff) {
|
|
6419
|
+
return beforeAfterDiff;
|
|
6420
|
+
}
|
|
6421
|
+
|
|
6422
|
+
for (const child of Object.values(value)) {
|
|
6423
|
+
const nested = extractStructuredApprovalDiffText(child, fileRef ? [fileRef] : normalizedFallbackRefs);
|
|
6424
|
+
if (nested) {
|
|
6425
|
+
return nested;
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
return "";
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6010
6431
|
function extractApprovalDiffText(params, fileRefs = []) {
|
|
6011
6432
|
if (!isPlainObject(params)) {
|
|
6012
6433
|
return "";
|
|
@@ -6018,8 +6439,13 @@ function extractApprovalDiffText(params, fileRefs = []) {
|
|
|
6018
6439
|
"patch",
|
|
6019
6440
|
"patchText",
|
|
6020
6441
|
"unifiedDiff",
|
|
6442
|
+
"unified_diff",
|
|
6021
6443
|
"diffPreview",
|
|
6022
6444
|
"diffString",
|
|
6445
|
+
"fileChange",
|
|
6446
|
+
"fileChanges",
|
|
6447
|
+
"change",
|
|
6448
|
+
"changes",
|
|
6023
6449
|
]);
|
|
6024
6450
|
|
|
6025
6451
|
let best = "";
|
|
@@ -6067,6 +6493,14 @@ function extractApprovalDiffText(params, fileRefs = []) {
|
|
|
6067
6493
|
return;
|
|
6068
6494
|
}
|
|
6069
6495
|
|
|
6496
|
+
const normalizedParentKey = cleanText(parentKey);
|
|
6497
|
+
if (candidateKeys.has(normalizedParentKey)) {
|
|
6498
|
+
considerText(extractStructuredApprovalDiffText(value, fileRefs));
|
|
6499
|
+
if (best) {
|
|
6500
|
+
return;
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
|
|
6070
6504
|
for (const [key, child] of Object.entries(value)) {
|
|
6071
6505
|
visit(child, key, depth + 1);
|
|
6072
6506
|
if (best) {
|
|
@@ -6094,6 +6528,214 @@ function extractApprovalDiffText(params, fileRefs = []) {
|
|
|
6094
6528
|
return filtered ? normalizeTimelineDiffText(filtered) : best;
|
|
6095
6529
|
}
|
|
6096
6530
|
|
|
6531
|
+
function extractApprovalPayloadDelta(params, diffSource = "approval_request") {
|
|
6532
|
+
const fileRefs = extractApprovalFileRefs(params);
|
|
6533
|
+
const diffText = extractApprovalDiffText(params, fileRefs);
|
|
6534
|
+
const diffCounts = diffLineCounts(diffText);
|
|
6535
|
+
return {
|
|
6536
|
+
fileRefs,
|
|
6537
|
+
diffText,
|
|
6538
|
+
diffAvailable: Boolean(diffText),
|
|
6539
|
+
diffSource: diffText ? diffSource : "",
|
|
6540
|
+
diffAddedLines: diffCounts.addedLines,
|
|
6541
|
+
diffRemovedLines: diffCounts.removedLines,
|
|
6542
|
+
};
|
|
6543
|
+
}
|
|
6544
|
+
|
|
6545
|
+
function mergeApprovalDiffTexts(existingText = "", nextText = "", fileRefs = []) {
|
|
6546
|
+
const existing = normalizeTimelineDiffText(existingText);
|
|
6547
|
+
const next = normalizeTimelineDiffText(nextText);
|
|
6548
|
+
if (!existing) {
|
|
6549
|
+
return next;
|
|
6550
|
+
}
|
|
6551
|
+
if (!next || next === existing) {
|
|
6552
|
+
return existing;
|
|
6553
|
+
}
|
|
6554
|
+
if (next.includes(existing)) {
|
|
6555
|
+
return next;
|
|
6556
|
+
}
|
|
6557
|
+
if (existing.includes(next)) {
|
|
6558
|
+
return existing;
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
const relevantRefs = normalizeTimelineFileRefs(fileRefs);
|
|
6562
|
+
const existingSections = splitUnifiedDiffTextByFile(existing);
|
|
6563
|
+
const nextSections = splitUnifiedDiffTextByFile(next);
|
|
6564
|
+
if (existingSections.length === 0 || nextSections.length === 0) {
|
|
6565
|
+
return normalizeTimelineDiffText([existing, next].join("\n\n"));
|
|
6566
|
+
}
|
|
6567
|
+
|
|
6568
|
+
const mergedSections = [...existingSections];
|
|
6569
|
+
for (const section of nextSections) {
|
|
6570
|
+
const matchIndex = mergedSections.findIndex((candidate) => timelineFileRefsMatch(candidate.fileRef, section.fileRef));
|
|
6571
|
+
if (matchIndex === -1) {
|
|
6572
|
+
mergedSections.push(section);
|
|
6573
|
+
continue;
|
|
6574
|
+
}
|
|
6575
|
+
if ((section.diffText || "").length > (mergedSections[matchIndex].diffText || "").length) {
|
|
6576
|
+
mergedSections[matchIndex] = section;
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
const filteredSections =
|
|
6581
|
+
relevantRefs.length > 0
|
|
6582
|
+
? mergedSections.filter((section) => relevantRefs.some((fileRef) => timelineFileRefsMatch(section.fileRef, fileRef)))
|
|
6583
|
+
: mergedSections;
|
|
6584
|
+
return normalizeTimelineDiffText(filteredSections.map((section) => section.diffText).filter(Boolean).join("\n\n"));
|
|
6585
|
+
}
|
|
6586
|
+
|
|
6587
|
+
function mergeApprovalPayloadDelta(base, next) {
|
|
6588
|
+
if (!base && !next) {
|
|
6589
|
+
return null;
|
|
6590
|
+
}
|
|
6591
|
+
if (!base) {
|
|
6592
|
+
return next ? { ...next, fileRefs: normalizeTimelineFileRefs(next.fileRefs ?? []) } : null;
|
|
6593
|
+
}
|
|
6594
|
+
if (!next) {
|
|
6595
|
+
return base ? { ...base, fileRefs: normalizeTimelineFileRefs(base.fileRefs ?? []) } : null;
|
|
6596
|
+
}
|
|
6597
|
+
|
|
6598
|
+
const fileRefs = normalizeTimelineFileRefs([...(base.fileRefs ?? []), ...(next.fileRefs ?? [])]);
|
|
6599
|
+
const diffText = mergeApprovalDiffTexts(base.diffText ?? "", next.diffText ?? "", fileRefs);
|
|
6600
|
+
const diffCounts = diffLineCounts(diffText);
|
|
6601
|
+
return {
|
|
6602
|
+
fileRefs,
|
|
6603
|
+
diffText,
|
|
6604
|
+
diffAvailable: Boolean(diffText) || base.diffAvailable === true || next.diffAvailable === true,
|
|
6605
|
+
diffSource: normalizeTimelineDiffSource(next.diffSource || base.diffSource || ""),
|
|
6606
|
+
diffAddedLines: diffCounts.addedLines,
|
|
6607
|
+
diffRemovedLines: diffCounts.removedLines,
|
|
6608
|
+
};
|
|
6609
|
+
}
|
|
6610
|
+
|
|
6611
|
+
function collectFileApprovalCorrelationIds(params, fallbackRequestId = "") {
|
|
6612
|
+
const ids = new Set();
|
|
6613
|
+
const pushValue = (value) => {
|
|
6614
|
+
const normalized = cleanText(value ?? "");
|
|
6615
|
+
if (normalized) {
|
|
6616
|
+
ids.add(normalized);
|
|
6617
|
+
}
|
|
6618
|
+
};
|
|
6619
|
+
|
|
6620
|
+
pushValue(fallbackRequestId);
|
|
6621
|
+
if (isPlainObject(params)) {
|
|
6622
|
+
pushValue(params.approvalId);
|
|
6623
|
+
pushValue(params.requestId);
|
|
6624
|
+
pushValue(params.id);
|
|
6625
|
+
}
|
|
6626
|
+
return [...ids.values()];
|
|
6627
|
+
}
|
|
6628
|
+
|
|
6629
|
+
function approvalCorrelationIds(approval) {
|
|
6630
|
+
const ids = new Set();
|
|
6631
|
+
if (approval?.requestId != null) {
|
|
6632
|
+
ids.add(cleanText(approval.requestId));
|
|
6633
|
+
}
|
|
6634
|
+
if (Array.isArray(approval?.approvalIds)) {
|
|
6635
|
+
for (const approvalId of approval.approvalIds) {
|
|
6636
|
+
const normalized = cleanText(approvalId);
|
|
6637
|
+
if (normalized) {
|
|
6638
|
+
ids.add(normalized);
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
const rawParams = isPlainObject(approval?.rawParams) ? approval.rawParams : {};
|
|
6643
|
+
for (const candidate of [rawParams.approvalId, rawParams.requestId, rawParams.id]) {
|
|
6644
|
+
const normalized = cleanText(candidate ?? "");
|
|
6645
|
+
if (normalized) {
|
|
6646
|
+
ids.add(normalized);
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
return [...ids.values()];
|
|
6650
|
+
}
|
|
6651
|
+
|
|
6652
|
+
function applyApprovalPayloadDeltaToApproval(approval, delta) {
|
|
6653
|
+
if (!approval || !delta) {
|
|
6654
|
+
return false;
|
|
6655
|
+
}
|
|
6656
|
+
const merged = mergeApprovalPayloadDelta(
|
|
6657
|
+
{
|
|
6658
|
+
fileRefs: approval.fileRefs ?? [],
|
|
6659
|
+
diffText: approval.diffText ?? "",
|
|
6660
|
+
diffAvailable: approval.diffAvailable === true,
|
|
6661
|
+
diffSource: approval.diffSource ?? "",
|
|
6662
|
+
diffAddedLines: approval.diffAddedLines ?? 0,
|
|
6663
|
+
diffRemovedLines: approval.diffRemovedLines ?? 0,
|
|
6664
|
+
},
|
|
6665
|
+
delta
|
|
6666
|
+
);
|
|
6667
|
+
if (!merged) {
|
|
6668
|
+
return false;
|
|
6669
|
+
}
|
|
6670
|
+
const changed =
|
|
6671
|
+
JSON.stringify([
|
|
6672
|
+
normalizeTimelineFileRefs(approval.fileRefs ?? []),
|
|
6673
|
+
normalizeTimelineDiffText(approval.diffText ?? ""),
|
|
6674
|
+
Boolean(approval.diffAvailable),
|
|
6675
|
+
normalizeTimelineDiffSource(approval.diffSource ?? ""),
|
|
6676
|
+
Math.max(0, Number(approval.diffAddedLines) || 0),
|
|
6677
|
+
Math.max(0, Number(approval.diffRemovedLines) || 0),
|
|
6678
|
+
]) !==
|
|
6679
|
+
JSON.stringify([
|
|
6680
|
+
normalizeTimelineFileRefs(merged.fileRefs ?? []),
|
|
6681
|
+
normalizeTimelineDiffText(merged.diffText ?? ""),
|
|
6682
|
+
Boolean(merged.diffAvailable),
|
|
6683
|
+
normalizeTimelineDiffSource(merged.diffSource ?? ""),
|
|
6684
|
+
Math.max(0, Number(merged.diffAddedLines) || 0),
|
|
6685
|
+
Math.max(0, Number(merged.diffRemovedLines) || 0),
|
|
6686
|
+
]);
|
|
6687
|
+
approval.fileRefs = normalizeTimelineFileRefs(merged.fileRefs ?? []);
|
|
6688
|
+
approval.diffText = normalizeTimelineDiffText(merged.diffText ?? "");
|
|
6689
|
+
approval.diffAvailable = Boolean(merged.diffAvailable);
|
|
6690
|
+
approval.diffSource = normalizeTimelineDiffSource(merged.diffSource ?? "");
|
|
6691
|
+
approval.diffAddedLines = Math.max(0, Number(merged.diffAddedLines) || 0);
|
|
6692
|
+
approval.diffRemovedLines = Math.max(0, Number(merged.diffRemovedLines) || 0);
|
|
6693
|
+
return changed;
|
|
6694
|
+
}
|
|
6695
|
+
|
|
6696
|
+
function rememberFileApprovalDelta(runtime, params) {
|
|
6697
|
+
const approvalIds = collectFileApprovalCorrelationIds(params);
|
|
6698
|
+
if (approvalIds.length === 0) {
|
|
6699
|
+
return false;
|
|
6700
|
+
}
|
|
6701
|
+
|
|
6702
|
+
const delta = extractApprovalPayloadDelta(params, "approval_request");
|
|
6703
|
+
if (!delta.diffAvailable && normalizeTimelineFileRefs(delta.fileRefs ?? []).length === 0) {
|
|
6704
|
+
return false;
|
|
6705
|
+
}
|
|
6706
|
+
|
|
6707
|
+
let changed = false;
|
|
6708
|
+
for (const approvalId of approvalIds) {
|
|
6709
|
+
const previous = runtime.fileApprovalDeltasById.get(approvalId) ?? null;
|
|
6710
|
+
const next = mergeApprovalPayloadDelta(previous, delta);
|
|
6711
|
+
if (!next) {
|
|
6712
|
+
continue;
|
|
6713
|
+
}
|
|
6714
|
+
runtime.fileApprovalDeltasById.set(approvalId, next);
|
|
6715
|
+
changed = true;
|
|
6716
|
+
}
|
|
6717
|
+
|
|
6718
|
+
if (runtime.fileApprovalDeltasById.size > 256) {
|
|
6719
|
+
const oldestKey = runtime.fileApprovalDeltasById.keys().next().value;
|
|
6720
|
+
if (oldestKey) {
|
|
6721
|
+
runtime.fileApprovalDeltasById.delete(oldestKey);
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6725
|
+
if (!changed) {
|
|
6726
|
+
return false;
|
|
6727
|
+
}
|
|
6728
|
+
|
|
6729
|
+
for (const approval of runtime.nativeApprovalsByToken.values()) {
|
|
6730
|
+
const matches = approvalCorrelationIds(approval).some((approvalId) => approvalIds.includes(approvalId));
|
|
6731
|
+
if (!matches) {
|
|
6732
|
+
continue;
|
|
6733
|
+
}
|
|
6734
|
+
applyApprovalPayloadDeltaToApproval(approval, delta);
|
|
6735
|
+
}
|
|
6736
|
+
return true;
|
|
6737
|
+
}
|
|
6738
|
+
|
|
6097
6739
|
function buildNativeApprovalActions(reviewUrl, locale = config?.defaultLocale || DEFAULT_LOCALE) {
|
|
6098
6740
|
return [{ action: "view", label: t(locale, "server.action.review"), url: reviewUrl, clear: true }];
|
|
6099
6741
|
}
|
|
@@ -7729,6 +8371,11 @@ function buildHistoryDetail(item, locale, runtime = null) {
|
|
|
7729
8371
|
createdAtMs: Number(item.createdAtMs) || 0,
|
|
7730
8372
|
messageHtml: renderMessageHtml(item.messageText, `<p>${escapeHtml(t(locale, "detail.detailUnavailable"))}</p>`),
|
|
7731
8373
|
fileRefs: normalizeTimelineFileRefs(item.fileRefs ?? []),
|
|
8374
|
+
diffText: normalizeTimelineDiffText(item.diffText ?? ""),
|
|
8375
|
+
diffAvailable: item.diffAvailable === true || Boolean(item.diffText),
|
|
8376
|
+
diffSource: normalizeTimelineDiffSource(item.diffSource ?? ""),
|
|
8377
|
+
diffAddedLines: Math.max(0, Number(item.diffAddedLines) || 0),
|
|
8378
|
+
diffRemovedLines: Math.max(0, Number(item.diffRemovedLines) || 0),
|
|
7732
8379
|
interruptNotice: interruptedDetailNotice(item.messageText, locale),
|
|
7733
8380
|
readOnly: true,
|
|
7734
8381
|
reply: replyEnabled
|
|
@@ -8357,8 +9004,15 @@ async function handleNativeApprovalDecision({ config, runtime, state, approval,
|
|
|
8357
9004
|
stableId: `approval:${approval.requestKey}:${Date.now()}`,
|
|
8358
9005
|
token: approval.token,
|
|
8359
9006
|
title: approval.title,
|
|
9007
|
+
threadLabel: approval.threadLabel || "",
|
|
8360
9008
|
messageText: `${approvalDecisionMessage(decision, config.defaultLocale)}\n\n${approval.messageText}`,
|
|
8361
9009
|
summary: approvalDecisionMessage(decision, config.defaultLocale),
|
|
9010
|
+
fileRefs: normalizeTimelineFileRefs(approval.fileRefs ?? []),
|
|
9011
|
+
diffText: normalizeTimelineDiffText(approval.diffText ?? ""),
|
|
9012
|
+
diffSource: normalizeTimelineDiffSource(approval.diffSource ?? ""),
|
|
9013
|
+
diffAvailable: approval.diffAvailable === true || Boolean(approval.diffText),
|
|
9014
|
+
diffAddedLines: Math.max(0, Number(approval.diffAddedLines) || 0),
|
|
9015
|
+
diffRemovedLines: Math.max(0, Number(approval.diffRemovedLines) || 0),
|
|
8362
9016
|
outcome: decision === "accept" ? "approved" : "rejected",
|
|
8363
9017
|
});
|
|
8364
9018
|
if (stateChanged) {
|
package/web/app.css
CHANGED
|
@@ -1867,6 +1867,16 @@ code {
|
|
|
1867
1867
|
align-items: center;
|
|
1868
1868
|
}
|
|
1869
1869
|
|
|
1870
|
+
.detail-diff-card__toggle {
|
|
1871
|
+
width: 100%;
|
|
1872
|
+
padding: 0;
|
|
1873
|
+
border: 0;
|
|
1874
|
+
background: transparent;
|
|
1875
|
+
color: inherit;
|
|
1876
|
+
text-align: left;
|
|
1877
|
+
cursor: pointer;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1870
1880
|
.detail-diff-card__title-wrap {
|
|
1871
1881
|
display: inline-flex;
|
|
1872
1882
|
align-items: center;
|
|
@@ -1888,6 +1898,23 @@ code {
|
|
|
1888
1898
|
line-height: 1.3;
|
|
1889
1899
|
}
|
|
1890
1900
|
|
|
1901
|
+
.detail-diff-card__header-right {
|
|
1902
|
+
display: inline-flex;
|
|
1903
|
+
align-items: center;
|
|
1904
|
+
gap: 0.52rem;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
.detail-diff-card__chevron {
|
|
1908
|
+
width: 0.92rem;
|
|
1909
|
+
height: 0.92rem;
|
|
1910
|
+
color: rgba(202, 220, 233, 0.72);
|
|
1911
|
+
transition: transform 160ms ease;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
.detail-diff-card__toggle.is-open .detail-diff-card__chevron {
|
|
1915
|
+
transform: rotate(90deg);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1891
1918
|
.detail-diff-card__notice {
|
|
1892
1919
|
margin: 0;
|
|
1893
1920
|
color: var(--muted);
|
|
@@ -1902,6 +1929,27 @@ code {
|
|
|
1902
1929
|
line-height: 1.55;
|
|
1903
1930
|
}
|
|
1904
1931
|
|
|
1932
|
+
.detail-page-copy--approval {
|
|
1933
|
+
margin: 0.78rem 0 0.48rem;
|
|
1934
|
+
padding: 0.06rem 0;
|
|
1935
|
+
display: grid;
|
|
1936
|
+
gap: 0;
|
|
1937
|
+
line-height: 1.24;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
.detail-page-copy--approval > * {
|
|
1941
|
+
margin-top: 0;
|
|
1942
|
+
margin-bottom: 0;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
.detail-page-copy--approval p {
|
|
1946
|
+
line-height: 1.24;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
.detail-page-copy--approval > :last-child {
|
|
1950
|
+
margin-bottom: 0;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1905
1953
|
.detail-page-copy--mobile {
|
|
1906
1954
|
padding-inline: 0.1rem;
|
|
1907
1955
|
}
|
|
@@ -2850,7 +2898,7 @@ button:disabled {
|
|
|
2850
2898
|
gap: 0.2rem;
|
|
2851
2899
|
}
|
|
2852
2900
|
|
|
2853
|
-
.bottom-nav__button
|
|
2901
|
+
.bottom-nav__button .tab-label,
|
|
2854
2902
|
.segmented-nav__button span {
|
|
2855
2903
|
display: block;
|
|
2856
2904
|
max-width: 100%;
|
|
@@ -2875,12 +2923,19 @@ button:disabled {
|
|
|
2875
2923
|
display: inline-flex;
|
|
2876
2924
|
align-items: center;
|
|
2877
2925
|
justify-content: center;
|
|
2926
|
+
overflow: visible;
|
|
2878
2927
|
}
|
|
2879
2928
|
|
|
2880
2929
|
.bottom-nav__attention-dot {
|
|
2881
2930
|
position: absolute;
|
|
2882
|
-
top: -0.
|
|
2883
|
-
right: -0.
|
|
2931
|
+
top: -0.08rem;
|
|
2932
|
+
right: -0.12rem;
|
|
2933
|
+
display: block;
|
|
2934
|
+
max-width: none;
|
|
2935
|
+
overflow: visible;
|
|
2936
|
+
white-space: normal;
|
|
2937
|
+
line-height: 0;
|
|
2938
|
+
z-index: 1;
|
|
2884
2939
|
width: 0.52rem;
|
|
2885
2940
|
height: 0.52rem;
|
|
2886
2941
|
border-radius: 999px;
|
|
@@ -3218,7 +3273,7 @@ button:disabled {
|
|
|
3218
3273
|
padding: 0.28rem 0.15rem;
|
|
3219
3274
|
}
|
|
3220
3275
|
|
|
3221
|
-
.bottom-nav__button
|
|
3276
|
+
.bottom-nav__button .tab-label {
|
|
3222
3277
|
font-size: 0.72rem;
|
|
3223
3278
|
}
|
|
3224
3279
|
|
package/web/app.js
CHANGED
|
@@ -39,6 +39,7 @@ const state = {
|
|
|
39
39
|
pendingListScrollRestore: false,
|
|
40
40
|
threadFilterInteractionUntilMs: 0,
|
|
41
41
|
diffThreadExpandedFiles: {},
|
|
42
|
+
detailDiffExpanded: {},
|
|
42
43
|
choiceLocalDrafts: {},
|
|
43
44
|
completionReplyDrafts: {},
|
|
44
45
|
pairError: "",
|
|
@@ -2253,6 +2254,26 @@ function toggleDiffThreadFileExpanded(token, fileRef) {
|
|
|
2253
2254
|
};
|
|
2254
2255
|
}
|
|
2255
2256
|
|
|
2257
|
+
function detailDiffExpansionKey(detail) {
|
|
2258
|
+
return `${normalizeClientText(detail?.kind || "")}:${normalizeClientText(detail?.token || "")}`;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
function isDetailDiffExpanded(detail) {
|
|
2262
|
+
const key = detailDiffExpansionKey(detail);
|
|
2263
|
+
return Boolean(key && state.detailDiffExpanded?.[key] === true);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
function toggleDetailDiffExpanded(detail) {
|
|
2267
|
+
const key = detailDiffExpansionKey(detail);
|
|
2268
|
+
if (!key || key === ":") {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
state.detailDiffExpanded = {
|
|
2272
|
+
...(state.detailDiffExpanded || {}),
|
|
2273
|
+
[key]: !isDetailDiffExpanded(detail),
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2256
2277
|
function timelineFileEventFileSummary(item) {
|
|
2257
2278
|
const labels = normalizeClientFileRefs(item?.fileRefs)
|
|
2258
2279
|
.map((fileRef) => fileRefLabel(fileRef))
|
|
@@ -2989,7 +3010,7 @@ function renderStandardDetailDesktop(detail) {
|
|
|
2989
3010
|
<div class="detail-shell">
|
|
2990
3011
|
${renderDetailMetaRow(detail, kindInfo)}
|
|
2991
3012
|
<h2 class="detail-title detail-title--desktop">${escapeHtml(detailDisplayTitle(detail))}</h2>
|
|
2992
|
-
${detail.readOnly ? "" : renderDetailLead(detail, kindInfo)}
|
|
3013
|
+
${detail.readOnly || detail.kind === "approval" ? "" : renderDetailLead(detail, kindInfo)}
|
|
2993
3014
|
${renderPreviousContextCard(detail)}
|
|
2994
3015
|
${renderInterruptedDetailNotice(detail)}
|
|
2995
3016
|
${
|
|
@@ -3027,7 +3048,7 @@ function renderStandardDetailMobile(detail) {
|
|
|
3027
3048
|
? plainIntro
|
|
3028
3049
|
: `
|
|
3029
3050
|
<section class="detail-card detail-card--body detail-card--mobile ${spaciousBodyDetail ? "detail-card--message-body" : ""}">
|
|
3030
|
-
${detail.readOnly ? "" : renderDetailLead(detail, kindInfo, { mobile: true })}
|
|
3051
|
+
${detail.readOnly || detail.kind === "approval" ? "" : renderDetailLead(detail, kindInfo, { mobile: true })}
|
|
3031
3052
|
<div class="detail-body ${spaciousBodyDetail ? "detail-body--message " : ""}markdown">${detail.messageHtml || ""}</div>
|
|
3032
3053
|
</section>
|
|
3033
3054
|
`
|
|
@@ -3045,14 +3066,20 @@ function renderStandardDetailMobile(detail) {
|
|
|
3045
3066
|
}
|
|
3046
3067
|
|
|
3047
3068
|
function renderDetailPlainIntro(detail, options = {}) {
|
|
3048
|
-
if (!["diff_thread", "file_event"].includes(detail?.kind || "")) {
|
|
3069
|
+
if (!["approval", "diff_thread", "file_event"].includes(detail?.kind || "")) {
|
|
3049
3070
|
return "";
|
|
3050
3071
|
}
|
|
3051
3072
|
if (!detail?.messageHtml) {
|
|
3052
3073
|
return "";
|
|
3053
3074
|
}
|
|
3075
|
+
const approvalClass = detail?.kind === "approval" ? " detail-page-copy--approval" : "";
|
|
3076
|
+
const approvalLead =
|
|
3077
|
+
detail?.kind === "approval"
|
|
3078
|
+
? `<p>${escapeHtml(detailIntentText(detail))}</p>`
|
|
3079
|
+
: "";
|
|
3054
3080
|
return `
|
|
3055
|
-
<div class="detail-page-copy ${options.mobile ? "detail-page-copy--mobile" : ""} markdown">
|
|
3081
|
+
<div class="detail-page-copy${approvalClass} ${options.mobile ? "detail-page-copy--mobile" : ""} markdown">
|
|
3082
|
+
${approvalLead}
|
|
3056
3083
|
${detail.messageHtml}
|
|
3057
3084
|
</div>
|
|
3058
3085
|
`;
|
|
@@ -3206,21 +3233,31 @@ function renderDetailDiffPanel(detail, options = {}) {
|
|
|
3206
3233
|
|
|
3207
3234
|
const diffText = String(detail?.diffText || "").replace(/\r\n/g, "\n").trim();
|
|
3208
3235
|
const statsHtml = renderDiffEntryStatsHtml(detail);
|
|
3236
|
+
const expanded = isDetailDiffExpanded(detail);
|
|
3209
3237
|
|
|
3210
3238
|
return `
|
|
3211
3239
|
<section class="detail-card detail-card--diff ${options.mobile ? "detail-card--mobile" : ""}">
|
|
3212
|
-
<
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3240
|
+
<button
|
|
3241
|
+
type="button"
|
|
3242
|
+
class="detail-diff-card__toggle ${expanded ? "is-open" : ""}"
|
|
3243
|
+
data-detail-diff-toggle
|
|
3244
|
+
>
|
|
3245
|
+
<div class="detail-diff-card__header">
|
|
3246
|
+
<div class="detail-diff-card__title-wrap">
|
|
3247
|
+
<span class="detail-diff-card__icon" aria-hidden="true">${renderIcon("diff")}</span>
|
|
3248
|
+
<span>${escapeHtml(L("detail.diffTitle"))}</span>
|
|
3249
|
+
</div>
|
|
3250
|
+
<div class="detail-diff-card__header-right">
|
|
3251
|
+
${statsHtml ? `<span class="detail-diff-card__stats diff-entry__stats">${statsHtml}</span>` : ""}
|
|
3252
|
+
<span class="detail-diff-card__chevron" aria-hidden="true">${renderIcon("chevron-right")}</span>
|
|
3253
|
+
</div>
|
|
3216
3254
|
</div>
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
diffText
|
|
3255
|
+
</button>
|
|
3256
|
+
${expanded
|
|
3257
|
+
? diffText
|
|
3221
3258
|
? `<div class="detail-diff-viewer">${renderDiffLines(diffText)}</div>`
|
|
3222
3259
|
: `<p class="detail-diff-card__notice">${escapeHtml(L("detail.diffUnavailable"))}</p>`
|
|
3223
|
-
|
|
3260
|
+
: ""}
|
|
3224
3261
|
</section>
|
|
3225
3262
|
`;
|
|
3226
3263
|
}
|
|
@@ -4059,6 +4096,18 @@ function bindShellInteractions() {
|
|
|
4059
4096
|
});
|
|
4060
4097
|
}
|
|
4061
4098
|
|
|
4099
|
+
for (const button of document.querySelectorAll("[data-detail-diff-toggle]")) {
|
|
4100
|
+
button.addEventListener("click", async (event) => {
|
|
4101
|
+
event.preventDefault();
|
|
4102
|
+
event.stopPropagation();
|
|
4103
|
+
if (!state.currentDetail) {
|
|
4104
|
+
return;
|
|
4105
|
+
}
|
|
4106
|
+
toggleDetailDiffExpanded(state.currentDetail);
|
|
4107
|
+
await renderShell();
|
|
4108
|
+
});
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4062
4111
|
for (const button of document.querySelectorAll("[data-back-to-list]")) {
|
|
4063
4112
|
button.addEventListener("click", async () => {
|
|
4064
4113
|
clearChoiceLocalDraftForItem(state.currentItem);
|