@ricsam/r5d-worker 0.0.6 → 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/git-identity.cjs +39 -0
- package/dist/cjs/main.cjs +347 -9
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/git-identity.mjs +15 -0
- package/dist/mjs/main.mjs +348 -10
- package/dist/mjs/package.json +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var git_identity_exports = {};
|
|
20
|
+
__export(git_identity_exports, {
|
|
21
|
+
configureVisibleGitIdentity: () => configureVisibleGitIdentity
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(git_identity_exports);
|
|
24
|
+
function configureVisibleGitIdentity(branchPath, identity, runGit) {
|
|
25
|
+
if (!identity) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const name = identity.name.trim();
|
|
29
|
+
const email = identity.email.trim();
|
|
30
|
+
if (!name || !email) {
|
|
31
|
+
throw new Error("Git identity must include name and email");
|
|
32
|
+
}
|
|
33
|
+
runGit(["config", "user.name", name], { cwd: branchPath });
|
|
34
|
+
runGit(["config", "user.email", email], { cwd: branchPath });
|
|
35
|
+
}
|
|
36
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
37
|
+
0 && (module.exports = {
|
|
38
|
+
configureVisibleGitIdentity
|
|
39
|
+
});
|
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;
|
|
@@ -97,7 +98,7 @@ function getWorkerVersion() {
|
|
|
97
98
|
return packageVersion;
|
|
98
99
|
}
|
|
99
100
|
const sourceVersion = readVersionFile(import_node_path.default.join(current, "VERSION.txt"));
|
|
100
|
-
if (sourceVersion && import_node_path.default.basename(current) === "
|
|
101
|
+
if (sourceVersion && import_node_path.default.basename(current) === "packages") {
|
|
101
102
|
return sourceVersion;
|
|
102
103
|
}
|
|
103
104
|
const parent = import_node_path.default.dirname(current);
|
|
@@ -223,8 +224,8 @@ async function readResponseText(response) {
|
|
|
223
224
|
return "";
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
|
-
async function
|
|
227
|
-
const statusUrl = new URL("/api/
|
|
227
|
+
async function verifyR5dctlAuth(baseUrl, token) {
|
|
228
|
+
const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
|
|
228
229
|
let response;
|
|
229
230
|
try {
|
|
230
231
|
response = await fetch(statusUrl, {
|
|
@@ -253,12 +254,12 @@ async function verifyBinctlAuth(baseUrl, token) {
|
|
|
253
254
|
);
|
|
254
255
|
}
|
|
255
256
|
function resolveCredentials(options, config) {
|
|
256
|
-
const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.
|
|
257
|
+
const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey;
|
|
257
258
|
if (!token) {
|
|
258
259
|
throw new Error("Authentication required. Run `r5dctl auth login` or set R5D_WORKER_TOKEN/R5D_API_KEY.");
|
|
259
260
|
}
|
|
260
261
|
return {
|
|
261
|
-
baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.
|
|
262
|
+
baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL),
|
|
262
263
|
token
|
|
263
264
|
};
|
|
264
265
|
}
|
|
@@ -685,6 +686,31 @@ async function collectStream(stream) {
|
|
|
685
686
|
}
|
|
686
687
|
return await new Response(stream).text();
|
|
687
688
|
}
|
|
689
|
+
async function streamCommandOutput(stream, onData) {
|
|
690
|
+
if (!stream) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const reader = stream.getReader();
|
|
694
|
+
const decoder = new TextDecoder();
|
|
695
|
+
try {
|
|
696
|
+
while (true) {
|
|
697
|
+
const chunk = await reader.read();
|
|
698
|
+
if (chunk.done) {
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
const text = decoder.decode(chunk.value, { stream: true });
|
|
702
|
+
if (text.length > 0) {
|
|
703
|
+
onData(text);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const trailing = decoder.decode();
|
|
707
|
+
if (trailing.length > 0) {
|
|
708
|
+
onData(trailing);
|
|
709
|
+
}
|
|
710
|
+
} finally {
|
|
711
|
+
reader.releaseLock();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
688
714
|
function ensureOperationBranch(input) {
|
|
689
715
|
return ensureBranchWorkspace(input);
|
|
690
716
|
}
|
|
@@ -831,6 +857,171 @@ async function executeEditFileOperation(input) {
|
|
|
831
857
|
file: resolved.virtualPath
|
|
832
858
|
};
|
|
833
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
|
+
}
|
|
834
1025
|
async function executeViewFileBytesOperation(input) {
|
|
835
1026
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
836
1027
|
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
@@ -952,6 +1143,8 @@ async function executeOperation(input) {
|
|
|
952
1143
|
return executeCreateFileOperation({ ...input, message: input.message });
|
|
953
1144
|
case "edit_file":
|
|
954
1145
|
return executeEditFileOperation({ ...input, message: input.message });
|
|
1146
|
+
case "apply_patch":
|
|
1147
|
+
return executeApplyPatchOperation({ ...input, message: input.message });
|
|
955
1148
|
case "view_file_bytes":
|
|
956
1149
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
957
1150
|
case "sync":
|
|
@@ -1053,6 +1246,105 @@ async function executeCommand(input) {
|
|
|
1053
1246
|
activeProcesses.delete(input.message.runId);
|
|
1054
1247
|
}
|
|
1055
1248
|
}
|
|
1249
|
+
async function executeStreamingCommand(input) {
|
|
1250
|
+
const startedAt = Date.now();
|
|
1251
|
+
let timeout;
|
|
1252
|
+
let started = false;
|
|
1253
|
+
let timedOut = false;
|
|
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
|
+
}
|
|
1264
|
+
const branchPath = ensureBranchWorkspace({
|
|
1265
|
+
projectId: input.projectId,
|
|
1266
|
+
baseUrl: input.baseUrl,
|
|
1267
|
+
token: input.token,
|
|
1268
|
+
projectRoot: input.projectRoot,
|
|
1269
|
+
syncRoot: input.syncRoot,
|
|
1270
|
+
branchName: input.message.branchName,
|
|
1271
|
+
manifest: input.manifest
|
|
1272
|
+
});
|
|
1273
|
+
const cwd = resolveCommandCwd(branchPath.branchPath, input.message.cwd);
|
|
1274
|
+
const subprocess = Bun.spawn(input.message.argv, {
|
|
1275
|
+
cwd,
|
|
1276
|
+
stdout: "pipe",
|
|
1277
|
+
stderr: "pipe",
|
|
1278
|
+
env: {
|
|
1279
|
+
...process.env,
|
|
1280
|
+
...containerRegistryEnv(),
|
|
1281
|
+
...input.message.env ?? {}
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
activeProcesses.set(input.message.runId, { process: subprocess, command: input.message.argv });
|
|
1285
|
+
started = true;
|
|
1286
|
+
sendWorkerMessage(input.ws, {
|
|
1287
|
+
type: "exec_started",
|
|
1288
|
+
requestId: input.message.requestId,
|
|
1289
|
+
runId: input.message.runId
|
|
1290
|
+
});
|
|
1291
|
+
if (input.message.timeoutMs) {
|
|
1292
|
+
timeout = setTimeout(() => {
|
|
1293
|
+
timedOut = true;
|
|
1294
|
+
subprocess.kill("SIGTERM");
|
|
1295
|
+
}, input.message.timeoutMs);
|
|
1296
|
+
}
|
|
1297
|
+
const [exitCode] = await Promise.all([
|
|
1298
|
+
subprocess.exited,
|
|
1299
|
+
streamCommandOutput(subprocess.stdout, (data) => {
|
|
1300
|
+
sendWorkerMessage(input.ws, {
|
|
1301
|
+
type: "exec_output",
|
|
1302
|
+
runId: input.message.runId,
|
|
1303
|
+
stream: "stdout",
|
|
1304
|
+
data
|
|
1305
|
+
});
|
|
1306
|
+
}),
|
|
1307
|
+
streamCommandOutput(subprocess.stderr, (data) => {
|
|
1308
|
+
sendWorkerMessage(input.ws, {
|
|
1309
|
+
type: "exec_output",
|
|
1310
|
+
runId: input.message.runId,
|
|
1311
|
+
stream: "stderr",
|
|
1312
|
+
data
|
|
1313
|
+
});
|
|
1314
|
+
})
|
|
1315
|
+
]);
|
|
1316
|
+
sendWorkerMessage(input.ws, {
|
|
1317
|
+
type: "exec_exit",
|
|
1318
|
+
runId: input.message.runId,
|
|
1319
|
+
exitCode,
|
|
1320
|
+
durationMs: Date.now() - startedAt,
|
|
1321
|
+
...timedOut ? { timedOut: true } : {}
|
|
1322
|
+
});
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1325
|
+
if (started) {
|
|
1326
|
+
sendWorkerMessage(input.ws, {
|
|
1327
|
+
type: "exec_error",
|
|
1328
|
+
runId: input.message.runId,
|
|
1329
|
+
error: message,
|
|
1330
|
+
durationMs: Date.now() - startedAt
|
|
1331
|
+
});
|
|
1332
|
+
} else {
|
|
1333
|
+
sendWorkerMessage(input.ws, {
|
|
1334
|
+
type: "exec_start_error",
|
|
1335
|
+
requestId: input.message.requestId,
|
|
1336
|
+
runId: input.message.runId,
|
|
1337
|
+
error: message
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
} finally {
|
|
1341
|
+
if (timeout) {
|
|
1342
|
+
clearTimeout(timeout);
|
|
1343
|
+
}
|
|
1344
|
+
activeProcesses.delete(input.message.runId);
|
|
1345
|
+
cancelledProcessRuns.delete(input.message.runId);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1056
1348
|
function sendWorkerMessage(ws, message) {
|
|
1057
1349
|
ws.send(JSON.stringify(message));
|
|
1058
1350
|
}
|
|
@@ -1370,7 +1662,7 @@ async function startWorker(options) {
|
|
|
1370
1662
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
1371
1663
|
`);
|
|
1372
1664
|
runGit(["--version"]);
|
|
1373
|
-
await
|
|
1665
|
+
await verifyR5dctlAuth(baseUrl, token);
|
|
1374
1666
|
import_node_fs.default.mkdirSync(projectsRoot, { recursive: true });
|
|
1375
1667
|
import_node_fs.default.mkdirSync(syncRoot, { recursive: true });
|
|
1376
1668
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
@@ -1443,14 +1735,16 @@ async function startWorker(options) {
|
|
|
1443
1735
|
const active = activeProcesses.get(message.runId);
|
|
1444
1736
|
if (active) {
|
|
1445
1737
|
active.process.kill("SIGTERM");
|
|
1738
|
+
} else {
|
|
1739
|
+
cancelledProcessRuns.add(message.runId);
|
|
1446
1740
|
}
|
|
1447
1741
|
ws.send(
|
|
1448
1742
|
JSON.stringify({
|
|
1449
1743
|
type: "cancel_result",
|
|
1450
1744
|
requestId: message.requestId,
|
|
1451
1745
|
runId: message.runId,
|
|
1452
|
-
cancelled:
|
|
1453
|
-
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"
|
|
1454
1748
|
})
|
|
1455
1749
|
);
|
|
1456
1750
|
return;
|
|
@@ -1488,7 +1782,40 @@ async function startWorker(options) {
|
|
|
1488
1782
|
ws.send(JSON.stringify(result));
|
|
1489
1783
|
return;
|
|
1490
1784
|
}
|
|
1491
|
-
if (message.type === "
|
|
1785
|
+
if (message.type === "exec_start") {
|
|
1786
|
+
sendWorkerMessage(ws, {
|
|
1787
|
+
type: "exec_accepted",
|
|
1788
|
+
requestId: message.requestId,
|
|
1789
|
+
runId: message.runId
|
|
1790
|
+
});
|
|
1791
|
+
try {
|
|
1792
|
+
const manifest = manifestByProjectId.get(message.projectId);
|
|
1793
|
+
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
1794
|
+
process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
|
|
1795
|
+
`);
|
|
1796
|
+
void withBranchLock(
|
|
1797
|
+
message.projectId,
|
|
1798
|
+
message.branchName,
|
|
1799
|
+
() => executeStreamingCommand({ ws, message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, manifest })
|
|
1800
|
+
).catch((error) => {
|
|
1801
|
+
sendWorkerMessage(ws, {
|
|
1802
|
+
type: "exec_start_error",
|
|
1803
|
+
requestId: message.requestId,
|
|
1804
|
+
runId: message.runId,
|
|
1805
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1806
|
+
});
|
|
1807
|
+
});
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
sendWorkerMessage(ws, {
|
|
1810
|
+
type: "exec_start_error",
|
|
1811
|
+
requestId: message.requestId,
|
|
1812
|
+
runId: message.runId,
|
|
1813
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
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") {
|
|
1492
1819
|
try {
|
|
1493
1820
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
1494
1821
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
@@ -1513,6 +1840,17 @@ async function startWorker(options) {
|
|
|
1513
1840
|
})
|
|
1514
1841
|
);
|
|
1515
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
|
+
);
|
|
1516
1854
|
}
|
|
1517
1855
|
})().catch((error) => {
|
|
1518
1856
|
process.stderr.write(`[r5d-worker] message handling failed: ${error instanceof Error ? error.message : String(error)}
|
package/dist/cjs/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function configureVisibleGitIdentity(branchPath, identity, runGit) {
|
|
2
|
+
if (!identity) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
const name = identity.name.trim();
|
|
6
|
+
const email = identity.email.trim();
|
|
7
|
+
if (!name || !email) {
|
|
8
|
+
throw new Error("Git identity must include name and email");
|
|
9
|
+
}
|
|
10
|
+
runGit(["config", "user.name", name], { cwd: branchPath });
|
|
11
|
+
runGit(["config", "user.email", email], { cwd: branchPath });
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
configureVisibleGitIdentity
|
|
15
|
+
};
|
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;
|
|
@@ -74,7 +75,7 @@ function getWorkerVersion() {
|
|
|
74
75
|
return packageVersion;
|
|
75
76
|
}
|
|
76
77
|
const sourceVersion = readVersionFile(path.join(current, "VERSION.txt"));
|
|
77
|
-
if (sourceVersion && path.basename(current) === "
|
|
78
|
+
if (sourceVersion && path.basename(current) === "packages") {
|
|
78
79
|
return sourceVersion;
|
|
79
80
|
}
|
|
80
81
|
const parent = path.dirname(current);
|
|
@@ -200,8 +201,8 @@ async function readResponseText(response) {
|
|
|
200
201
|
return "";
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
|
-
async function
|
|
204
|
-
const statusUrl = new URL("/api/
|
|
204
|
+
async function verifyR5dctlAuth(baseUrl, token) {
|
|
205
|
+
const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
|
|
205
206
|
let response;
|
|
206
207
|
try {
|
|
207
208
|
response = await fetch(statusUrl, {
|
|
@@ -230,12 +231,12 @@ async function verifyBinctlAuth(baseUrl, token) {
|
|
|
230
231
|
);
|
|
231
232
|
}
|
|
232
233
|
function resolveCredentials(options, config) {
|
|
233
|
-
const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.
|
|
234
|
+
const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey;
|
|
234
235
|
if (!token) {
|
|
235
236
|
throw new Error("Authentication required. Run `r5dctl auth login` or set R5D_WORKER_TOKEN/R5D_API_KEY.");
|
|
236
237
|
}
|
|
237
238
|
return {
|
|
238
|
-
baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.
|
|
239
|
+
baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL),
|
|
239
240
|
token
|
|
240
241
|
};
|
|
241
242
|
}
|
|
@@ -662,6 +663,31 @@ async function collectStream(stream) {
|
|
|
662
663
|
}
|
|
663
664
|
return await new Response(stream).text();
|
|
664
665
|
}
|
|
666
|
+
async function streamCommandOutput(stream, onData) {
|
|
667
|
+
if (!stream) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const reader = stream.getReader();
|
|
671
|
+
const decoder = new TextDecoder();
|
|
672
|
+
try {
|
|
673
|
+
while (true) {
|
|
674
|
+
const chunk = await reader.read();
|
|
675
|
+
if (chunk.done) {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
const text = decoder.decode(chunk.value, { stream: true });
|
|
679
|
+
if (text.length > 0) {
|
|
680
|
+
onData(text);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const trailing = decoder.decode();
|
|
684
|
+
if (trailing.length > 0) {
|
|
685
|
+
onData(trailing);
|
|
686
|
+
}
|
|
687
|
+
} finally {
|
|
688
|
+
reader.releaseLock();
|
|
689
|
+
}
|
|
690
|
+
}
|
|
665
691
|
function ensureOperationBranch(input) {
|
|
666
692
|
return ensureBranchWorkspace(input);
|
|
667
693
|
}
|
|
@@ -808,6 +834,171 @@ async function executeEditFileOperation(input) {
|
|
|
808
834
|
file: resolved.virtualPath
|
|
809
835
|
};
|
|
810
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
|
+
}
|
|
811
1002
|
async function executeViewFileBytesOperation(input) {
|
|
812
1003
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
813
1004
|
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
@@ -929,6 +1120,8 @@ async function executeOperation(input) {
|
|
|
929
1120
|
return executeCreateFileOperation({ ...input, message: input.message });
|
|
930
1121
|
case "edit_file":
|
|
931
1122
|
return executeEditFileOperation({ ...input, message: input.message });
|
|
1123
|
+
case "apply_patch":
|
|
1124
|
+
return executeApplyPatchOperation({ ...input, message: input.message });
|
|
932
1125
|
case "view_file_bytes":
|
|
933
1126
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
934
1127
|
case "sync":
|
|
@@ -1030,6 +1223,105 @@ async function executeCommand(input) {
|
|
|
1030
1223
|
activeProcesses.delete(input.message.runId);
|
|
1031
1224
|
}
|
|
1032
1225
|
}
|
|
1226
|
+
async function executeStreamingCommand(input) {
|
|
1227
|
+
const startedAt = Date.now();
|
|
1228
|
+
let timeout;
|
|
1229
|
+
let started = false;
|
|
1230
|
+
let timedOut = false;
|
|
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
|
+
}
|
|
1241
|
+
const branchPath = ensureBranchWorkspace({
|
|
1242
|
+
projectId: input.projectId,
|
|
1243
|
+
baseUrl: input.baseUrl,
|
|
1244
|
+
token: input.token,
|
|
1245
|
+
projectRoot: input.projectRoot,
|
|
1246
|
+
syncRoot: input.syncRoot,
|
|
1247
|
+
branchName: input.message.branchName,
|
|
1248
|
+
manifest: input.manifest
|
|
1249
|
+
});
|
|
1250
|
+
const cwd = resolveCommandCwd(branchPath.branchPath, input.message.cwd);
|
|
1251
|
+
const subprocess = Bun.spawn(input.message.argv, {
|
|
1252
|
+
cwd,
|
|
1253
|
+
stdout: "pipe",
|
|
1254
|
+
stderr: "pipe",
|
|
1255
|
+
env: {
|
|
1256
|
+
...process.env,
|
|
1257
|
+
...containerRegistryEnv(),
|
|
1258
|
+
...input.message.env ?? {}
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
activeProcesses.set(input.message.runId, { process: subprocess, command: input.message.argv });
|
|
1262
|
+
started = true;
|
|
1263
|
+
sendWorkerMessage(input.ws, {
|
|
1264
|
+
type: "exec_started",
|
|
1265
|
+
requestId: input.message.requestId,
|
|
1266
|
+
runId: input.message.runId
|
|
1267
|
+
});
|
|
1268
|
+
if (input.message.timeoutMs) {
|
|
1269
|
+
timeout = setTimeout(() => {
|
|
1270
|
+
timedOut = true;
|
|
1271
|
+
subprocess.kill("SIGTERM");
|
|
1272
|
+
}, input.message.timeoutMs);
|
|
1273
|
+
}
|
|
1274
|
+
const [exitCode] = await Promise.all([
|
|
1275
|
+
subprocess.exited,
|
|
1276
|
+
streamCommandOutput(subprocess.stdout, (data) => {
|
|
1277
|
+
sendWorkerMessage(input.ws, {
|
|
1278
|
+
type: "exec_output",
|
|
1279
|
+
runId: input.message.runId,
|
|
1280
|
+
stream: "stdout",
|
|
1281
|
+
data
|
|
1282
|
+
});
|
|
1283
|
+
}),
|
|
1284
|
+
streamCommandOutput(subprocess.stderr, (data) => {
|
|
1285
|
+
sendWorkerMessage(input.ws, {
|
|
1286
|
+
type: "exec_output",
|
|
1287
|
+
runId: input.message.runId,
|
|
1288
|
+
stream: "stderr",
|
|
1289
|
+
data
|
|
1290
|
+
});
|
|
1291
|
+
})
|
|
1292
|
+
]);
|
|
1293
|
+
sendWorkerMessage(input.ws, {
|
|
1294
|
+
type: "exec_exit",
|
|
1295
|
+
runId: input.message.runId,
|
|
1296
|
+
exitCode,
|
|
1297
|
+
durationMs: Date.now() - startedAt,
|
|
1298
|
+
...timedOut ? { timedOut: true } : {}
|
|
1299
|
+
});
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1302
|
+
if (started) {
|
|
1303
|
+
sendWorkerMessage(input.ws, {
|
|
1304
|
+
type: "exec_error",
|
|
1305
|
+
runId: input.message.runId,
|
|
1306
|
+
error: message,
|
|
1307
|
+
durationMs: Date.now() - startedAt
|
|
1308
|
+
});
|
|
1309
|
+
} else {
|
|
1310
|
+
sendWorkerMessage(input.ws, {
|
|
1311
|
+
type: "exec_start_error",
|
|
1312
|
+
requestId: input.message.requestId,
|
|
1313
|
+
runId: input.message.runId,
|
|
1314
|
+
error: message
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
} finally {
|
|
1318
|
+
if (timeout) {
|
|
1319
|
+
clearTimeout(timeout);
|
|
1320
|
+
}
|
|
1321
|
+
activeProcesses.delete(input.message.runId);
|
|
1322
|
+
cancelledProcessRuns.delete(input.message.runId);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1033
1325
|
function sendWorkerMessage(ws, message) {
|
|
1034
1326
|
ws.send(JSON.stringify(message));
|
|
1035
1327
|
}
|
|
@@ -1347,7 +1639,7 @@ async function startWorker(options) {
|
|
|
1347
1639
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
1348
1640
|
`);
|
|
1349
1641
|
runGit(["--version"]);
|
|
1350
|
-
await
|
|
1642
|
+
await verifyR5dctlAuth(baseUrl, token);
|
|
1351
1643
|
fs.mkdirSync(projectsRoot, { recursive: true });
|
|
1352
1644
|
fs.mkdirSync(syncRoot, { recursive: true });
|
|
1353
1645
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
@@ -1420,14 +1712,16 @@ async function startWorker(options) {
|
|
|
1420
1712
|
const active = activeProcesses.get(message.runId);
|
|
1421
1713
|
if (active) {
|
|
1422
1714
|
active.process.kill("SIGTERM");
|
|
1715
|
+
} else {
|
|
1716
|
+
cancelledProcessRuns.add(message.runId);
|
|
1423
1717
|
}
|
|
1424
1718
|
ws.send(
|
|
1425
1719
|
JSON.stringify({
|
|
1426
1720
|
type: "cancel_result",
|
|
1427
1721
|
requestId: message.requestId,
|
|
1428
1722
|
runId: message.runId,
|
|
1429
|
-
cancelled:
|
|
1430
|
-
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"
|
|
1431
1725
|
})
|
|
1432
1726
|
);
|
|
1433
1727
|
return;
|
|
@@ -1465,7 +1759,40 @@ async function startWorker(options) {
|
|
|
1465
1759
|
ws.send(JSON.stringify(result));
|
|
1466
1760
|
return;
|
|
1467
1761
|
}
|
|
1468
|
-
if (message.type === "
|
|
1762
|
+
if (message.type === "exec_start") {
|
|
1763
|
+
sendWorkerMessage(ws, {
|
|
1764
|
+
type: "exec_accepted",
|
|
1765
|
+
requestId: message.requestId,
|
|
1766
|
+
runId: message.runId
|
|
1767
|
+
});
|
|
1768
|
+
try {
|
|
1769
|
+
const manifest = manifestByProjectId.get(message.projectId);
|
|
1770
|
+
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
1771
|
+
process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
|
|
1772
|
+
`);
|
|
1773
|
+
void withBranchLock(
|
|
1774
|
+
message.projectId,
|
|
1775
|
+
message.branchName,
|
|
1776
|
+
() => executeStreamingCommand({ ws, message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, manifest })
|
|
1777
|
+
).catch((error) => {
|
|
1778
|
+
sendWorkerMessage(ws, {
|
|
1779
|
+
type: "exec_start_error",
|
|
1780
|
+
requestId: message.requestId,
|
|
1781
|
+
runId: message.runId,
|
|
1782
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1783
|
+
});
|
|
1784
|
+
});
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
sendWorkerMessage(ws, {
|
|
1787
|
+
type: "exec_start_error",
|
|
1788
|
+
requestId: message.requestId,
|
|
1789
|
+
runId: message.runId,
|
|
1790
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
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") {
|
|
1469
1796
|
try {
|
|
1470
1797
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
1471
1798
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
@@ -1490,6 +1817,17 @@ async function startWorker(options) {
|
|
|
1490
1817
|
})
|
|
1491
1818
|
);
|
|
1492
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
|
+
);
|
|
1493
1831
|
}
|
|
1494
1832
|
})().catch((error) => {
|
|
1495
1833
|
process.stderr.write(`[r5d-worker] message handling failed: ${error instanceof Error ? error.message : String(error)}
|
package/dist/mjs/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5d-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/cjs/main.cjs",
|
|
6
6
|
"module": "./dist/mjs/main.mjs",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "git+https://github.com/ricsam/r5d-dev.git",
|
|
18
|
-
"directory": "
|
|
18
|
+
"directory": "packages/r5d-worker"
|
|
19
19
|
},
|
|
20
20
|
"bin": {
|
|
21
21
|
"r5d-worker": "dist/cjs/main.cjs"
|