@ricsam/r5d-worker 0.0.36 → 0.0.38
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/README.md +5 -1
- package/dist/cjs/main.cjs +234 -525
- package/dist/cjs/managed-paths.cjs +158 -0
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync.cjs +653 -0
- package/dist/mjs/main.mjs +239 -523
- package/dist/mjs/managed-paths.mjs +116 -0
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-sync.mjs +608 -0
- package/dist/types/main.d.ts +0 -50
- package/dist/types/managed-paths.d.ts +15 -0
- package/dist/types/workspace-sync.d.ts +74 -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,18 +56,17 @@ 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_managed_paths = require("./managed-paths.cjs");
|
|
60
|
+
var import_workspace_sync = require("./workspace-sync.cjs");
|
|
61
61
|
const import_meta = {};
|
|
62
62
|
const DEFAULT_BASE_URL = "https://r5d.dev";
|
|
63
63
|
const WORKER_PACKAGE_NAME = "@ricsam/r5d-worker";
|
|
64
64
|
const LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
|
|
65
|
-
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
|
}
|
|
@@ -603,9 +604,7 @@ function validateProjectId(projectId) {
|
|
|
603
604
|
}
|
|
604
605
|
}
|
|
605
606
|
function validateBranchName(branchName) {
|
|
606
|
-
|
|
607
|
-
throw new Error(`Invalid branch name from server: ${branchName}`);
|
|
608
|
-
}
|
|
607
|
+
(0, import_managed_paths.validateManagedBranchName)(branchName);
|
|
609
608
|
}
|
|
610
609
|
function validatePlanId(planId) {
|
|
611
610
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(planId)) {
|
|
@@ -639,27 +638,6 @@ function runGit(args, options = {}) {
|
|
|
639
638
|
}
|
|
640
639
|
return result.stdout.toString().trim();
|
|
641
640
|
}
|
|
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
641
|
function tryGit(args, options = {}) {
|
|
664
642
|
try {
|
|
665
643
|
runGit(args, options);
|
|
@@ -668,34 +646,6 @@ function tryGit(args, options = {}) {
|
|
|
668
646
|
return false;
|
|
669
647
|
}
|
|
670
648
|
}
|
|
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
649
|
function getGitBlobHashForContent(content) {
|
|
700
650
|
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
701
651
|
return (0, import_node_crypto.createHash)("sha1").update(`blob ${buffer.length}\0`).update(buffer).digest("hex");
|
|
@@ -703,12 +653,6 @@ function getGitBlobHashForContent(content) {
|
|
|
703
653
|
function hasWorktreeChanges(cwd) {
|
|
704
654
|
return runGit(["status", "--porcelain"], { cwd }).trim().length > 0;
|
|
705
655
|
}
|
|
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
656
|
function isInsideBranchPath(branchPath, filePath) {
|
|
713
657
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(branchPath), import_node_path.default.resolve(filePath));
|
|
714
658
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
@@ -771,32 +715,15 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
771
715
|
function resolveProjectFilePath(branchPath, inputPath) {
|
|
772
716
|
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
773
717
|
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
|
-
);
|
|
718
|
+
throw new Error(`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`);
|
|
777
719
|
}
|
|
778
720
|
return resolved;
|
|
779
721
|
}
|
|
780
|
-
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
781
|
-
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
782
|
-
return remoteUrl;
|
|
783
|
-
}
|
|
784
|
-
return new URL(remoteUrl, baseUrl).toString();
|
|
785
|
-
}
|
|
786
722
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
787
723
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
788
724
|
}
|
|
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
725
|
function githubRemoteUrlFor(baseUrl, projectId, manifest) {
|
|
799
|
-
return manifest?.repoHttpUrl ??
|
|
726
|
+
return manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId);
|
|
800
727
|
}
|
|
801
728
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
802
729
|
try {
|
|
@@ -814,12 +741,6 @@ function gitAuthForRemote(remoteUrl, authHeader) {
|
|
|
814
741
|
header: authHeader
|
|
815
742
|
};
|
|
816
743
|
}
|
|
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
744
|
function hasNormalVisibleGitDir(branchPath) {
|
|
824
745
|
const gitPath = import_node_path.default.join(branchPath, ".git");
|
|
825
746
|
return import_node_fs.default.existsSync(gitPath) && import_node_fs.default.statSync(gitPath).isDirectory();
|
|
@@ -831,13 +752,6 @@ function ensureOriginRemote(branchPath, remoteUrl) {
|
|
|
831
752
|
runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
832
753
|
}
|
|
833
754
|
}
|
|
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
755
|
function shellQuote(value) {
|
|
842
756
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
843
757
|
}
|
|
@@ -1033,194 +947,20 @@ function ensureVisibleGitCheckout(input) {
|
|
|
1033
947
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1034
948
|
return branchPath;
|
|
1035
949
|
}
|
|
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
950
|
function ensureBranchWorkspace(input) {
|
|
1069
951
|
const defaultBranch = input.manifest?.defaultBranch || "main";
|
|
1070
952
|
const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1071
953
|
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
954
|
return {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
955
|
+
branchPath: ensureVisibleGitCheckout({
|
|
956
|
+
projectRoot: input.projectRoot,
|
|
957
|
+
branchName: input.branchName,
|
|
958
|
+
githubRemoteUrl,
|
|
959
|
+
githubAuth,
|
|
960
|
+
githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
|
|
961
|
+
defaultBranch,
|
|
962
|
+
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
|
|
963
|
+
})
|
|
1224
964
|
};
|
|
1225
965
|
}
|
|
1226
966
|
function resolveCommandCwd(branchPath, cwd) {
|
|
@@ -1271,15 +1011,6 @@ async function streamCommandOutput(stream, onData) {
|
|
|
1271
1011
|
function ensureOperationBranch(input) {
|
|
1272
1012
|
return ensureBranchWorkspace(input);
|
|
1273
1013
|
}
|
|
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
1014
|
function formatLineNumberedContent(content, offset, limit) {
|
|
1284
1015
|
const allLines = content.split("\n");
|
|
1285
1016
|
const totalLines = allLines.length;
|
|
@@ -1388,9 +1119,11 @@ const fileMutationQueues = /* @__PURE__ */ new Map();
|
|
|
1388
1119
|
async function withFileMutationQueue(key, operation) {
|
|
1389
1120
|
const previous = fileMutationQueues.get(key) ?? Promise.resolve();
|
|
1390
1121
|
let release;
|
|
1391
|
-
const current = previous.catch(() => void 0).then(
|
|
1392
|
-
|
|
1393
|
-
|
|
1122
|
+
const current = previous.catch(() => void 0).then(
|
|
1123
|
+
() => new Promise((resolve) => {
|
|
1124
|
+
release = resolve;
|
|
1125
|
+
})
|
|
1126
|
+
);
|
|
1394
1127
|
fileMutationQueues.set(key, current);
|
|
1395
1128
|
await previous.catch(() => void 0);
|
|
1396
1129
|
try {
|
|
@@ -1485,7 +1218,9 @@ async function executeEditFileOperation(input) {
|
|
|
1485
1218
|
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1486
1219
|
}
|
|
1487
1220
|
if (occurrences > 1) {
|
|
1488
|
-
throw new Error(
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1223
|
+
);
|
|
1489
1224
|
}
|
|
1490
1225
|
const start = originalContent.indexOf(edit.oldText);
|
|
1491
1226
|
return { index, start, end: start + edit.oldText.length, edit };
|
|
@@ -1745,96 +1480,6 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1745
1480
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1746
1481
|
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1747
1482
|
}
|
|
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
1483
|
async function executeOperation(input) {
|
|
1839
1484
|
switch (input.message.type) {
|
|
1840
1485
|
case "read":
|
|
@@ -1851,43 +1496,6 @@ async function executeOperation(input) {
|
|
|
1851
1496
|
return executeLsOperation({ ...input, message: input.message });
|
|
1852
1497
|
case "view_file_bytes":
|
|
1853
1498
|
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
1499
|
}
|
|
1892
1500
|
}
|
|
1893
1501
|
async function executeCommand(input) {
|
|
@@ -1919,7 +1527,11 @@ async function executeCommand(input) {
|
|
|
1919
1527
|
planRoot: input.planRoot,
|
|
1920
1528
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
1921
1529
|
});
|
|
1922
|
-
const
|
|
1530
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1531
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
|
|
1532
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1533
|
+
}
|
|
1534
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
1923
1535
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
1924
1536
|
cwd,
|
|
1925
1537
|
stdout: "pipe",
|
|
@@ -1929,7 +1541,6 @@ async function executeCommand(input) {
|
|
|
1929
1541
|
...process.env,
|
|
1930
1542
|
...githubProcessEnv(),
|
|
1931
1543
|
...input.message.env ?? {},
|
|
1932
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
1933
1544
|
...artifactProcessEnv,
|
|
1934
1545
|
...planProcessEnv
|
|
1935
1546
|
}
|
|
@@ -2027,7 +1638,11 @@ async function executeStreamingCommand(input) {
|
|
|
2027
1638
|
planRoot: input.planRoot,
|
|
2028
1639
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2029
1640
|
});
|
|
2030
|
-
const
|
|
1641
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1642
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
|
|
1643
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1644
|
+
}
|
|
1645
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
2031
1646
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
2032
1647
|
cwd,
|
|
2033
1648
|
stdout: "pipe",
|
|
@@ -2037,7 +1652,6 @@ async function executeStreamingCommand(input) {
|
|
|
2037
1652
|
...process.env,
|
|
2038
1653
|
...githubProcessEnv(),
|
|
2039
1654
|
...input.message.env ?? {},
|
|
2040
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
2041
1655
|
...artifactProcessEnv,
|
|
2042
1656
|
...planProcessEnv
|
|
2043
1657
|
}
|
|
@@ -2095,22 +1709,26 @@ async function executeStreamingCommand(input) {
|
|
|
2095
1709
|
});
|
|
2096
1710
|
})
|
|
2097
1711
|
]);
|
|
2098
|
-
|
|
1712
|
+
const terminal = {
|
|
2099
1713
|
type: "exec_exit",
|
|
2100
1714
|
runId: input.message.runId,
|
|
2101
1715
|
exitCode,
|
|
2102
1716
|
durationMs: Date.now() - startedAt,
|
|
2103
1717
|
...timedOut ? { timedOut: true } : {}
|
|
2104
|
-
}
|
|
1718
|
+
};
|
|
1719
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1720
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2105
1721
|
} catch (error) {
|
|
2106
1722
|
const message = error instanceof Error ? error.message : String(error);
|
|
2107
1723
|
if (started) {
|
|
2108
|
-
|
|
1724
|
+
const terminal = {
|
|
2109
1725
|
type: "exec_error",
|
|
2110
1726
|
runId: input.message.runId,
|
|
2111
1727
|
error: message,
|
|
2112
1728
|
durationMs: Date.now() - startedAt
|
|
2113
|
-
}
|
|
1729
|
+
};
|
|
1730
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1731
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2114
1732
|
} else {
|
|
2115
1733
|
sendWorkerMessage(input.ws, {
|
|
2116
1734
|
type: "exec_start_error",
|
|
@@ -2448,10 +2066,7 @@ function resizePty(message) {
|
|
|
2448
2066
|
if (!ptyProcess) {
|
|
2449
2067
|
return;
|
|
2450
2068
|
}
|
|
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
|
-
);
|
|
2069
|
+
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
2070
|
}
|
|
2456
2071
|
function closePty(message) {
|
|
2457
2072
|
const ptyProcess = activePtys.get(message.ptyId);
|
|
@@ -2471,10 +2086,13 @@ function websocketUrl(baseUrl, label) {
|
|
|
2471
2086
|
const url = new URL("/worker/ws", baseUrl);
|
|
2472
2087
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
2473
2088
|
url.searchParams.set("label", label);
|
|
2089
|
+
url.searchParams.set("version", getWorkerVersion());
|
|
2474
2090
|
return url.toString();
|
|
2475
2091
|
}
|
|
2476
2092
|
function projectRootFor(projectsRoot, projectId, manifestByProjectId) {
|
|
2477
|
-
|
|
2093
|
+
const manifest = manifestByProjectId.get(projectId);
|
|
2094
|
+
if (!manifest) throw new Error(`Project ${projectId} is missing from the worker workspace manifest`);
|
|
2095
|
+
return (0, import_managed_paths.managedProjectRoot)(projectsRoot, manifest.checkoutPathSegments);
|
|
2478
2096
|
}
|
|
2479
2097
|
async function startWorker(options) {
|
|
2480
2098
|
const config = readConfig(options.configPath);
|
|
@@ -2483,6 +2101,7 @@ async function startWorker(options) {
|
|
|
2483
2101
|
validateLabel(label);
|
|
2484
2102
|
const projectsRoot = import_node_path.default.resolve(options.projectRoot ?? defaultProjectsRoot());
|
|
2485
2103
|
const syncRoot = import_node_path.default.resolve(options.syncRoot ?? process.env.R5D_SYNC_ROOT ?? defaultSyncRoot());
|
|
2104
|
+
const workspaceShadowRoot = import_node_path.default.join(syncRoot, "workspace");
|
|
2486
2105
|
const artifactRoot = import_node_path.default.resolve(options.artifactRoot ?? process.env.R5D_ARTIFACTS_ROOT ?? defaultArtifactRoot());
|
|
2487
2106
|
const planRoot = import_node_path.default.resolve(options.planRoot ?? process.env.R5D_PLANS_ROOT ?? defaultPlanRoot());
|
|
2488
2107
|
process.stdout.write(`[r5d-worker] label: ${label}
|
|
@@ -2504,10 +2123,104 @@ async function startWorker(options) {
|
|
|
2504
2123
|
import_node_fs.default.mkdirSync(artifactRoot, { recursive: true });
|
|
2505
2124
|
import_node_fs.default.mkdirSync(planRoot, { recursive: true });
|
|
2506
2125
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
2507
|
-
|
|
2508
|
-
let
|
|
2126
|
+
const workspaceSyncSingleFlight = new import_workspace_sync.WorkspaceSyncSingleFlight();
|
|
2127
|
+
let workspaceRemoteUrl = null;
|
|
2128
|
+
let activeWorkspaceIncidentId = null;
|
|
2129
|
+
let previousPeriodicFingerprint = null;
|
|
2130
|
+
let periodicWorkspaceScan;
|
|
2131
|
+
let periodicWorkspaceScanInFlight = false;
|
|
2132
|
+
let terminalReplayTimer;
|
|
2133
|
+
let workspaceSyncRequestsInFlight = 0;
|
|
2509
2134
|
let cliUpdateInProgress = false;
|
|
2510
2135
|
let reloadAfterClose = false;
|
|
2136
|
+
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2137
|
+
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2138
|
+
return {
|
|
2139
|
+
workerLabel: label,
|
|
2140
|
+
remoteUrl: workspaceRemoteUrl,
|
|
2141
|
+
authHeader: `Authorization: Bearer ${token}`,
|
|
2142
|
+
projectsRoot,
|
|
2143
|
+
plansRoot: planRoot,
|
|
2144
|
+
shadowRoot: workspaceShadowRoot,
|
|
2145
|
+
projects: [...manifestByProjectId.values()],
|
|
2146
|
+
trigger,
|
|
2147
|
+
...overrides
|
|
2148
|
+
};
|
|
2149
|
+
};
|
|
2150
|
+
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2151
|
+
sendWorkerMessage(ws, { type: "workspace_sync_result", requestId, result });
|
|
2152
|
+
};
|
|
2153
|
+
const ensureAllVisibleWorkspaceCheckouts = () => {
|
|
2154
|
+
const created = [];
|
|
2155
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2156
|
+
for (const branchName of manifest.branches) {
|
|
2157
|
+
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId), branchName);
|
|
2158
|
+
const existed = import_node_fs.default.existsSync(import_node_path.default.join(branchPath, ".git"));
|
|
2159
|
+
const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
|
|
2160
|
+
ensureVisibleGitCheckout({
|
|
2161
|
+
projectRoot: projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId),
|
|
2162
|
+
branchName,
|
|
2163
|
+
githubRemoteUrl,
|
|
2164
|
+
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2165
|
+
githubAuthHeader: manifest.repoAuthHeader,
|
|
2166
|
+
defaultBranch: manifest.defaultBranch || "main",
|
|
2167
|
+
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2168
|
+
});
|
|
2169
|
+
if (!existed) created.push({ projectId: manifest.projectId, branchName });
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return created;
|
|
2173
|
+
};
|
|
2174
|
+
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2175
|
+
workspaceSyncRequestsInFlight += 1;
|
|
2176
|
+
try {
|
|
2177
|
+
if (trigger.type === "plan_write" || trigger.type === "task_status" || trigger.type === "connect" || trigger.type === "inbound_head" || trigger.type === "manual") {
|
|
2178
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2179
|
+
for (const branchName of manifest.branches) {
|
|
2180
|
+
await syncProjectPlans({
|
|
2181
|
+
baseUrl,
|
|
2182
|
+
token,
|
|
2183
|
+
projectId: manifest.projectId,
|
|
2184
|
+
branchName,
|
|
2185
|
+
planRoot
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
const newVisibleCheckouts = trigger.type === "connect" ? ensureAllVisibleWorkspaceCheckouts() : [];
|
|
2191
|
+
const result = await workspaceSyncSingleFlight.run(
|
|
2192
|
+
workspaceSyncInput(trigger, {
|
|
2193
|
+
...overrides,
|
|
2194
|
+
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2195
|
+
})
|
|
2196
|
+
);
|
|
2197
|
+
previousPeriodicFingerprint = null;
|
|
2198
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2199
|
+
return result;
|
|
2200
|
+
} catch (error) {
|
|
2201
|
+
const result = {
|
|
2202
|
+
type: "workspace_sync",
|
|
2203
|
+
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2204
|
+
workerLabel: label,
|
|
2205
|
+
trigger,
|
|
2206
|
+
outcome: "failed",
|
|
2207
|
+
startingHead: null,
|
|
2208
|
+
rebaseCount: 0,
|
|
2209
|
+
diffSizeBytes: 0,
|
|
2210
|
+
gitStatus: "",
|
|
2211
|
+
affectedProjects: [],
|
|
2212
|
+
affectedPaths: [],
|
|
2213
|
+
discardedPaths: [],
|
|
2214
|
+
localChangesDiscarded: false,
|
|
2215
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2216
|
+
};
|
|
2217
|
+
previousPeriodicFingerprint = null;
|
|
2218
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2219
|
+
return result;
|
|
2220
|
+
} finally {
|
|
2221
|
+
workspaceSyncRequestsInFlight -= 1;
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2511
2224
|
const ws = new WebSocket(websocketUrl(baseUrl, label), {
|
|
2512
2225
|
headers: {
|
|
2513
2226
|
Authorization: `Bearer ${token}`
|
|
@@ -2552,6 +2265,15 @@ async function startWorker(options) {
|
|
|
2552
2265
|
};
|
|
2553
2266
|
ws.send(JSON.stringify(hello));
|
|
2554
2267
|
sendActiveProcessReport(ws);
|
|
2268
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2269
|
+
sendWorkerMessage(ws, terminal);
|
|
2270
|
+
}
|
|
2271
|
+
terminalReplayTimer = setInterval(() => {
|
|
2272
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2273
|
+
sendWorkerMessage(ws, terminal);
|
|
2274
|
+
}
|
|
2275
|
+
}, 5e3);
|
|
2276
|
+
terminalReplayTimer.unref();
|
|
2555
2277
|
process.stdout.write("[r5d-worker] connected\n");
|
|
2556
2278
|
});
|
|
2557
2279
|
ws.addEventListener("message", (event) => {
|
|
@@ -2560,92 +2282,73 @@ async function startWorker(options) {
|
|
|
2560
2282
|
if (message.type === "connected") {
|
|
2561
2283
|
return;
|
|
2562
2284
|
}
|
|
2563
|
-
if (message.type === "
|
|
2285
|
+
if (message.type === "workspace_manifest") {
|
|
2564
2286
|
configureGitHubAuth(message.githubCredential);
|
|
2565
2287
|
visibleGitIdentity = message.gitIdentity;
|
|
2288
|
+
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2289
|
+
const migratedProjectIds = (0, import_managed_paths.migrateLegacyProjectRoots)(projectsRoot, message.projects);
|
|
2566
2290
|
manifestByProjectId.clear();
|
|
2567
|
-
for (const project of message.projects)
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
process.stdout.write(`[r5d-worker] project manifest: ${message.projects.length} projects
|
|
2571
|
-
`);
|
|
2572
|
-
return;
|
|
2573
|
-
}
|
|
2574
|
-
if (message.type === "sync_projects") {
|
|
2575
|
-
if (cliUpdateInProgress) {
|
|
2576
|
-
sendWorkerMessage(ws, {
|
|
2577
|
-
type: "sync_projects_result",
|
|
2578
|
-
requestId: message.requestId,
|
|
2579
|
-
result: {
|
|
2580
|
-
status: "blocked",
|
|
2581
|
-
results: [],
|
|
2582
|
-
blockedBy: []
|
|
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)}
|
|
2291
|
+
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2292
|
+
process.stdout.write(
|
|
2293
|
+
`[r5d-worker] workspace manifest: ${message.projects.length} projects${migratedProjectIds.length > 0 ? `; migrated ${migratedProjectIds.length} project checkout root(s)` : ""}
|
|
2612
2294
|
`
|
|
2613
|
-
);
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
} finally {
|
|
2617
|
-
repositorySyncInProgress = false;
|
|
2618
|
-
}
|
|
2619
|
-
};
|
|
2620
|
-
const queued = repositorySyncQueue.then(executeSync, executeSync);
|
|
2621
|
-
repositorySyncQueue = queued.then(
|
|
2622
|
-
() => void 0,
|
|
2623
|
-
() => void 0
|
|
2624
2295
|
);
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
{
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2296
|
+
if (!periodicWorkspaceScan) {
|
|
2297
|
+
const emptyFingerprint = (0, import_node_crypto.createHash)("sha256").update("").digest("hex");
|
|
2298
|
+
periodicWorkspaceScan = setInterval(() => {
|
|
2299
|
+
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2300
|
+
periodicWorkspaceScanInFlight = true;
|
|
2301
|
+
void (async () => {
|
|
2302
|
+
const input = workspaceSyncInput({ type: "periodic" });
|
|
2303
|
+
const fingerprint = await workspaceSyncSingleFlight.fingerprint(input);
|
|
2304
|
+
if (fingerprint === emptyFingerprint) {
|
|
2305
|
+
previousPeriodicFingerprint = null;
|
|
2306
|
+
return;
|
|
2637
2307
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2308
|
+
if (previousPeriodicFingerprint !== fingerprint) {
|
|
2309
|
+
previousPeriodicFingerprint = fingerprint;
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
previousPeriodicFingerprint = null;
|
|
2313
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "periodic" });
|
|
2314
|
+
})().catch((error) => {
|
|
2315
|
+
process.stderr.write(
|
|
2316
|
+
`[r5d-worker] periodic workspace synchronization failed: ${error instanceof Error ? error.message : String(error)}
|
|
2317
|
+
`
|
|
2318
|
+
);
|
|
2319
|
+
}).finally(() => {
|
|
2320
|
+
periodicWorkspaceScanInFlight = false;
|
|
2321
|
+
});
|
|
2322
|
+
}, import_workspace_sync.WORKSPACE_PERIODIC_SCAN_INTERVAL_MS);
|
|
2323
|
+
periodicWorkspaceScan.unref();
|
|
2641
2324
|
}
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
if (message.type === "sync_workspace") {
|
|
2328
|
+
await runRequestedWorkspaceSync(message.requestId, message.trigger, {
|
|
2329
|
+
attemptId: message.attemptId,
|
|
2330
|
+
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
2331
|
+
confirmationReason: message.confirmationReason,
|
|
2332
|
+
resetToCanonical: message.resetToCanonical,
|
|
2333
|
+
skipVisibleMirror: message.skipVisibleMirror
|
|
2646
2334
|
});
|
|
2647
2335
|
return;
|
|
2648
2336
|
}
|
|
2337
|
+
if (message.type === "workspace_head_updated") {
|
|
2338
|
+
if (!activeWorkspaceIncidentId) {
|
|
2339
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "inbound_head", detail: message.commitHash });
|
|
2340
|
+
}
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
if (message.type === "workspace_incident_updated") {
|
|
2344
|
+
activeWorkspaceIncidentId = message.status === "remediating" || message.status === "waiting_for_worker" ? message.incidentId : null;
|
|
2345
|
+
previousPeriodicFingerprint = null;
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
if (message.type === "exec_terminal_ack") {
|
|
2349
|
+
pendingProcessTerminals.delete(message.runId);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2649
2352
|
if (message.type === "update_clis") {
|
|
2650
2353
|
if (cliUpdateInProgress) {
|
|
2651
2354
|
sendWorkerMessage(ws, {
|
|
@@ -2655,7 +2358,7 @@ async function startWorker(options) {
|
|
|
2655
2358
|
});
|
|
2656
2359
|
return;
|
|
2657
2360
|
}
|
|
2658
|
-
if (activeProcesses.size > 0 || activePtys.size > 0 ||
|
|
2361
|
+
if (activeProcesses.size > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0) {
|
|
2659
2362
|
sendWorkerMessage(ws, {
|
|
2660
2363
|
type: "update_clis_result",
|
|
2661
2364
|
requestId: message.requestId,
|
|
@@ -2725,7 +2428,6 @@ async function startWorker(options) {
|
|
|
2725
2428
|
});
|
|
2726
2429
|
return;
|
|
2727
2430
|
}
|
|
2728
|
-
await repositorySyncQueue;
|
|
2729
2431
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2730
2432
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2731
2433
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.projectId}/${message.branchName}
|
|
@@ -2756,7 +2458,6 @@ async function startWorker(options) {
|
|
|
2756
2458
|
});
|
|
2757
2459
|
return;
|
|
2758
2460
|
}
|
|
2759
|
-
await repositorySyncQueue;
|
|
2760
2461
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2761
2462
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2762
2463
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
@@ -2770,6 +2471,7 @@ async function startWorker(options) {
|
|
|
2770
2471
|
syncRoot,
|
|
2771
2472
|
artifactRoot,
|
|
2772
2473
|
planRoot,
|
|
2474
|
+
workspaceShadowRoot,
|
|
2773
2475
|
manifest
|
|
2774
2476
|
});
|
|
2775
2477
|
ws.send(JSON.stringify(result));
|
|
@@ -2785,7 +2487,6 @@ async function startWorker(options) {
|
|
|
2785
2487
|
});
|
|
2786
2488
|
return;
|
|
2787
2489
|
}
|
|
2788
|
-
await repositorySyncQueue;
|
|
2789
2490
|
sendWorkerMessage(ws, {
|
|
2790
2491
|
type: "exec_accepted",
|
|
2791
2492
|
requestId: message.requestId,
|
|
@@ -2806,6 +2507,7 @@ async function startWorker(options) {
|
|
|
2806
2507
|
syncRoot,
|
|
2807
2508
|
artifactRoot,
|
|
2808
2509
|
planRoot,
|
|
2510
|
+
workspaceShadowRoot,
|
|
2809
2511
|
manifest
|
|
2810
2512
|
}).catch((error) => {
|
|
2811
2513
|
sendWorkerMessage(ws, {
|
|
@@ -2825,9 +2527,8 @@ async function startWorker(options) {
|
|
|
2825
2527
|
}
|
|
2826
2528
|
return;
|
|
2827
2529
|
}
|
|
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"
|
|
2530
|
+
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
2531
|
try {
|
|
2830
|
-
await repositorySyncQueue;
|
|
2831
2532
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2832
2533
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2833
2534
|
const result = await executeOperation({
|
|
@@ -2875,6 +2576,14 @@ async function startWorker(options) {
|
|
|
2875
2576
|
});
|
|
2876
2577
|
ws.addEventListener("close", (event) => {
|
|
2877
2578
|
stopHeartbeatWatchdog();
|
|
2579
|
+
if (periodicWorkspaceScan) {
|
|
2580
|
+
clearInterval(periodicWorkspaceScan);
|
|
2581
|
+
periodicWorkspaceScan = void 0;
|
|
2582
|
+
}
|
|
2583
|
+
if (terminalReplayTimer) {
|
|
2584
|
+
clearInterval(terminalReplayTimer);
|
|
2585
|
+
terminalReplayTimer = void 0;
|
|
2586
|
+
}
|
|
2878
2587
|
if (currentWorkerSocket === ws) {
|
|
2879
2588
|
currentWorkerSocket = null;
|
|
2880
2589
|
}
|
|
@@ -2885,15 +2594,17 @@ async function startWorker(options) {
|
|
|
2885
2594
|
if (reloadAfterClose) {
|
|
2886
2595
|
process.exit(import_supervisor.WORKER_RELOAD_EXIT_CODE);
|
|
2887
2596
|
}
|
|
2888
|
-
if (activeProcesses.size > 0) {
|
|
2597
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
2889
2598
|
const delayMs = 2e3;
|
|
2890
|
-
process.stderr.write(
|
|
2891
|
-
`);
|
|
2599
|
+
process.stderr.write(
|
|
2600
|
+
`[r5d-worker] ${activeProcesses.size} process(es) active and ${pendingProcessTerminals.size} terminal report(s) pending; reconnecting in ${delayMs}ms
|
|
2601
|
+
`
|
|
2602
|
+
);
|
|
2892
2603
|
const reconnect = () => {
|
|
2893
2604
|
void startWorker(options).catch((error) => {
|
|
2894
2605
|
process.stderr.write(`[r5d-worker] reconnect failed: ${error instanceof Error ? error.message : String(error)}
|
|
2895
2606
|
`);
|
|
2896
|
-
if (activeProcesses.size > 0) {
|
|
2607
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
2897
2608
|
setTimeout(reconnect, delayMs);
|
|
2898
2609
|
return;
|
|
2899
2610
|
}
|
|
@@ -2949,7 +2660,6 @@ if (isCliEntrypoint()) {
|
|
|
2949
2660
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2950
2661
|
0 && (module.exports = {
|
|
2951
2662
|
ensureVisibleGitCheckout,
|
|
2952
|
-
findRepositorySyncBlockers,
|
|
2953
2663
|
findWorkerFiles,
|
|
2954
2664
|
githubCliEnv,
|
|
2955
2665
|
grepWorkerFiles,
|
|
@@ -2962,7 +2672,6 @@ if (isCliEntrypoint()) {
|
|
|
2962
2672
|
resolveHostShell,
|
|
2963
2673
|
resolveProjectFilePath,
|
|
2964
2674
|
resolveWorkerFilePath,
|
|
2965
|
-
syncManifestProjectsFromInternal,
|
|
2966
2675
|
syncProjectPlans,
|
|
2967
2676
|
syncSessionArtifacts
|
|
2968
2677
|
});
|