@ricsam/r5d-worker 0.0.36 → 0.0.37
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 +225 -520
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync.cjs +652 -0
- package/dist/mjs/main.mjs +230 -518
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-sync.mjs +607 -0
- package/dist/types/main.d.ts +0 -50
- package/dist/types/workspace-sync.d.ts +73 -0
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -30,7 +30,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
var main_exports = {};
|
|
31
31
|
__export(main_exports, {
|
|
32
32
|
ensureVisibleGitCheckout: () => ensureVisibleGitCheckout,
|
|
33
|
-
findRepositorySyncBlockers: () => findRepositorySyncBlockers,
|
|
34
33
|
findWorkerFiles: () => findWorkerFiles,
|
|
35
34
|
githubCliEnv: () => githubCliEnv,
|
|
36
35
|
grepWorkerFiles: () => grepWorkerFiles,
|
|
@@ -43,7 +42,6 @@ __export(main_exports, {
|
|
|
43
42
|
resolveHostShell: () => resolveHostShell,
|
|
44
43
|
resolveProjectFilePath: () => resolveProjectFilePath,
|
|
45
44
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
46
|
-
syncManifestProjectsFromInternal: () => syncManifestProjectsFromInternal,
|
|
47
45
|
syncProjectPlans: () => syncProjectPlans,
|
|
48
46
|
syncSessionArtifacts: () => syncSessionArtifacts
|
|
49
47
|
});
|
|
@@ -58,6 +56,7 @@ var import_git_identity = require("./git-identity.cjs");
|
|
|
58
56
|
var import_heartbeat = require("./heartbeat.cjs");
|
|
59
57
|
var import_process_tree = require("./process-tree.cjs");
|
|
60
58
|
var import_supervisor = require("./supervisor.cjs");
|
|
59
|
+
var import_workspace_sync = require("./workspace-sync.cjs");
|
|
61
60
|
const import_meta = {};
|
|
62
61
|
const DEFAULT_BASE_URL = "https://r5d.dev";
|
|
63
62
|
const WORKER_PACKAGE_NAME = "@ricsam/r5d-worker";
|
|
@@ -66,10 +65,8 @@ const BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]$|^[A-Za-z0-9]$/;
|
|
|
66
65
|
const DEFAULT_READ_LIMIT = 2e3;
|
|
67
66
|
const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
68
67
|
const MAX_LINE_LENGTH = 2e3;
|
|
69
|
-
const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
|
|
70
|
-
const R5D_REMOTE_NAME = "r5d";
|
|
71
|
-
const LEGACY_BUILD_IT_NOW_REMOTE_NAME = "build-it-now";
|
|
72
68
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
69
|
+
const pendingProcessTerminals = /* @__PURE__ */ new Map();
|
|
73
70
|
const cancelledProcessRuns = /* @__PURE__ */ new Set();
|
|
74
71
|
const activePtys = /* @__PURE__ */ new Map();
|
|
75
72
|
let currentWorkerSocket = null;
|
|
@@ -528,8 +525,10 @@ async function prepareArtifactEnvForShell(input) {
|
|
|
528
525
|
artifactRoot: input.artifactRoot
|
|
529
526
|
});
|
|
530
527
|
} catch (error) {
|
|
531
|
-
process.stderr.write(
|
|
532
|
-
`)
|
|
528
|
+
process.stderr.write(
|
|
529
|
+
`[r5d-worker] artifact sync skipped for ${input.sessionId}: ${error instanceof Error ? error.message : String(error)}
|
|
530
|
+
`
|
|
531
|
+
);
|
|
533
532
|
}
|
|
534
533
|
const artifactsDir = sessionArtifactDir(input.artifactRoot, input.sessionId);
|
|
535
534
|
import_node_fs.default.mkdirSync(artifactsDir, { recursive: true });
|
|
@@ -585,7 +584,9 @@ function resolveCredentials(options, config) {
|
|
|
585
584
|
throw new Error("Authentication required. Run `r5dctl auth login` or set R5D_WORKER_TOKEN/R5D_API_KEY.");
|
|
586
585
|
}
|
|
587
586
|
return {
|
|
588
|
-
baseUrl: normalizeBaseUrl(
|
|
587
|
+
baseUrl: normalizeBaseUrl(
|
|
588
|
+
options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL
|
|
589
|
+
),
|
|
589
590
|
token
|
|
590
591
|
};
|
|
591
592
|
}
|
|
@@ -639,27 +640,6 @@ function runGit(args, options = {}) {
|
|
|
639
640
|
}
|
|
640
641
|
return result.stdout.toString().trim();
|
|
641
642
|
}
|
|
642
|
-
async function runGitAsync(args, options = {}) {
|
|
643
|
-
const command = ["git", ...gitAuthArgs(options.auth), ...args];
|
|
644
|
-
const subprocess = Bun.spawn(command, {
|
|
645
|
-
cwd: options.cwd,
|
|
646
|
-
stdout: "pipe",
|
|
647
|
-
stderr: "pipe",
|
|
648
|
-
env: {
|
|
649
|
-
...process.env,
|
|
650
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
654
|
-
collectStream(subprocess.stdout),
|
|
655
|
-
collectStream(subprocess.stderr),
|
|
656
|
-
subprocess.exited
|
|
657
|
-
]);
|
|
658
|
-
if (exitCode !== 0) {
|
|
659
|
-
throw new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim() || `exit ${exitCode}`}`);
|
|
660
|
-
}
|
|
661
|
-
return stdout.trim();
|
|
662
|
-
}
|
|
663
643
|
function tryGit(args, options = {}) {
|
|
664
644
|
try {
|
|
665
645
|
runGit(args, options);
|
|
@@ -668,34 +648,6 @@ function tryGit(args, options = {}) {
|
|
|
668
648
|
return false;
|
|
669
649
|
}
|
|
670
650
|
}
|
|
671
|
-
async function tryGitAsync(args, options = {}) {
|
|
672
|
-
try {
|
|
673
|
-
await runGitAsync(args, options);
|
|
674
|
-
return true;
|
|
675
|
-
} catch {
|
|
676
|
-
return false;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
function runInternalGit(context, args) {
|
|
680
|
-
return runGit(["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], { auth: context.auth });
|
|
681
|
-
}
|
|
682
|
-
function runInternalGitAsync(context, args) {
|
|
683
|
-
return runGitAsync(["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], { auth: context.auth });
|
|
684
|
-
}
|
|
685
|
-
function runInternalGitDir(context, args) {
|
|
686
|
-
return runGit(["--git-dir", context.gitDir, ...args], { auth: context.auth });
|
|
687
|
-
}
|
|
688
|
-
function tryInternalGit(context, args) {
|
|
689
|
-
try {
|
|
690
|
-
runInternalGit(context, args);
|
|
691
|
-
return true;
|
|
692
|
-
} catch {
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
function getInternalCommitHash(context) {
|
|
697
|
-
return runInternalGit(context, ["rev-parse", "HEAD"]);
|
|
698
|
-
}
|
|
699
651
|
function getGitBlobHashForContent(content) {
|
|
700
652
|
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
701
653
|
return (0, import_node_crypto.createHash)("sha1").update(`blob ${buffer.length}\0`).update(buffer).digest("hex");
|
|
@@ -703,12 +655,6 @@ function getGitBlobHashForContent(content) {
|
|
|
703
655
|
function hasWorktreeChanges(cwd) {
|
|
704
656
|
return runGit(["status", "--porcelain"], { cwd }).trim().length > 0;
|
|
705
657
|
}
|
|
706
|
-
function getInternalStatus(context) {
|
|
707
|
-
return runInternalGit(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
|
|
708
|
-
}
|
|
709
|
-
function hasInternalWorktreeChanges(context) {
|
|
710
|
-
return getInternalStatus(context).trim().length > 0;
|
|
711
|
-
}
|
|
712
658
|
function isInsideBranchPath(branchPath, filePath) {
|
|
713
659
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(branchPath), import_node_path.default.resolve(filePath));
|
|
714
660
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
@@ -771,32 +717,15 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
771
717
|
function resolveProjectFilePath(branchPath, inputPath) {
|
|
772
718
|
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
773
719
|
if (resolved.scope === "host") {
|
|
774
|
-
throw new Error(
|
|
775
|
-
`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`
|
|
776
|
-
);
|
|
720
|
+
throw new Error(`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`);
|
|
777
721
|
}
|
|
778
722
|
return resolved;
|
|
779
723
|
}
|
|
780
|
-
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
781
|
-
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
782
|
-
return remoteUrl;
|
|
783
|
-
}
|
|
784
|
-
return new URL(remoteUrl, baseUrl).toString();
|
|
785
|
-
}
|
|
786
724
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
787
725
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
788
726
|
}
|
|
789
|
-
function internalGitAuth(baseUrl, token) {
|
|
790
|
-
return {
|
|
791
|
-
extraHeaderUrl: baseUrl,
|
|
792
|
-
header: `Authorization: Bearer ${token}`
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
function internalRemoteUrlFor(baseUrl, projectId, manifest) {
|
|
796
|
-
return resolveRemoteUrl(baseUrl, manifest?.internalRemoteUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId));
|
|
797
|
-
}
|
|
798
727
|
function githubRemoteUrlFor(baseUrl, projectId, manifest) {
|
|
799
|
-
return manifest?.repoHttpUrl ??
|
|
728
|
+
return manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId);
|
|
800
729
|
}
|
|
801
730
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
802
731
|
try {
|
|
@@ -814,12 +743,6 @@ function gitAuthForRemote(remoteUrl, authHeader) {
|
|
|
814
743
|
header: authHeader
|
|
815
744
|
};
|
|
816
745
|
}
|
|
817
|
-
function branchGitDirName(branchName) {
|
|
818
|
-
return `${encodeURIComponent(branchName)}.git`;
|
|
819
|
-
}
|
|
820
|
-
function internalGitDirFor(syncRoot, projectId, branchName) {
|
|
821
|
-
return import_node_path.default.join(syncRoot, projectId, branchGitDirName(branchName));
|
|
822
|
-
}
|
|
823
746
|
function hasNormalVisibleGitDir(branchPath) {
|
|
824
747
|
const gitPath = import_node_path.default.join(branchPath, ".git");
|
|
825
748
|
return import_node_fs.default.existsSync(gitPath) && import_node_fs.default.statSync(gitPath).isDirectory();
|
|
@@ -831,13 +754,6 @@ function ensureOriginRemote(branchPath, remoteUrl) {
|
|
|
831
754
|
runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
832
755
|
}
|
|
833
756
|
}
|
|
834
|
-
async function ensureOriginRemoteAsync(branchPath, remoteUrl) {
|
|
835
|
-
if (await tryGitAsync(["remote", "get-url", "origin"], { cwd: branchPath })) {
|
|
836
|
-
await runGitAsync(["remote", "set-url", "origin", remoteUrl], { cwd: branchPath });
|
|
837
|
-
} else {
|
|
838
|
-
await runGitAsync(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
757
|
function shellQuote(value) {
|
|
842
758
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
843
759
|
}
|
|
@@ -1033,194 +949,20 @@ function ensureVisibleGitCheckout(input) {
|
|
|
1033
949
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1034
950
|
return branchPath;
|
|
1035
951
|
}
|
|
1036
|
-
function configureInternalRepo(context, remoteUrl) {
|
|
1037
|
-
if (tryInternalGit(context, ["remote", "get-url", R5D_REMOTE_NAME])) {
|
|
1038
|
-
runInternalGit(context, ["remote", "set-url", R5D_REMOTE_NAME, remoteUrl]);
|
|
1039
|
-
} else {
|
|
1040
|
-
runInternalGit(context, ["remote", "add", R5D_REMOTE_NAME, remoteUrl]);
|
|
1041
|
-
}
|
|
1042
|
-
if (tryInternalGit(context, ["remote", "get-url", LEGACY_BUILD_IT_NOW_REMOTE_NAME])) {
|
|
1043
|
-
runInternalGit(context, ["remote", "remove", LEGACY_BUILD_IT_NOW_REMOTE_NAME]);
|
|
1044
|
-
}
|
|
1045
|
-
runInternalGitDir(context, ["config", "user.name", "r5d.dev Worker"]);
|
|
1046
|
-
runInternalGitDir(context, ["config", "user.email", "worker@r5d.dev"]);
|
|
1047
|
-
}
|
|
1048
|
-
function ensureInternalSyncGit(input) {
|
|
1049
|
-
validateBranchName(input.branchName);
|
|
1050
|
-
const gitDir = internalGitDirFor(input.syncRoot, input.projectId, input.branchName);
|
|
1051
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(gitDir), { recursive: true });
|
|
1052
|
-
if (!import_node_fs.default.existsSync(gitDir)) {
|
|
1053
|
-
runGit(["init", "--bare", gitDir]);
|
|
1054
|
-
}
|
|
1055
|
-
const auth = internalGitAuth(input.baseUrl, input.token);
|
|
1056
|
-
const context = { gitDir, workTree: input.branchPath, auth };
|
|
1057
|
-
const remoteUrl = internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1058
|
-
configureInternalRepo(context, remoteUrl);
|
|
1059
|
-
runInternalGit(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1060
|
-
const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`]);
|
|
1061
|
-
const target = hasRemoteBranch ? `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}` : `refs/remotes/${R5D_REMOTE_NAME}/main`;
|
|
1062
|
-
const hasHead = tryInternalGit(context, ["rev-parse", "--verify", "HEAD"]);
|
|
1063
|
-
if (!hasHead || !hasInternalWorktreeChanges(context)) {
|
|
1064
|
-
runInternalGit(context, ["checkout", "-f", "-B", input.branchName, target]);
|
|
1065
|
-
}
|
|
1066
|
-
return context;
|
|
1067
|
-
}
|
|
1068
952
|
function ensureBranchWorkspace(input) {
|
|
1069
953
|
const defaultBranch = input.manifest?.defaultBranch || "main";
|
|
1070
954
|
const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1071
955
|
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
|
|
1072
|
-
const branchPath = ensureVisibleGitCheckout({
|
|
1073
|
-
projectRoot: input.projectRoot,
|
|
1074
|
-
branchName: input.branchName,
|
|
1075
|
-
githubRemoteUrl,
|
|
1076
|
-
githubAuth,
|
|
1077
|
-
githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
|
|
1078
|
-
defaultBranch,
|
|
1079
|
-
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
|
|
1080
|
-
});
|
|
1081
|
-
const internalGit = ensureInternalSyncGit({
|
|
1082
|
-
projectId: input.projectId,
|
|
1083
|
-
baseUrl: input.baseUrl,
|
|
1084
|
-
token: input.token,
|
|
1085
|
-
syncRoot: input.syncRoot,
|
|
1086
|
-
branchPath,
|
|
1087
|
-
branchName: input.branchName,
|
|
1088
|
-
manifest: input.manifest
|
|
1089
|
-
});
|
|
1090
|
-
return { branchPath, internalGit };
|
|
1091
|
-
}
|
|
1092
|
-
async function ensureVisibleGitCheckoutForRepositorySync(input) {
|
|
1093
|
-
validateBranchName(input.branchName);
|
|
1094
|
-
import_node_fs.default.mkdirSync(input.projectRoot, { recursive: true });
|
|
1095
|
-
const branchPath = import_node_path.default.join(input.projectRoot, input.branchName);
|
|
1096
|
-
const createdCheckout = !hasNormalVisibleGitDir(branchPath);
|
|
1097
|
-
if (createdCheckout) {
|
|
1098
|
-
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1099
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(branchPath), { recursive: true });
|
|
1100
|
-
if (!await tryGitAsync(["clone", "--origin", "origin", input.githubRemoteUrl, branchPath], { auth: input.githubAuth })) {
|
|
1101
|
-
import_node_fs.default.mkdirSync(branchPath, { recursive: true });
|
|
1102
|
-
await runGitAsync(["init"], { cwd: branchPath });
|
|
1103
|
-
}
|
|
1104
|
-
await ensureOriginRemoteAsync(branchPath, input.githubRemoteUrl);
|
|
1105
|
-
configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
|
|
1106
|
-
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1107
|
-
await tryGitAsync(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
|
|
1108
|
-
checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
|
|
1109
|
-
return branchPath;
|
|
1110
|
-
}
|
|
1111
|
-
if (input.reconcileExistingOrigin) {
|
|
1112
|
-
await ensureOriginRemoteAsync(branchPath, input.githubRemoteUrl);
|
|
1113
|
-
}
|
|
1114
|
-
configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
|
|
1115
|
-
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1116
|
-
return branchPath;
|
|
1117
|
-
}
|
|
1118
|
-
async function forceSyncManifestBranchFromInternal(input) {
|
|
1119
|
-
const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1120
|
-
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest.repoAuthHeader);
|
|
1121
|
-
const branchPath = await ensureVisibleGitCheckoutForRepositorySync({
|
|
1122
|
-
projectRoot: input.projectRoot,
|
|
1123
|
-
branchName: input.branchName,
|
|
1124
|
-
githubRemoteUrl,
|
|
1125
|
-
githubAuth,
|
|
1126
|
-
githubAuthHeader: input.manifest.repoAuthHeader,
|
|
1127
|
-
defaultBranch: input.manifest.defaultBranch || "main",
|
|
1128
|
-
reconcileExistingOrigin: Boolean(input.manifest.repoHttpUrl)
|
|
1129
|
-
});
|
|
1130
|
-
const gitDir = internalGitDirFor(input.syncRoot, input.projectId, input.branchName);
|
|
1131
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(gitDir), { recursive: true });
|
|
1132
|
-
if (!import_node_fs.default.existsSync(gitDir)) {
|
|
1133
|
-
await runGitAsync(["init", "--bare", gitDir]);
|
|
1134
|
-
}
|
|
1135
|
-
const context = {
|
|
1136
|
-
gitDir,
|
|
1137
|
-
workTree: branchPath,
|
|
1138
|
-
auth: internalGitAuth(input.baseUrl, input.token)
|
|
1139
|
-
};
|
|
1140
|
-
configureInternalRepo(context, internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest));
|
|
1141
|
-
await runInternalGitAsync(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1142
|
-
const branchTarget = `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`;
|
|
1143
|
-
const mainTarget = `refs/remotes/${R5D_REMOTE_NAME}/main`;
|
|
1144
|
-
const target = tryInternalGit(context, ["show-ref", "--verify", "--quiet", branchTarget]) ? branchTarget : tryInternalGit(context, ["show-ref", "--verify", "--quiet", mainTarget]) ? mainTarget : null;
|
|
1145
|
-
if (!target) {
|
|
1146
|
-
throw new Error(`Internal remote has neither ${input.branchName} nor main`);
|
|
1147
|
-
}
|
|
1148
|
-
await runInternalGitAsync(context, ["reset", "--hard", target]);
|
|
1149
|
-
await runInternalGitAsync(context, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1150
|
-
return getInternalCommitHash(context);
|
|
1151
|
-
}
|
|
1152
|
-
function findRepositorySyncBlockers(input) {
|
|
1153
|
-
const targetByKey = new Map(input.targets.map((target) => [`${target.projectId}:${target.branchName}`, target]));
|
|
1154
|
-
const blockedBy = [];
|
|
1155
|
-
for (const process2 of input.processes) {
|
|
1156
|
-
const target = targetByKey.get(`${process2.projectId}:${process2.branchName}`);
|
|
1157
|
-
if (target) blockedBy.push({ ...target, kind: "process", id: process2.id });
|
|
1158
|
-
}
|
|
1159
|
-
for (const shell of input.shells) {
|
|
1160
|
-
const target = targetByKey.get(`${shell.projectId}:${shell.branchName}`);
|
|
1161
|
-
if (target) blockedBy.push({ ...target, kind: "shell", id: shell.id });
|
|
1162
|
-
}
|
|
1163
|
-
return blockedBy;
|
|
1164
|
-
}
|
|
1165
|
-
async function syncManifestProjectsFromInternal(input) {
|
|
1166
|
-
const requestedProjectIds = input.projectIds ? new Set(input.projectIds) : null;
|
|
1167
|
-
const targets = input.manifests.filter((manifest) => !requestedProjectIds || requestedProjectIds.has(manifest.projectId)).flatMap((manifest) => {
|
|
1168
|
-
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
1169
|
-
return [...new Set(branches)].map((branchName) => ({ manifest, branchName }));
|
|
1170
|
-
});
|
|
1171
|
-
const blockedBy = findRepositorySyncBlockers({
|
|
1172
|
-
targets: targets.map(({ manifest, branchName }) => ({
|
|
1173
|
-
projectId: manifest.projectId,
|
|
1174
|
-
projectPath: manifest.projectPath,
|
|
1175
|
-
branchName
|
|
1176
|
-
})),
|
|
1177
|
-
processes: [...activeProcesses].map(([id, active]) => ({
|
|
1178
|
-
id,
|
|
1179
|
-
projectId: active.projectId,
|
|
1180
|
-
branchName: active.branchName
|
|
1181
|
-
})),
|
|
1182
|
-
shells: [...activePtys].map(([id, active]) => ({
|
|
1183
|
-
id,
|
|
1184
|
-
projectId: active.projectId,
|
|
1185
|
-
branchName: active.branchName
|
|
1186
|
-
}))
|
|
1187
|
-
});
|
|
1188
|
-
if (blockedBy.length > 0) {
|
|
1189
|
-
return { status: "blocked", results: [], blockedBy };
|
|
1190
|
-
}
|
|
1191
|
-
const results = [];
|
|
1192
|
-
for (const { manifest, branchName } of targets) {
|
|
1193
|
-
try {
|
|
1194
|
-
const commitHash = await forceSyncManifestBranchFromInternal({
|
|
1195
|
-
projectId: manifest.projectId,
|
|
1196
|
-
baseUrl: input.baseUrl,
|
|
1197
|
-
token: input.token,
|
|
1198
|
-
projectRoot: import_node_path.default.join(input.projectsRoot, manifest.repoSlug || manifest.projectId),
|
|
1199
|
-
syncRoot: input.syncRoot,
|
|
1200
|
-
branchName,
|
|
1201
|
-
manifest
|
|
1202
|
-
});
|
|
1203
|
-
results.push({
|
|
1204
|
-
projectId: manifest.projectId,
|
|
1205
|
-
projectPath: manifest.projectPath,
|
|
1206
|
-
branchName,
|
|
1207
|
-
status: "synced",
|
|
1208
|
-
commitHash
|
|
1209
|
-
});
|
|
1210
|
-
} catch (error) {
|
|
1211
|
-
results.push({
|
|
1212
|
-
projectId: manifest.projectId,
|
|
1213
|
-
projectPath: manifest.projectPath,
|
|
1214
|
-
branchName,
|
|
1215
|
-
status: "failed",
|
|
1216
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
956
|
return {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
957
|
+
branchPath: ensureVisibleGitCheckout({
|
|
958
|
+
projectRoot: input.projectRoot,
|
|
959
|
+
branchName: input.branchName,
|
|
960
|
+
githubRemoteUrl,
|
|
961
|
+
githubAuth,
|
|
962
|
+
githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
|
|
963
|
+
defaultBranch,
|
|
964
|
+
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
|
|
965
|
+
})
|
|
1224
966
|
};
|
|
1225
967
|
}
|
|
1226
968
|
function resolveCommandCwd(branchPath, cwd) {
|
|
@@ -1271,15 +1013,6 @@ async function streamCommandOutput(stream, onData) {
|
|
|
1271
1013
|
function ensureOperationBranch(input) {
|
|
1272
1014
|
return ensureBranchWorkspace(input);
|
|
1273
1015
|
}
|
|
1274
|
-
function internalGitProcessEnv(workspace, env) {
|
|
1275
|
-
if (env?.R5D_USE_INTERNAL_GIT !== "1") {
|
|
1276
|
-
return {};
|
|
1277
|
-
}
|
|
1278
|
-
return {
|
|
1279
|
-
GIT_DIR: workspace.internalGit.gitDir,
|
|
1280
|
-
GIT_WORK_TREE: workspace.branchPath
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
1016
|
function formatLineNumberedContent(content, offset, limit) {
|
|
1284
1017
|
const allLines = content.split("\n");
|
|
1285
1018
|
const totalLines = allLines.length;
|
|
@@ -1388,9 +1121,11 @@ const fileMutationQueues = /* @__PURE__ */ new Map();
|
|
|
1388
1121
|
async function withFileMutationQueue(key, operation) {
|
|
1389
1122
|
const previous = fileMutationQueues.get(key) ?? Promise.resolve();
|
|
1390
1123
|
let release;
|
|
1391
|
-
const current = previous.catch(() => void 0).then(
|
|
1392
|
-
|
|
1393
|
-
|
|
1124
|
+
const current = previous.catch(() => void 0).then(
|
|
1125
|
+
() => new Promise((resolve) => {
|
|
1126
|
+
release = resolve;
|
|
1127
|
+
})
|
|
1128
|
+
);
|
|
1394
1129
|
fileMutationQueues.set(key, current);
|
|
1395
1130
|
await previous.catch(() => void 0);
|
|
1396
1131
|
try {
|
|
@@ -1485,7 +1220,9 @@ async function executeEditFileOperation(input) {
|
|
|
1485
1220
|
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1486
1221
|
}
|
|
1487
1222
|
if (occurrences > 1) {
|
|
1488
|
-
throw new Error(
|
|
1223
|
+
throw new Error(
|
|
1224
|
+
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1225
|
+
);
|
|
1489
1226
|
}
|
|
1490
1227
|
const start = originalContent.indexOf(edit.oldText);
|
|
1491
1228
|
return { index, start, end: start + edit.oldText.length, edit };
|
|
@@ -1745,96 +1482,6 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1745
1482
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1746
1483
|
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1747
1484
|
}
|
|
1748
|
-
function stagedBlobSizeBytes(context) {
|
|
1749
|
-
const names = runInternalGit(context, ["diff", "--cached", "--name-only", "-z", "--", ".", ":(exclude).git"]).split("\0").filter(Boolean);
|
|
1750
|
-
let total = 0;
|
|
1751
|
-
for (const name of names) {
|
|
1752
|
-
try {
|
|
1753
|
-
const sizeText = runInternalGit(context, ["cat-file", "-s", `:${name}`]);
|
|
1754
|
-
total += Number.parseInt(sizeText, 10) || 0;
|
|
1755
|
-
} catch {
|
|
1756
|
-
}
|
|
1757
|
-
if (total > MAX_SYNC_DIFF_BYTES) {
|
|
1758
|
-
return MAX_SYNC_DIFF_BYTES + 1;
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
return total;
|
|
1762
|
-
}
|
|
1763
|
-
function syncBranch(input) {
|
|
1764
|
-
const workspace = ensureOperationBranch(input);
|
|
1765
|
-
runInternalGit(workspace.internalGit, ["add", "-A", "--", ".", ":(exclude).git"]);
|
|
1766
|
-
const hasStagedChanges = !tryInternalGit(workspace.internalGit, ["diff", "--cached", "--quiet", "--", ".", ":(exclude).git"]);
|
|
1767
|
-
const status = getInternalStatus(workspace.internalGit);
|
|
1768
|
-
const currentCommit = getInternalCommitHash(workspace.internalGit);
|
|
1769
|
-
if (!hasStagedChanges) {
|
|
1770
|
-
return {
|
|
1771
|
-
type: "sync",
|
|
1772
|
-
changed: false,
|
|
1773
|
-
commitHash: currentCommit,
|
|
1774
|
-
diffSizeBytes: 0,
|
|
1775
|
-
status
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
const diffSizeBytes = stagedBlobSizeBytes(workspace.internalGit);
|
|
1779
|
-
if (diffSizeBytes > MAX_SYNC_DIFF_BYTES && !input.confirmedLargeDiff) {
|
|
1780
|
-
return {
|
|
1781
|
-
type: "sync",
|
|
1782
|
-
changed: true,
|
|
1783
|
-
commitHash: currentCommit,
|
|
1784
|
-
diffSizeBytes,
|
|
1785
|
-
status,
|
|
1786
|
-
largeDiffBlocked: true,
|
|
1787
|
-
trigger: input.trigger
|
|
1788
|
-
};
|
|
1789
|
-
}
|
|
1790
|
-
const message = JSON.stringify({
|
|
1791
|
-
type: "agent_changes",
|
|
1792
|
-
sessionId: input.sessionId,
|
|
1793
|
-
parentNodeId: input.parentNodeId,
|
|
1794
|
-
...input.confirmedLargeDiff ? { confirmedLargeDiff: true, confirmationReason: input.confirmationReason } : {}
|
|
1795
|
-
});
|
|
1796
|
-
runInternalGit(workspace.internalGit, ["commit", "-m", message]);
|
|
1797
|
-
const commitHash = getInternalCommitHash(workspace.internalGit);
|
|
1798
|
-
runInternalGit(workspace.internalGit, ["push", R5D_REMOTE_NAME, `HEAD:refs/heads/${input.branchName}`]);
|
|
1799
|
-
return {
|
|
1800
|
-
type: "sync",
|
|
1801
|
-
changed: true,
|
|
1802
|
-
commitHash,
|
|
1803
|
-
diffSizeBytes,
|
|
1804
|
-
status: getInternalStatus(workspace.internalGit),
|
|
1805
|
-
trigger: input.trigger
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
function confirmLargeDiff(input) {
|
|
1809
|
-
const reason = input.reason.trim();
|
|
1810
|
-
if (!reason) {
|
|
1811
|
-
throw new Error("confirm_large_diff requires a non-empty reason");
|
|
1812
|
-
}
|
|
1813
|
-
const result = syncBranch({
|
|
1814
|
-
...input,
|
|
1815
|
-
confirmedLargeDiff: true,
|
|
1816
|
-
confirmationReason: reason
|
|
1817
|
-
});
|
|
1818
|
-
return {
|
|
1819
|
-
type: "confirm_large_diff",
|
|
1820
|
-
changed: result.changed,
|
|
1821
|
-
commitHash: result.commitHash,
|
|
1822
|
-
diffSizeBytes: result.diffSizeBytes,
|
|
1823
|
-
status: result.status,
|
|
1824
|
-
reason
|
|
1825
|
-
};
|
|
1826
|
-
}
|
|
1827
|
-
function pullBranch(input) {
|
|
1828
|
-
const workspace = ensureOperationBranch(input);
|
|
1829
|
-
runInternalGit(workspace.internalGit, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1830
|
-
const target = input.commitHash ?? `${R5D_REMOTE_NAME}/${input.branchName}`;
|
|
1831
|
-
runInternalGit(workspace.internalGit, ["reset", "--hard", target]);
|
|
1832
|
-
runInternalGit(workspace.internalGit, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1833
|
-
return {
|
|
1834
|
-
type: "pull_branch",
|
|
1835
|
-
commitHash: getInternalCommitHash(workspace.internalGit)
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
1485
|
async function executeOperation(input) {
|
|
1839
1486
|
switch (input.message.type) {
|
|
1840
1487
|
case "read":
|
|
@@ -1851,43 +1498,6 @@ async function executeOperation(input) {
|
|
|
1851
1498
|
return executeLsOperation({ ...input, message: input.message });
|
|
1852
1499
|
case "view_file_bytes":
|
|
1853
1500
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
1854
|
-
case "sync":
|
|
1855
|
-
return syncBranch({
|
|
1856
|
-
projectId: input.projectId,
|
|
1857
|
-
baseUrl: input.baseUrl,
|
|
1858
|
-
token: input.token,
|
|
1859
|
-
projectRoot: input.projectRoot,
|
|
1860
|
-
syncRoot: input.syncRoot,
|
|
1861
|
-
branchName: input.message.branchName,
|
|
1862
|
-
sessionId: input.message.sessionId,
|
|
1863
|
-
parentNodeId: input.message.parentNodeId,
|
|
1864
|
-
trigger: input.message.trigger,
|
|
1865
|
-
manifest: input.manifest
|
|
1866
|
-
});
|
|
1867
|
-
case "confirm_large_diff":
|
|
1868
|
-
return confirmLargeDiff({
|
|
1869
|
-
projectId: input.projectId,
|
|
1870
|
-
baseUrl: input.baseUrl,
|
|
1871
|
-
token: input.token,
|
|
1872
|
-
projectRoot: input.projectRoot,
|
|
1873
|
-
syncRoot: input.syncRoot,
|
|
1874
|
-
branchName: input.message.branchName,
|
|
1875
|
-
sessionId: input.message.sessionId,
|
|
1876
|
-
parentNodeId: input.message.parentNodeId,
|
|
1877
|
-
reason: input.message.reason,
|
|
1878
|
-
manifest: input.manifest
|
|
1879
|
-
});
|
|
1880
|
-
case "pull_branch":
|
|
1881
|
-
return pullBranch({
|
|
1882
|
-
projectId: input.projectId,
|
|
1883
|
-
baseUrl: input.baseUrl,
|
|
1884
|
-
token: input.token,
|
|
1885
|
-
projectRoot: input.projectRoot,
|
|
1886
|
-
syncRoot: input.syncRoot,
|
|
1887
|
-
branchName: input.message.branchName,
|
|
1888
|
-
commitHash: input.message.commitHash,
|
|
1889
|
-
manifest: input.manifest
|
|
1890
|
-
});
|
|
1891
1501
|
}
|
|
1892
1502
|
}
|
|
1893
1503
|
async function executeCommand(input) {
|
|
@@ -1919,7 +1529,11 @@ async function executeCommand(input) {
|
|
|
1919
1529
|
planRoot: input.planRoot,
|
|
1920
1530
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
1921
1531
|
});
|
|
1922
|
-
const
|
|
1532
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1533
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
|
|
1534
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1535
|
+
}
|
|
1536
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
1923
1537
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
1924
1538
|
cwd,
|
|
1925
1539
|
stdout: "pipe",
|
|
@@ -1929,7 +1543,6 @@ async function executeCommand(input) {
|
|
|
1929
1543
|
...process.env,
|
|
1930
1544
|
...githubProcessEnv(),
|
|
1931
1545
|
...input.message.env ?? {},
|
|
1932
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
1933
1546
|
...artifactProcessEnv,
|
|
1934
1547
|
...planProcessEnv
|
|
1935
1548
|
}
|
|
@@ -2027,7 +1640,11 @@ async function executeStreamingCommand(input) {
|
|
|
2027
1640
|
planRoot: input.planRoot,
|
|
2028
1641
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2029
1642
|
});
|
|
2030
|
-
const
|
|
1643
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1644
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
|
|
1645
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1646
|
+
}
|
|
1647
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
2031
1648
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
2032
1649
|
cwd,
|
|
2033
1650
|
stdout: "pipe",
|
|
@@ -2037,7 +1654,6 @@ async function executeStreamingCommand(input) {
|
|
|
2037
1654
|
...process.env,
|
|
2038
1655
|
...githubProcessEnv(),
|
|
2039
1656
|
...input.message.env ?? {},
|
|
2040
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
2041
1657
|
...artifactProcessEnv,
|
|
2042
1658
|
...planProcessEnv
|
|
2043
1659
|
}
|
|
@@ -2095,22 +1711,26 @@ async function executeStreamingCommand(input) {
|
|
|
2095
1711
|
});
|
|
2096
1712
|
})
|
|
2097
1713
|
]);
|
|
2098
|
-
|
|
1714
|
+
const terminal = {
|
|
2099
1715
|
type: "exec_exit",
|
|
2100
1716
|
runId: input.message.runId,
|
|
2101
1717
|
exitCode,
|
|
2102
1718
|
durationMs: Date.now() - startedAt,
|
|
2103
1719
|
...timedOut ? { timedOut: true } : {}
|
|
2104
|
-
}
|
|
1720
|
+
};
|
|
1721
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1722
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2105
1723
|
} catch (error) {
|
|
2106
1724
|
const message = error instanceof Error ? error.message : String(error);
|
|
2107
1725
|
if (started) {
|
|
2108
|
-
|
|
1726
|
+
const terminal = {
|
|
2109
1727
|
type: "exec_error",
|
|
2110
1728
|
runId: input.message.runId,
|
|
2111
1729
|
error: message,
|
|
2112
1730
|
durationMs: Date.now() - startedAt
|
|
2113
|
-
}
|
|
1731
|
+
};
|
|
1732
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1733
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2114
1734
|
} else {
|
|
2115
1735
|
sendWorkerMessage(input.ws, {
|
|
2116
1736
|
type: "exec_start_error",
|
|
@@ -2448,10 +2068,7 @@ function resizePty(message) {
|
|
|
2448
2068
|
if (!ptyProcess) {
|
|
2449
2069
|
return;
|
|
2450
2070
|
}
|
|
2451
|
-
ptyProcess.resize(
|
|
2452
|
-
Math.max(1, Math.min(Math.floor(message.cols || 80), 500)),
|
|
2453
|
-
Math.max(1, Math.min(Math.floor(message.rows || 24), 500))
|
|
2454
|
-
);
|
|
2071
|
+
ptyProcess.resize(Math.max(1, Math.min(Math.floor(message.cols || 80), 500)), Math.max(1, Math.min(Math.floor(message.rows || 24), 500)));
|
|
2455
2072
|
}
|
|
2456
2073
|
function closePty(message) {
|
|
2457
2074
|
const ptyProcess = activePtys.get(message.ptyId);
|
|
@@ -2483,6 +2100,7 @@ async function startWorker(options) {
|
|
|
2483
2100
|
validateLabel(label);
|
|
2484
2101
|
const projectsRoot = import_node_path.default.resolve(options.projectRoot ?? defaultProjectsRoot());
|
|
2485
2102
|
const syncRoot = import_node_path.default.resolve(options.syncRoot ?? process.env.R5D_SYNC_ROOT ?? defaultSyncRoot());
|
|
2103
|
+
const workspaceShadowRoot = import_node_path.default.join(syncRoot, "workspace");
|
|
2486
2104
|
const artifactRoot = import_node_path.default.resolve(options.artifactRoot ?? process.env.R5D_ARTIFACTS_ROOT ?? defaultArtifactRoot());
|
|
2487
2105
|
const planRoot = import_node_path.default.resolve(options.planRoot ?? process.env.R5D_PLANS_ROOT ?? defaultPlanRoot());
|
|
2488
2106
|
process.stdout.write(`[r5d-worker] label: ${label}
|
|
@@ -2504,10 +2122,104 @@ async function startWorker(options) {
|
|
|
2504
2122
|
import_node_fs.default.mkdirSync(artifactRoot, { recursive: true });
|
|
2505
2123
|
import_node_fs.default.mkdirSync(planRoot, { recursive: true });
|
|
2506
2124
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
2507
|
-
|
|
2508
|
-
let
|
|
2125
|
+
const workspaceSyncSingleFlight = new import_workspace_sync.WorkspaceSyncSingleFlight();
|
|
2126
|
+
let workspaceRemoteUrl = null;
|
|
2127
|
+
let activeWorkspaceIncidentId = null;
|
|
2128
|
+
let previousPeriodicFingerprint = null;
|
|
2129
|
+
let periodicWorkspaceScan;
|
|
2130
|
+
let periodicWorkspaceScanInFlight = false;
|
|
2131
|
+
let terminalReplayTimer;
|
|
2132
|
+
let workspaceSyncRequestsInFlight = 0;
|
|
2509
2133
|
let cliUpdateInProgress = false;
|
|
2510
2134
|
let reloadAfterClose = false;
|
|
2135
|
+
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2136
|
+
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2137
|
+
return {
|
|
2138
|
+
workerLabel: label,
|
|
2139
|
+
remoteUrl: workspaceRemoteUrl,
|
|
2140
|
+
authHeader: `Authorization: Bearer ${token}`,
|
|
2141
|
+
projectsRoot,
|
|
2142
|
+
plansRoot: planRoot,
|
|
2143
|
+
shadowRoot: workspaceShadowRoot,
|
|
2144
|
+
projects: [...manifestByProjectId.values()],
|
|
2145
|
+
trigger,
|
|
2146
|
+
...overrides
|
|
2147
|
+
};
|
|
2148
|
+
};
|
|
2149
|
+
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2150
|
+
sendWorkerMessage(ws, { type: "workspace_sync_result", requestId, result });
|
|
2151
|
+
};
|
|
2152
|
+
const ensureAllVisibleWorkspaceCheckouts = () => {
|
|
2153
|
+
const created = [];
|
|
2154
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2155
|
+
for (const branchName of manifest.branches) {
|
|
2156
|
+
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId), branchName);
|
|
2157
|
+
const existed = import_node_fs.default.existsSync(import_node_path.default.join(branchPath, ".git"));
|
|
2158
|
+
const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
|
|
2159
|
+
ensureVisibleGitCheckout({
|
|
2160
|
+
projectRoot: projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId),
|
|
2161
|
+
branchName,
|
|
2162
|
+
githubRemoteUrl,
|
|
2163
|
+
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2164
|
+
githubAuthHeader: manifest.repoAuthHeader,
|
|
2165
|
+
defaultBranch: manifest.defaultBranch || "main",
|
|
2166
|
+
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2167
|
+
});
|
|
2168
|
+
if (!existed) created.push({ projectId: manifest.projectId, branchName });
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return created;
|
|
2172
|
+
};
|
|
2173
|
+
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2174
|
+
workspaceSyncRequestsInFlight += 1;
|
|
2175
|
+
try {
|
|
2176
|
+
if (trigger.type === "plan_write" || trigger.type === "task_status" || trigger.type === "connect" || trigger.type === "inbound_head" || trigger.type === "manual") {
|
|
2177
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2178
|
+
for (const branchName of manifest.branches) {
|
|
2179
|
+
await syncProjectPlans({
|
|
2180
|
+
baseUrl,
|
|
2181
|
+
token,
|
|
2182
|
+
projectId: manifest.projectId,
|
|
2183
|
+
branchName,
|
|
2184
|
+
planRoot
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
const newVisibleCheckouts = trigger.type === "connect" ? ensureAllVisibleWorkspaceCheckouts() : [];
|
|
2190
|
+
const result = await workspaceSyncSingleFlight.run(
|
|
2191
|
+
workspaceSyncInput(trigger, {
|
|
2192
|
+
...overrides,
|
|
2193
|
+
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2194
|
+
})
|
|
2195
|
+
);
|
|
2196
|
+
previousPeriodicFingerprint = null;
|
|
2197
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2198
|
+
return result;
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
const result = {
|
|
2201
|
+
type: "workspace_sync",
|
|
2202
|
+
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2203
|
+
workerLabel: label,
|
|
2204
|
+
trigger,
|
|
2205
|
+
outcome: "failed",
|
|
2206
|
+
startingHead: null,
|
|
2207
|
+
rebaseCount: 0,
|
|
2208
|
+
diffSizeBytes: 0,
|
|
2209
|
+
gitStatus: "",
|
|
2210
|
+
affectedProjects: [],
|
|
2211
|
+
affectedPaths: [],
|
|
2212
|
+
discardedPaths: [],
|
|
2213
|
+
localChangesDiscarded: false,
|
|
2214
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2215
|
+
};
|
|
2216
|
+
previousPeriodicFingerprint = null;
|
|
2217
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2218
|
+
return result;
|
|
2219
|
+
} finally {
|
|
2220
|
+
workspaceSyncRequestsInFlight -= 1;
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2511
2223
|
const ws = new WebSocket(websocketUrl(baseUrl, label), {
|
|
2512
2224
|
headers: {
|
|
2513
2225
|
Authorization: `Bearer ${token}`
|
|
@@ -2552,6 +2264,15 @@ async function startWorker(options) {
|
|
|
2552
2264
|
};
|
|
2553
2265
|
ws.send(JSON.stringify(hello));
|
|
2554
2266
|
sendActiveProcessReport(ws);
|
|
2267
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2268
|
+
sendWorkerMessage(ws, terminal);
|
|
2269
|
+
}
|
|
2270
|
+
terminalReplayTimer = setInterval(() => {
|
|
2271
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2272
|
+
sendWorkerMessage(ws, terminal);
|
|
2273
|
+
}
|
|
2274
|
+
}, 5e3);
|
|
2275
|
+
terminalReplayTimer.unref();
|
|
2555
2276
|
process.stdout.write("[r5d-worker] connected\n");
|
|
2556
2277
|
});
|
|
2557
2278
|
ws.addEventListener("message", (event) => {
|
|
@@ -2560,92 +2281,70 @@ async function startWorker(options) {
|
|
|
2560
2281
|
if (message.type === "connected") {
|
|
2561
2282
|
return;
|
|
2562
2283
|
}
|
|
2563
|
-
if (message.type === "
|
|
2284
|
+
if (message.type === "workspace_manifest") {
|
|
2564
2285
|
configureGitHubAuth(message.githubCredential);
|
|
2565
2286
|
visibleGitIdentity = message.gitIdentity;
|
|
2287
|
+
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2566
2288
|
manifestByProjectId.clear();
|
|
2567
|
-
for (const project of message.projects)
|
|
2568
|
-
|
|
2569
|
-
}
|
|
2570
|
-
process.stdout.write(`[r5d-worker] project manifest: ${message.projects.length} projects
|
|
2289
|
+
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2290
|
+
process.stdout.write(`[r5d-worker] workspace manifest: ${message.projects.length} projects
|
|
2571
2291
|
`);
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
}
|
|
2584
|
-
});
|
|
2585
|
-
return;
|
|
2586
|
-
}
|
|
2587
|
-
let syncResult = { status: "completed", results: [], blockedBy: [] };
|
|
2588
|
-
const executeSync = async () => {
|
|
2589
|
-
repositorySyncInProgress = true;
|
|
2590
|
-
try {
|
|
2591
|
-
syncResult = await syncManifestProjectsFromInternal({
|
|
2592
|
-
baseUrl,
|
|
2593
|
-
token,
|
|
2594
|
-
projectsRoot,
|
|
2595
|
-
syncRoot,
|
|
2596
|
-
manifests: [...manifestByProjectId.values()],
|
|
2597
|
-
projectIds: message.projectIds
|
|
2598
|
-
});
|
|
2599
|
-
for (const result of syncResult.results) {
|
|
2600
|
-
if (result.status !== "synced") continue;
|
|
2601
|
-
try {
|
|
2602
|
-
await syncProjectPlans({
|
|
2603
|
-
baseUrl,
|
|
2604
|
-
token,
|
|
2605
|
-
projectId: result.projectId,
|
|
2606
|
-
branchName: result.branchName,
|
|
2607
|
-
planRoot
|
|
2608
|
-
});
|
|
2609
|
-
} catch (error) {
|
|
2610
|
-
process.stderr.write(
|
|
2611
|
-
`[r5d-worker] plan sync skipped for ${result.projectId}/${result.branchName}: ${error instanceof Error ? error.message : String(error)}
|
|
2612
|
-
`
|
|
2613
|
-
);
|
|
2292
|
+
if (!periodicWorkspaceScan) {
|
|
2293
|
+
const emptyFingerprint = (0, import_node_crypto.createHash)("sha256").update("").digest("hex");
|
|
2294
|
+
periodicWorkspaceScan = setInterval(() => {
|
|
2295
|
+
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2296
|
+
periodicWorkspaceScanInFlight = true;
|
|
2297
|
+
void (async () => {
|
|
2298
|
+
const input = workspaceSyncInput({ type: "periodic" });
|
|
2299
|
+
const fingerprint = await workspaceSyncSingleFlight.fingerprint(input);
|
|
2300
|
+
if (fingerprint === emptyFingerprint) {
|
|
2301
|
+
previousPeriodicFingerprint = null;
|
|
2302
|
+
return;
|
|
2614
2303
|
}
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
}
|
|
2619
|
-
};
|
|
2620
|
-
const queued = repositorySyncQueue.then(executeSync, executeSync);
|
|
2621
|
-
repositorySyncQueue = queued.then(
|
|
2622
|
-
() => void 0,
|
|
2623
|
-
() => void 0
|
|
2624
|
-
);
|
|
2625
|
-
try {
|
|
2626
|
-
await queued;
|
|
2627
|
-
} catch (error) {
|
|
2628
|
-
syncResult = {
|
|
2629
|
-
status: "partial",
|
|
2630
|
-
results: [
|
|
2631
|
-
{
|
|
2632
|
-
projectId: "worker",
|
|
2633
|
-
projectPath: "worker",
|
|
2634
|
-
branchName: "all",
|
|
2635
|
-
status: "failed",
|
|
2636
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2304
|
+
if (previousPeriodicFingerprint !== fingerprint) {
|
|
2305
|
+
previousPeriodicFingerprint = fingerprint;
|
|
2306
|
+
return;
|
|
2637
2307
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2308
|
+
previousPeriodicFingerprint = null;
|
|
2309
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "periodic" });
|
|
2310
|
+
})().catch((error) => {
|
|
2311
|
+
process.stderr.write(
|
|
2312
|
+
`[r5d-worker] periodic workspace synchronization failed: ${error instanceof Error ? error.message : String(error)}
|
|
2313
|
+
`
|
|
2314
|
+
);
|
|
2315
|
+
}).finally(() => {
|
|
2316
|
+
periodicWorkspaceScanInFlight = false;
|
|
2317
|
+
});
|
|
2318
|
+
}, import_workspace_sync.WORKSPACE_PERIODIC_SCAN_INTERVAL_MS);
|
|
2319
|
+
periodicWorkspaceScan.unref();
|
|
2641
2320
|
}
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
if (message.type === "sync_workspace") {
|
|
2324
|
+
await runRequestedWorkspaceSync(message.requestId, message.trigger, {
|
|
2325
|
+
attemptId: message.attemptId,
|
|
2326
|
+
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
2327
|
+
confirmationReason: message.confirmationReason,
|
|
2328
|
+
resetToCanonical: message.resetToCanonical,
|
|
2329
|
+
skipVisibleMirror: message.skipVisibleMirror
|
|
2646
2330
|
});
|
|
2647
2331
|
return;
|
|
2648
2332
|
}
|
|
2333
|
+
if (message.type === "workspace_head_updated") {
|
|
2334
|
+
if (!activeWorkspaceIncidentId) {
|
|
2335
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "inbound_head", detail: message.commitHash });
|
|
2336
|
+
}
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
if (message.type === "workspace_incident_updated") {
|
|
2340
|
+
activeWorkspaceIncidentId = message.status === "remediating" || message.status === "waiting_for_worker" ? message.incidentId : null;
|
|
2341
|
+
previousPeriodicFingerprint = null;
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
if (message.type === "exec_terminal_ack") {
|
|
2345
|
+
pendingProcessTerminals.delete(message.runId);
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2649
2348
|
if (message.type === "update_clis") {
|
|
2650
2349
|
if (cliUpdateInProgress) {
|
|
2651
2350
|
sendWorkerMessage(ws, {
|
|
@@ -2655,7 +2354,7 @@ async function startWorker(options) {
|
|
|
2655
2354
|
});
|
|
2656
2355
|
return;
|
|
2657
2356
|
}
|
|
2658
|
-
if (activeProcesses.size > 0 || activePtys.size > 0 ||
|
|
2357
|
+
if (activeProcesses.size > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0) {
|
|
2659
2358
|
sendWorkerMessage(ws, {
|
|
2660
2359
|
type: "update_clis_result",
|
|
2661
2360
|
requestId: message.requestId,
|
|
@@ -2725,7 +2424,6 @@ async function startWorker(options) {
|
|
|
2725
2424
|
});
|
|
2726
2425
|
return;
|
|
2727
2426
|
}
|
|
2728
|
-
await repositorySyncQueue;
|
|
2729
2427
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2730
2428
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2731
2429
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.projectId}/${message.branchName}
|
|
@@ -2756,7 +2454,6 @@ async function startWorker(options) {
|
|
|
2756
2454
|
});
|
|
2757
2455
|
return;
|
|
2758
2456
|
}
|
|
2759
|
-
await repositorySyncQueue;
|
|
2760
2457
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2761
2458
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2762
2459
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
@@ -2770,6 +2467,7 @@ async function startWorker(options) {
|
|
|
2770
2467
|
syncRoot,
|
|
2771
2468
|
artifactRoot,
|
|
2772
2469
|
planRoot,
|
|
2470
|
+
workspaceShadowRoot,
|
|
2773
2471
|
manifest
|
|
2774
2472
|
});
|
|
2775
2473
|
ws.send(JSON.stringify(result));
|
|
@@ -2785,7 +2483,6 @@ async function startWorker(options) {
|
|
|
2785
2483
|
});
|
|
2786
2484
|
return;
|
|
2787
2485
|
}
|
|
2788
|
-
await repositorySyncQueue;
|
|
2789
2486
|
sendWorkerMessage(ws, {
|
|
2790
2487
|
type: "exec_accepted",
|
|
2791
2488
|
requestId: message.requestId,
|
|
@@ -2806,6 +2503,7 @@ async function startWorker(options) {
|
|
|
2806
2503
|
syncRoot,
|
|
2807
2504
|
artifactRoot,
|
|
2808
2505
|
planRoot,
|
|
2506
|
+
workspaceShadowRoot,
|
|
2809
2507
|
manifest
|
|
2810
2508
|
}).catch((error) => {
|
|
2811
2509
|
sendWorkerMessage(ws, {
|
|
@@ -2825,9 +2523,8 @@ async function startWorker(options) {
|
|
|
2825
2523
|
}
|
|
2826
2524
|
return;
|
|
2827
2525
|
}
|
|
2828
|
-
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes"
|
|
2526
|
+
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
|
|
2829
2527
|
try {
|
|
2830
|
-
await repositorySyncQueue;
|
|
2831
2528
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2832
2529
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2833
2530
|
const result = await executeOperation({
|
|
@@ -2875,6 +2572,14 @@ async function startWorker(options) {
|
|
|
2875
2572
|
});
|
|
2876
2573
|
ws.addEventListener("close", (event) => {
|
|
2877
2574
|
stopHeartbeatWatchdog();
|
|
2575
|
+
if (periodicWorkspaceScan) {
|
|
2576
|
+
clearInterval(periodicWorkspaceScan);
|
|
2577
|
+
periodicWorkspaceScan = void 0;
|
|
2578
|
+
}
|
|
2579
|
+
if (terminalReplayTimer) {
|
|
2580
|
+
clearInterval(terminalReplayTimer);
|
|
2581
|
+
terminalReplayTimer = void 0;
|
|
2582
|
+
}
|
|
2878
2583
|
if (currentWorkerSocket === ws) {
|
|
2879
2584
|
currentWorkerSocket = null;
|
|
2880
2585
|
}
|
|
@@ -2885,15 +2590,17 @@ async function startWorker(options) {
|
|
|
2885
2590
|
if (reloadAfterClose) {
|
|
2886
2591
|
process.exit(import_supervisor.WORKER_RELOAD_EXIT_CODE);
|
|
2887
2592
|
}
|
|
2888
|
-
if (activeProcesses.size > 0) {
|
|
2593
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
2889
2594
|
const delayMs = 2e3;
|
|
2890
|
-
process.stderr.write(
|
|
2891
|
-
`);
|
|
2595
|
+
process.stderr.write(
|
|
2596
|
+
`[r5d-worker] ${activeProcesses.size} process(es) active and ${pendingProcessTerminals.size} terminal report(s) pending; reconnecting in ${delayMs}ms
|
|
2597
|
+
`
|
|
2598
|
+
);
|
|
2892
2599
|
const reconnect = () => {
|
|
2893
2600
|
void startWorker(options).catch((error) => {
|
|
2894
2601
|
process.stderr.write(`[r5d-worker] reconnect failed: ${error instanceof Error ? error.message : String(error)}
|
|
2895
2602
|
`);
|
|
2896
|
-
if (activeProcesses.size > 0) {
|
|
2603
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
2897
2604
|
setTimeout(reconnect, delayMs);
|
|
2898
2605
|
return;
|
|
2899
2606
|
}
|
|
@@ -2949,7 +2656,6 @@ if (isCliEntrypoint()) {
|
|
|
2949
2656
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2950
2657
|
0 && (module.exports = {
|
|
2951
2658
|
ensureVisibleGitCheckout,
|
|
2952
|
-
findRepositorySyncBlockers,
|
|
2953
2659
|
findWorkerFiles,
|
|
2954
2660
|
githubCliEnv,
|
|
2955
2661
|
grepWorkerFiles,
|
|
@@ -2962,7 +2668,6 @@ if (isCliEntrypoint()) {
|
|
|
2962
2668
|
resolveHostShell,
|
|
2963
2669
|
resolveProjectFilePath,
|
|
2964
2670
|
resolveWorkerFilePath,
|
|
2965
|
-
syncManifestProjectsFromInternal,
|
|
2966
2671
|
syncProjectPlans,
|
|
2967
2672
|
syncSessionArtifacts
|
|
2968
2673
|
});
|