@ricsam/r5d-worker 0.0.7 → 0.0.8
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/dist/cjs/main.cjs +199 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +200 -4
- package/dist/mjs/package.json +1 -1
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -39,6 +39,7 @@ const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
|
|
|
39
39
|
const R5D_REMOTE_NAME = "r5d";
|
|
40
40
|
const LEGACY_BUILD_IT_NOW_REMOTE_NAME = "build-it-now";
|
|
41
41
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
42
|
+
const cancelledProcessRuns = /* @__PURE__ */ new Set();
|
|
42
43
|
const activePtys = /* @__PURE__ */ new Map();
|
|
43
44
|
const branchLocks = /* @__PURE__ */ new Map();
|
|
44
45
|
let containerRegistryCredential = null;
|
|
@@ -856,6 +857,171 @@ async function executeEditFileOperation(input) {
|
|
|
856
857
|
file: resolved.virtualPath
|
|
857
858
|
};
|
|
858
859
|
}
|
|
860
|
+
function decodePatchPath(rawPath) {
|
|
861
|
+
const trimmed = rawPath.trim();
|
|
862
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
863
|
+
try {
|
|
864
|
+
return JSON.parse(trimmed);
|
|
865
|
+
} catch {
|
|
866
|
+
return trimmed.slice(1, -1);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return trimmed;
|
|
870
|
+
}
|
|
871
|
+
function normalizePatchPath(rawPath) {
|
|
872
|
+
const withoutMetadata = rawPath.split(" ")[0] ?? rawPath;
|
|
873
|
+
let patchPath = decodePatchPath(withoutMetadata);
|
|
874
|
+
if (patchPath === "/dev/null") {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
if (patchPath.startsWith("a/") || patchPath.startsWith("b/")) {
|
|
878
|
+
patchPath = patchPath.slice(2);
|
|
879
|
+
}
|
|
880
|
+
if (!patchPath || import_node_path.default.isAbsolute(patchPath)) {
|
|
881
|
+
throw new Error(`Invalid patch path: ${rawPath}`);
|
|
882
|
+
}
|
|
883
|
+
const normalized = import_node_path.default.posix.normalize(patchPath);
|
|
884
|
+
if (normalized === "." || normalized.startsWith("../") || normalized === "..") {
|
|
885
|
+
throw new Error(`Invalid patch path: ${rawPath}`);
|
|
886
|
+
}
|
|
887
|
+
return normalized;
|
|
888
|
+
}
|
|
889
|
+
function readDiffPathToken(value, startIndex) {
|
|
890
|
+
let index = startIndex;
|
|
891
|
+
while (index < value.length && /\s/.test(value[index] ?? "")) {
|
|
892
|
+
index += 1;
|
|
893
|
+
}
|
|
894
|
+
if (index >= value.length) {
|
|
895
|
+
throw new Error("Missing path in patch header");
|
|
896
|
+
}
|
|
897
|
+
if (value[index] !== '"') {
|
|
898
|
+
const tokenStart2 = index;
|
|
899
|
+
while (index < value.length && !/\s/.test(value[index] ?? "")) {
|
|
900
|
+
index += 1;
|
|
901
|
+
}
|
|
902
|
+
return { token: value.slice(tokenStart2, index), nextIndex: index };
|
|
903
|
+
}
|
|
904
|
+
const tokenStart = index;
|
|
905
|
+
index += 1;
|
|
906
|
+
let escaped = false;
|
|
907
|
+
while (index < value.length) {
|
|
908
|
+
const character = value[index];
|
|
909
|
+
if (escaped) {
|
|
910
|
+
escaped = false;
|
|
911
|
+
} else if (character === "\\") {
|
|
912
|
+
escaped = true;
|
|
913
|
+
} else if (character === '"') {
|
|
914
|
+
index += 1;
|
|
915
|
+
return { token: value.slice(tokenStart, index), nextIndex: index };
|
|
916
|
+
}
|
|
917
|
+
index += 1;
|
|
918
|
+
}
|
|
919
|
+
throw new Error("Unterminated quoted path in patch header");
|
|
920
|
+
}
|
|
921
|
+
function parseDiffGitPaths(line) {
|
|
922
|
+
if (!line.startsWith("diff --git ")) {
|
|
923
|
+
throw new Error(`Invalid patch header: ${line}`);
|
|
924
|
+
}
|
|
925
|
+
const rest = line.slice("diff --git ".length);
|
|
926
|
+
const first = readDiffPathToken(rest, 0);
|
|
927
|
+
const second = readDiffPathToken(rest, first.nextIndex);
|
|
928
|
+
if (rest.slice(second.nextIndex).trim()) {
|
|
929
|
+
throw new Error(`Invalid patch header: ${line}`);
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
oldPath: normalizePatchPath(first.token),
|
|
933
|
+
newPath: normalizePatchPath(second.token)
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function summarizePatch(patch) {
|
|
937
|
+
if (!patch.trim()) {
|
|
938
|
+
throw new Error("Patch cannot be empty");
|
|
939
|
+
}
|
|
940
|
+
const files = [];
|
|
941
|
+
let current;
|
|
942
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
943
|
+
if (line.startsWith("diff --git ")) {
|
|
944
|
+
const { oldPath, newPath } = parseDiffGitPaths(line);
|
|
945
|
+
if (!oldPath && !newPath) {
|
|
946
|
+
throw new Error(`Invalid patch header: ${line}`);
|
|
947
|
+
}
|
|
948
|
+
current = {
|
|
949
|
+
path: newPath ?? oldPath,
|
|
950
|
+
...oldPath && newPath && oldPath !== newPath ? { oldPath } : {},
|
|
951
|
+
operation: oldPath && newPath && oldPath !== newPath ? "move" : "update",
|
|
952
|
+
additions: 0,
|
|
953
|
+
deletions: 0,
|
|
954
|
+
hunks: 0
|
|
955
|
+
};
|
|
956
|
+
files.push(current);
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (!current) {
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
if (line.startsWith("new file mode ")) {
|
|
963
|
+
current.operation = "add";
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
if (line.startsWith("deleted file mode ")) {
|
|
967
|
+
current.operation = "delete";
|
|
968
|
+
if (current.oldPath) {
|
|
969
|
+
current.path = current.oldPath;
|
|
970
|
+
delete current.oldPath;
|
|
971
|
+
}
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (line.startsWith("rename from ")) {
|
|
975
|
+
current.operation = "move";
|
|
976
|
+
current.oldPath = normalizePatchPath(line.slice("rename from ".length)) ?? void 0;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (line.startsWith("rename to ")) {
|
|
980
|
+
current.operation = "move";
|
|
981
|
+
current.path = normalizePatchPath(line.slice("rename to ".length)) ?? current.path;
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (line.startsWith("@@")) {
|
|
985
|
+
current.hunks += 1;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
989
|
+
current.additions += 1;
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
993
|
+
current.deletions += 1;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (files.length === 0) {
|
|
997
|
+
throw new Error("Patch must contain at least one diff --git file header");
|
|
998
|
+
}
|
|
999
|
+
return files;
|
|
1000
|
+
}
|
|
1001
|
+
async function executeApplyPatchOperation(input) {
|
|
1002
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1003
|
+
const files = summarizePatch(input.message.patch);
|
|
1004
|
+
const patchPath = import_node_path.default.join(import_node_os.default.tmpdir(), `r5d-apply-patch-${(0, import_node_crypto.randomUUID)()}.patch`);
|
|
1005
|
+
try {
|
|
1006
|
+
import_node_fs.default.writeFileSync(patchPath, input.message.patch, "utf8");
|
|
1007
|
+
runInternalGit(workspace.internalGit, ["apply", "--check", "--whitespace=nowarn", patchPath]);
|
|
1008
|
+
runInternalGit(workspace.internalGit, ["apply", "--whitespace=nowarn", patchPath]);
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
throw new Error(`Patch did not apply: ${error instanceof Error ? error.message : String(error)}`);
|
|
1011
|
+
} finally {
|
|
1012
|
+
import_node_fs.default.rmSync(patchPath, { force: true });
|
|
1013
|
+
}
|
|
1014
|
+
return {
|
|
1015
|
+
type: "apply_patch",
|
|
1016
|
+
files,
|
|
1017
|
+
summary: {
|
|
1018
|
+
filesChanged: files.length,
|
|
1019
|
+
additions: files.reduce((sum, file) => sum + file.additions, 0),
|
|
1020
|
+
deletions: files.reduce((sum, file) => sum + file.deletions, 0),
|
|
1021
|
+
hunks: files.reduce((sum, file) => sum + file.hunks, 0)
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
859
1025
|
async function executeViewFileBytesOperation(input) {
|
|
860
1026
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
861
1027
|
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
@@ -977,6 +1143,8 @@ async function executeOperation(input) {
|
|
|
977
1143
|
return executeCreateFileOperation({ ...input, message: input.message });
|
|
978
1144
|
case "edit_file":
|
|
979
1145
|
return executeEditFileOperation({ ...input, message: input.message });
|
|
1146
|
+
case "apply_patch":
|
|
1147
|
+
return executeApplyPatchOperation({ ...input, message: input.message });
|
|
980
1148
|
case "view_file_bytes":
|
|
981
1149
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
982
1150
|
case "sync":
|
|
@@ -1084,6 +1252,15 @@ async function executeStreamingCommand(input) {
|
|
|
1084
1252
|
let started = false;
|
|
1085
1253
|
let timedOut = false;
|
|
1086
1254
|
try {
|
|
1255
|
+
if (cancelledProcessRuns.delete(input.message.runId)) {
|
|
1256
|
+
sendWorkerMessage(input.ws, {
|
|
1257
|
+
type: "exec_start_error",
|
|
1258
|
+
requestId: input.message.requestId,
|
|
1259
|
+
runId: input.message.runId,
|
|
1260
|
+
error: "Command cancelled before it started"
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1087
1264
|
const branchPath = ensureBranchWorkspace({
|
|
1088
1265
|
projectId: input.projectId,
|
|
1089
1266
|
baseUrl: input.baseUrl,
|
|
@@ -1165,6 +1342,7 @@ async function executeStreamingCommand(input) {
|
|
|
1165
1342
|
clearTimeout(timeout);
|
|
1166
1343
|
}
|
|
1167
1344
|
activeProcesses.delete(input.message.runId);
|
|
1345
|
+
cancelledProcessRuns.delete(input.message.runId);
|
|
1168
1346
|
}
|
|
1169
1347
|
}
|
|
1170
1348
|
function sendWorkerMessage(ws, message) {
|
|
@@ -1557,14 +1735,16 @@ async function startWorker(options) {
|
|
|
1557
1735
|
const active = activeProcesses.get(message.runId);
|
|
1558
1736
|
if (active) {
|
|
1559
1737
|
active.process.kill("SIGTERM");
|
|
1738
|
+
} else {
|
|
1739
|
+
cancelledProcessRuns.add(message.runId);
|
|
1560
1740
|
}
|
|
1561
1741
|
ws.send(
|
|
1562
1742
|
JSON.stringify({
|
|
1563
1743
|
type: "cancel_result",
|
|
1564
1744
|
requestId: message.requestId,
|
|
1565
1745
|
runId: message.runId,
|
|
1566
|
-
cancelled:
|
|
1567
|
-
message: active ? `Sent SIGTERM to ${active.command.join(" ")}` : "
|
|
1746
|
+
cancelled: true,
|
|
1747
|
+
message: active ? `Sent SIGTERM to ${active.command.join(" ")}` : "Cancellation queued before command start"
|
|
1568
1748
|
})
|
|
1569
1749
|
);
|
|
1570
1750
|
return;
|
|
@@ -1603,6 +1783,11 @@ async function startWorker(options) {
|
|
|
1603
1783
|
return;
|
|
1604
1784
|
}
|
|
1605
1785
|
if (message.type === "exec_start") {
|
|
1786
|
+
sendWorkerMessage(ws, {
|
|
1787
|
+
type: "exec_accepted",
|
|
1788
|
+
requestId: message.requestId,
|
|
1789
|
+
runId: message.runId
|
|
1790
|
+
});
|
|
1606
1791
|
try {
|
|
1607
1792
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
1608
1793
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
@@ -1630,7 +1815,7 @@ async function startWorker(options) {
|
|
|
1630
1815
|
}
|
|
1631
1816
|
return;
|
|
1632
1817
|
}
|
|
1633
|
-
if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
1818
|
+
if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "apply_patch" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
1634
1819
|
try {
|
|
1635
1820
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
1636
1821
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
@@ -1655,6 +1840,17 @@ async function startWorker(options) {
|
|
|
1655
1840
|
})
|
|
1656
1841
|
);
|
|
1657
1842
|
}
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
const unhandledMessage = message;
|
|
1846
|
+
if (typeof unhandledMessage.requestId === "string") {
|
|
1847
|
+
ws.send(
|
|
1848
|
+
JSON.stringify({
|
|
1849
|
+
type: "operation_result",
|
|
1850
|
+
requestId: unhandledMessage.requestId,
|
|
1851
|
+
error: `Unsupported worker message type: ${String(unhandledMessage.type)}`
|
|
1852
|
+
})
|
|
1853
|
+
);
|
|
1658
1854
|
}
|
|
1659
1855
|
})().catch((error) => {
|
|
1660
1856
|
process.stderr.write(`[r5d-worker] message handling failed: ${error instanceof Error ? error.message : String(error)}
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/main.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
5
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { hostname } from "node:os";
|
|
7
7
|
import { spawn as spawnChildProcess } from "node:child_process";
|
|
8
8
|
import { configureVisibleGitIdentity } from "./git-identity.mjs";
|
|
@@ -16,6 +16,7 @@ const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
|
|
|
16
16
|
const R5D_REMOTE_NAME = "r5d";
|
|
17
17
|
const LEGACY_BUILD_IT_NOW_REMOTE_NAME = "build-it-now";
|
|
18
18
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
19
|
+
const cancelledProcessRuns = /* @__PURE__ */ new Set();
|
|
19
20
|
const activePtys = /* @__PURE__ */ new Map();
|
|
20
21
|
const branchLocks = /* @__PURE__ */ new Map();
|
|
21
22
|
let containerRegistryCredential = null;
|
|
@@ -833,6 +834,171 @@ async function executeEditFileOperation(input) {
|
|
|
833
834
|
file: resolved.virtualPath
|
|
834
835
|
};
|
|
835
836
|
}
|
|
837
|
+
function decodePatchPath(rawPath) {
|
|
838
|
+
const trimmed = rawPath.trim();
|
|
839
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
840
|
+
try {
|
|
841
|
+
return JSON.parse(trimmed);
|
|
842
|
+
} catch {
|
|
843
|
+
return trimmed.slice(1, -1);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return trimmed;
|
|
847
|
+
}
|
|
848
|
+
function normalizePatchPath(rawPath) {
|
|
849
|
+
const withoutMetadata = rawPath.split(" ")[0] ?? rawPath;
|
|
850
|
+
let patchPath = decodePatchPath(withoutMetadata);
|
|
851
|
+
if (patchPath === "/dev/null") {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
if (patchPath.startsWith("a/") || patchPath.startsWith("b/")) {
|
|
855
|
+
patchPath = patchPath.slice(2);
|
|
856
|
+
}
|
|
857
|
+
if (!patchPath || path.isAbsolute(patchPath)) {
|
|
858
|
+
throw new Error(`Invalid patch path: ${rawPath}`);
|
|
859
|
+
}
|
|
860
|
+
const normalized = path.posix.normalize(patchPath);
|
|
861
|
+
if (normalized === "." || normalized.startsWith("../") || normalized === "..") {
|
|
862
|
+
throw new Error(`Invalid patch path: ${rawPath}`);
|
|
863
|
+
}
|
|
864
|
+
return normalized;
|
|
865
|
+
}
|
|
866
|
+
function readDiffPathToken(value, startIndex) {
|
|
867
|
+
let index = startIndex;
|
|
868
|
+
while (index < value.length && /\s/.test(value[index] ?? "")) {
|
|
869
|
+
index += 1;
|
|
870
|
+
}
|
|
871
|
+
if (index >= value.length) {
|
|
872
|
+
throw new Error("Missing path in patch header");
|
|
873
|
+
}
|
|
874
|
+
if (value[index] !== '"') {
|
|
875
|
+
const tokenStart2 = index;
|
|
876
|
+
while (index < value.length && !/\s/.test(value[index] ?? "")) {
|
|
877
|
+
index += 1;
|
|
878
|
+
}
|
|
879
|
+
return { token: value.slice(tokenStart2, index), nextIndex: index };
|
|
880
|
+
}
|
|
881
|
+
const tokenStart = index;
|
|
882
|
+
index += 1;
|
|
883
|
+
let escaped = false;
|
|
884
|
+
while (index < value.length) {
|
|
885
|
+
const character = value[index];
|
|
886
|
+
if (escaped) {
|
|
887
|
+
escaped = false;
|
|
888
|
+
} else if (character === "\\") {
|
|
889
|
+
escaped = true;
|
|
890
|
+
} else if (character === '"') {
|
|
891
|
+
index += 1;
|
|
892
|
+
return { token: value.slice(tokenStart, index), nextIndex: index };
|
|
893
|
+
}
|
|
894
|
+
index += 1;
|
|
895
|
+
}
|
|
896
|
+
throw new Error("Unterminated quoted path in patch header");
|
|
897
|
+
}
|
|
898
|
+
function parseDiffGitPaths(line) {
|
|
899
|
+
if (!line.startsWith("diff --git ")) {
|
|
900
|
+
throw new Error(`Invalid patch header: ${line}`);
|
|
901
|
+
}
|
|
902
|
+
const rest = line.slice("diff --git ".length);
|
|
903
|
+
const first = readDiffPathToken(rest, 0);
|
|
904
|
+
const second = readDiffPathToken(rest, first.nextIndex);
|
|
905
|
+
if (rest.slice(second.nextIndex).trim()) {
|
|
906
|
+
throw new Error(`Invalid patch header: ${line}`);
|
|
907
|
+
}
|
|
908
|
+
return {
|
|
909
|
+
oldPath: normalizePatchPath(first.token),
|
|
910
|
+
newPath: normalizePatchPath(second.token)
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
function summarizePatch(patch) {
|
|
914
|
+
if (!patch.trim()) {
|
|
915
|
+
throw new Error("Patch cannot be empty");
|
|
916
|
+
}
|
|
917
|
+
const files = [];
|
|
918
|
+
let current;
|
|
919
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
920
|
+
if (line.startsWith("diff --git ")) {
|
|
921
|
+
const { oldPath, newPath } = parseDiffGitPaths(line);
|
|
922
|
+
if (!oldPath && !newPath) {
|
|
923
|
+
throw new Error(`Invalid patch header: ${line}`);
|
|
924
|
+
}
|
|
925
|
+
current = {
|
|
926
|
+
path: newPath ?? oldPath,
|
|
927
|
+
...oldPath && newPath && oldPath !== newPath ? { oldPath } : {},
|
|
928
|
+
operation: oldPath && newPath && oldPath !== newPath ? "move" : "update",
|
|
929
|
+
additions: 0,
|
|
930
|
+
deletions: 0,
|
|
931
|
+
hunks: 0
|
|
932
|
+
};
|
|
933
|
+
files.push(current);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
if (!current) {
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (line.startsWith("new file mode ")) {
|
|
940
|
+
current.operation = "add";
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (line.startsWith("deleted file mode ")) {
|
|
944
|
+
current.operation = "delete";
|
|
945
|
+
if (current.oldPath) {
|
|
946
|
+
current.path = current.oldPath;
|
|
947
|
+
delete current.oldPath;
|
|
948
|
+
}
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (line.startsWith("rename from ")) {
|
|
952
|
+
current.operation = "move";
|
|
953
|
+
current.oldPath = normalizePatchPath(line.slice("rename from ".length)) ?? void 0;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (line.startsWith("rename to ")) {
|
|
957
|
+
current.operation = "move";
|
|
958
|
+
current.path = normalizePatchPath(line.slice("rename to ".length)) ?? current.path;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (line.startsWith("@@")) {
|
|
962
|
+
current.hunks += 1;
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
966
|
+
current.additions += 1;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
970
|
+
current.deletions += 1;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (files.length === 0) {
|
|
974
|
+
throw new Error("Patch must contain at least one diff --git file header");
|
|
975
|
+
}
|
|
976
|
+
return files;
|
|
977
|
+
}
|
|
978
|
+
async function executeApplyPatchOperation(input) {
|
|
979
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
980
|
+
const files = summarizePatch(input.message.patch);
|
|
981
|
+
const patchPath = path.join(os.tmpdir(), `r5d-apply-patch-${randomUUID()}.patch`);
|
|
982
|
+
try {
|
|
983
|
+
fs.writeFileSync(patchPath, input.message.patch, "utf8");
|
|
984
|
+
runInternalGit(workspace.internalGit, ["apply", "--check", "--whitespace=nowarn", patchPath]);
|
|
985
|
+
runInternalGit(workspace.internalGit, ["apply", "--whitespace=nowarn", patchPath]);
|
|
986
|
+
} catch (error) {
|
|
987
|
+
throw new Error(`Patch did not apply: ${error instanceof Error ? error.message : String(error)}`);
|
|
988
|
+
} finally {
|
|
989
|
+
fs.rmSync(patchPath, { force: true });
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
type: "apply_patch",
|
|
993
|
+
files,
|
|
994
|
+
summary: {
|
|
995
|
+
filesChanged: files.length,
|
|
996
|
+
additions: files.reduce((sum, file) => sum + file.additions, 0),
|
|
997
|
+
deletions: files.reduce((sum, file) => sum + file.deletions, 0),
|
|
998
|
+
hunks: files.reduce((sum, file) => sum + file.hunks, 0)
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
836
1002
|
async function executeViewFileBytesOperation(input) {
|
|
837
1003
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
838
1004
|
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
@@ -954,6 +1120,8 @@ async function executeOperation(input) {
|
|
|
954
1120
|
return executeCreateFileOperation({ ...input, message: input.message });
|
|
955
1121
|
case "edit_file":
|
|
956
1122
|
return executeEditFileOperation({ ...input, message: input.message });
|
|
1123
|
+
case "apply_patch":
|
|
1124
|
+
return executeApplyPatchOperation({ ...input, message: input.message });
|
|
957
1125
|
case "view_file_bytes":
|
|
958
1126
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
959
1127
|
case "sync":
|
|
@@ -1061,6 +1229,15 @@ async function executeStreamingCommand(input) {
|
|
|
1061
1229
|
let started = false;
|
|
1062
1230
|
let timedOut = false;
|
|
1063
1231
|
try {
|
|
1232
|
+
if (cancelledProcessRuns.delete(input.message.runId)) {
|
|
1233
|
+
sendWorkerMessage(input.ws, {
|
|
1234
|
+
type: "exec_start_error",
|
|
1235
|
+
requestId: input.message.requestId,
|
|
1236
|
+
runId: input.message.runId,
|
|
1237
|
+
error: "Command cancelled before it started"
|
|
1238
|
+
});
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1064
1241
|
const branchPath = ensureBranchWorkspace({
|
|
1065
1242
|
projectId: input.projectId,
|
|
1066
1243
|
baseUrl: input.baseUrl,
|
|
@@ -1142,6 +1319,7 @@ async function executeStreamingCommand(input) {
|
|
|
1142
1319
|
clearTimeout(timeout);
|
|
1143
1320
|
}
|
|
1144
1321
|
activeProcesses.delete(input.message.runId);
|
|
1322
|
+
cancelledProcessRuns.delete(input.message.runId);
|
|
1145
1323
|
}
|
|
1146
1324
|
}
|
|
1147
1325
|
function sendWorkerMessage(ws, message) {
|
|
@@ -1534,14 +1712,16 @@ async function startWorker(options) {
|
|
|
1534
1712
|
const active = activeProcesses.get(message.runId);
|
|
1535
1713
|
if (active) {
|
|
1536
1714
|
active.process.kill("SIGTERM");
|
|
1715
|
+
} else {
|
|
1716
|
+
cancelledProcessRuns.add(message.runId);
|
|
1537
1717
|
}
|
|
1538
1718
|
ws.send(
|
|
1539
1719
|
JSON.stringify({
|
|
1540
1720
|
type: "cancel_result",
|
|
1541
1721
|
requestId: message.requestId,
|
|
1542
1722
|
runId: message.runId,
|
|
1543
|
-
cancelled:
|
|
1544
|
-
message: active ? `Sent SIGTERM to ${active.command.join(" ")}` : "
|
|
1723
|
+
cancelled: true,
|
|
1724
|
+
message: active ? `Sent SIGTERM to ${active.command.join(" ")}` : "Cancellation queued before command start"
|
|
1545
1725
|
})
|
|
1546
1726
|
);
|
|
1547
1727
|
return;
|
|
@@ -1580,6 +1760,11 @@ async function startWorker(options) {
|
|
|
1580
1760
|
return;
|
|
1581
1761
|
}
|
|
1582
1762
|
if (message.type === "exec_start") {
|
|
1763
|
+
sendWorkerMessage(ws, {
|
|
1764
|
+
type: "exec_accepted",
|
|
1765
|
+
requestId: message.requestId,
|
|
1766
|
+
runId: message.runId
|
|
1767
|
+
});
|
|
1583
1768
|
try {
|
|
1584
1769
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
1585
1770
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
@@ -1607,7 +1792,7 @@ async function startWorker(options) {
|
|
|
1607
1792
|
}
|
|
1608
1793
|
return;
|
|
1609
1794
|
}
|
|
1610
|
-
if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
1795
|
+
if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "apply_patch" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
1611
1796
|
try {
|
|
1612
1797
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
1613
1798
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
@@ -1632,6 +1817,17 @@ async function startWorker(options) {
|
|
|
1632
1817
|
})
|
|
1633
1818
|
);
|
|
1634
1819
|
}
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
const unhandledMessage = message;
|
|
1823
|
+
if (typeof unhandledMessage.requestId === "string") {
|
|
1824
|
+
ws.send(
|
|
1825
|
+
JSON.stringify({
|
|
1826
|
+
type: "operation_result",
|
|
1827
|
+
requestId: unhandledMessage.requestId,
|
|
1828
|
+
error: `Unsupported worker message type: ${String(unhandledMessage.type)}`
|
|
1829
|
+
})
|
|
1830
|
+
);
|
|
1635
1831
|
}
|
|
1636
1832
|
})().catch((error) => {
|
|
1637
1833
|
process.stderr.write(`[r5d-worker] message handling failed: ${error instanceof Error ? error.message : String(error)}
|
package/dist/mjs/package.json
CHANGED