@ricsam/r5d-worker 0.0.35 → 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 +233 -693
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync.cjs +652 -0
- package/dist/mjs/main.mjs +238 -688
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-sync.mjs +607 -0
- package/dist/types/main.d.ts +0 -92
- package/dist/types/workspace-sync.d.ts +73 -0
- package/package.json +1 -1
package/dist/mjs/main.mjs
CHANGED
|
@@ -9,6 +9,12 @@ import { configureVisibleGitIdentity } from "./git-identity.mjs";
|
|
|
9
9
|
import { hasWorkerHeartbeatTimedOut, WORKER_HEARTBEAT_INTERVAL_MS } from "./heartbeat.mjs";
|
|
10
10
|
import { terminateProcessTree } from "./process-tree.mjs";
|
|
11
11
|
import { superviseWorkerRuntime, WORKER_RELOAD_EXIT_CODE, WORKER_RUNTIME_ENV } from "./supervisor.mjs";
|
|
12
|
+
import {
|
|
13
|
+
WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
|
|
14
|
+
WorkspaceSyncSingleFlight,
|
|
15
|
+
calculateWorkspaceDiffFingerprint,
|
|
16
|
+
synchronizeWorkspace
|
|
17
|
+
} from "./workspace-sync.mjs";
|
|
12
18
|
const DEFAULT_BASE_URL = "https://r5d.dev";
|
|
13
19
|
const WORKER_PACKAGE_NAME = "@ricsam/r5d-worker";
|
|
14
20
|
const LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
|
|
@@ -16,10 +22,8 @@ const BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]$|^[A-Za-z0-9]$/;
|
|
|
16
22
|
const DEFAULT_READ_LIMIT = 2e3;
|
|
17
23
|
const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
18
24
|
const MAX_LINE_LENGTH = 2e3;
|
|
19
|
-
const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
|
|
20
|
-
const R5D_REMOTE_NAME = "r5d";
|
|
21
|
-
const LEGACY_BUILD_IT_NOW_REMOTE_NAME = "build-it-now";
|
|
22
25
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
26
|
+
const pendingProcessTerminals = /* @__PURE__ */ new Map();
|
|
23
27
|
const cancelledProcessRuns = /* @__PURE__ */ new Set();
|
|
24
28
|
const activePtys = /* @__PURE__ */ new Map();
|
|
25
29
|
let currentWorkerSocket = null;
|
|
@@ -478,8 +482,10 @@ async function prepareArtifactEnvForShell(input) {
|
|
|
478
482
|
artifactRoot: input.artifactRoot
|
|
479
483
|
});
|
|
480
484
|
} catch (error) {
|
|
481
|
-
process.stderr.write(
|
|
482
|
-
`)
|
|
485
|
+
process.stderr.write(
|
|
486
|
+
`[r5d-worker] artifact sync skipped for ${input.sessionId}: ${error instanceof Error ? error.message : String(error)}
|
|
487
|
+
`
|
|
488
|
+
);
|
|
483
489
|
}
|
|
484
490
|
const artifactsDir = sessionArtifactDir(input.artifactRoot, input.sessionId);
|
|
485
491
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
@@ -535,7 +541,9 @@ function resolveCredentials(options, config) {
|
|
|
535
541
|
throw new Error("Authentication required. Run `r5dctl auth login` or set R5D_WORKER_TOKEN/R5D_API_KEY.");
|
|
536
542
|
}
|
|
537
543
|
return {
|
|
538
|
-
baseUrl: normalizeBaseUrl(
|
|
544
|
+
baseUrl: normalizeBaseUrl(
|
|
545
|
+
options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL
|
|
546
|
+
),
|
|
539
547
|
token
|
|
540
548
|
};
|
|
541
549
|
}
|
|
@@ -589,27 +597,6 @@ function runGit(args, options = {}) {
|
|
|
589
597
|
}
|
|
590
598
|
return result.stdout.toString().trim();
|
|
591
599
|
}
|
|
592
|
-
async function runGitAsync(args, options = {}) {
|
|
593
|
-
const command = ["git", ...gitAuthArgs(options.auth), ...args];
|
|
594
|
-
const subprocess = Bun.spawn(command, {
|
|
595
|
-
cwd: options.cwd,
|
|
596
|
-
stdout: "pipe",
|
|
597
|
-
stderr: "pipe",
|
|
598
|
-
env: {
|
|
599
|
-
...process.env,
|
|
600
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
604
|
-
collectStream(subprocess.stdout),
|
|
605
|
-
collectStream(subprocess.stderr),
|
|
606
|
-
subprocess.exited
|
|
607
|
-
]);
|
|
608
|
-
if (exitCode !== 0) {
|
|
609
|
-
throw new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim() || `exit ${exitCode}`}`);
|
|
610
|
-
}
|
|
611
|
-
return stdout.trim();
|
|
612
|
-
}
|
|
613
600
|
function tryGit(args, options = {}) {
|
|
614
601
|
try {
|
|
615
602
|
runGit(args, options);
|
|
@@ -618,34 +605,6 @@ function tryGit(args, options = {}) {
|
|
|
618
605
|
return false;
|
|
619
606
|
}
|
|
620
607
|
}
|
|
621
|
-
async function tryGitAsync(args, options = {}) {
|
|
622
|
-
try {
|
|
623
|
-
await runGitAsync(args, options);
|
|
624
|
-
return true;
|
|
625
|
-
} catch {
|
|
626
|
-
return false;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
function runInternalGit(context, args) {
|
|
630
|
-
return runGit(["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], { auth: context.auth });
|
|
631
|
-
}
|
|
632
|
-
function runInternalGitAsync(context, args) {
|
|
633
|
-
return runGitAsync(["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], { auth: context.auth });
|
|
634
|
-
}
|
|
635
|
-
function runInternalGitDir(context, args) {
|
|
636
|
-
return runGit(["--git-dir", context.gitDir, ...args], { auth: context.auth });
|
|
637
|
-
}
|
|
638
|
-
function tryInternalGit(context, args) {
|
|
639
|
-
try {
|
|
640
|
-
runInternalGit(context, args);
|
|
641
|
-
return true;
|
|
642
|
-
} catch {
|
|
643
|
-
return false;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
function getInternalCommitHash(context) {
|
|
647
|
-
return runInternalGit(context, ["rev-parse", "HEAD"]);
|
|
648
|
-
}
|
|
649
608
|
function getGitBlobHashForContent(content) {
|
|
650
609
|
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
651
610
|
return createHash("sha1").update(`blob ${buffer.length}\0`).update(buffer).digest("hex");
|
|
@@ -653,12 +612,6 @@ function getGitBlobHashForContent(content) {
|
|
|
653
612
|
function hasWorktreeChanges(cwd) {
|
|
654
613
|
return runGit(["status", "--porcelain"], { cwd }).trim().length > 0;
|
|
655
614
|
}
|
|
656
|
-
function getInternalStatus(context) {
|
|
657
|
-
return runInternalGit(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
|
|
658
|
-
}
|
|
659
|
-
function hasInternalWorktreeChanges(context) {
|
|
660
|
-
return getInternalStatus(context).trim().length > 0;
|
|
661
|
-
}
|
|
662
615
|
function isInsideBranchPath(branchPath, filePath) {
|
|
663
616
|
const relative = path.relative(path.resolve(branchPath), path.resolve(filePath));
|
|
664
617
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
@@ -693,11 +646,6 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
693
646
|
const displayPath = toProjectDisplayPath(resolvedBranchPath, absolutePath2);
|
|
694
647
|
const repoRelativePath2 = displayPath === "." ? "" : displayPath;
|
|
695
648
|
assertAllowedProjectPath(repoRelativePath2, inputPath);
|
|
696
|
-
const realBranchPath2 = fs.existsSync(resolvedBranchPath) ? fs.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
697
|
-
const realTargetPath2 = resolveThroughExistingAncestor(absolutePath2);
|
|
698
|
-
if (!isInsideBranchPath(realBranchPath2, realTargetPath2)) {
|
|
699
|
-
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
700
|
-
}
|
|
701
649
|
return {
|
|
702
650
|
absolutePath: absolutePath2,
|
|
703
651
|
displayPath,
|
|
@@ -716,11 +664,6 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
716
664
|
if (!isInsideBranchPath(resolvedBranchPath, absolutePath)) {
|
|
717
665
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
718
666
|
}
|
|
719
|
-
const realBranchPath = fs.existsSync(resolvedBranchPath) ? fs.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
720
|
-
const realTargetPath = resolveThroughExistingAncestor(absolutePath);
|
|
721
|
-
if (!isInsideBranchPath(realBranchPath, realTargetPath)) {
|
|
722
|
-
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
723
|
-
}
|
|
724
667
|
return {
|
|
725
668
|
absolutePath,
|
|
726
669
|
displayPath: repoRelativePath || ".",
|
|
@@ -731,81 +674,15 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
731
674
|
function resolveProjectFilePath(branchPath, inputPath) {
|
|
732
675
|
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
733
676
|
if (resolved.scope === "host") {
|
|
734
|
-
throw new Error(
|
|
735
|
-
`Project file writes must resolve inside the selected managed checkout: ${inputPath}`
|
|
736
|
-
);
|
|
677
|
+
throw new Error(`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`);
|
|
737
678
|
}
|
|
738
679
|
return resolved;
|
|
739
680
|
}
|
|
740
|
-
function resolveThroughExistingAncestor(inputPath) {
|
|
741
|
-
const suffix = [];
|
|
742
|
-
let cursor = path.resolve(inputPath);
|
|
743
|
-
while (!fs.existsSync(cursor)) {
|
|
744
|
-
const parent = path.dirname(cursor);
|
|
745
|
-
if (parent === cursor) return path.resolve(inputPath);
|
|
746
|
-
suffix.unshift(path.basename(cursor));
|
|
747
|
-
cursor = parent;
|
|
748
|
-
}
|
|
749
|
-
return path.resolve(fs.realpathSync(cursor), ...suffix);
|
|
750
|
-
}
|
|
751
|
-
function resolveManagedCheckoutPath(input) {
|
|
752
|
-
const originManifest = input.manifests.find((manifest) => manifest.projectId === input.originProjectId);
|
|
753
|
-
const candidate = path.isAbsolute(input.inputPath) ? path.resolve(input.inputPath) : path.resolve(
|
|
754
|
-
input.projectsRoot,
|
|
755
|
-
originManifest?.repoSlug ?? input.originProjectId,
|
|
756
|
-
input.originBranchName,
|
|
757
|
-
input.inputPath
|
|
758
|
-
);
|
|
759
|
-
const resolvedCandidate = resolveThroughExistingAncestor(candidate);
|
|
760
|
-
for (const manifest of input.manifests) {
|
|
761
|
-
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
762
|
-
for (const branchName of branches) {
|
|
763
|
-
const checkoutPath = path.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName);
|
|
764
|
-
if (!hasNormalVisibleGitDir(checkoutPath)) continue;
|
|
765
|
-
const resolvedCheckoutPath = fs.existsSync(checkoutPath) ? fs.realpathSync(checkoutPath) : checkoutPath;
|
|
766
|
-
const matchesLexically = isInsideBranchPath(checkoutPath, candidate);
|
|
767
|
-
if (!matchesLexically) continue;
|
|
768
|
-
if (!isInsideBranchPath(resolvedCheckoutPath, resolvedCandidate)) {
|
|
769
|
-
throw new Error(`Managed path escapes its checkout through a symbolic link: ${input.inputPath}`);
|
|
770
|
-
}
|
|
771
|
-
const repoRelativePath = toProjectDisplayPath(resolvedCheckoutPath, resolvedCandidate);
|
|
772
|
-
assertAllowedProjectPath(repoRelativePath === "." ? "" : repoRelativePath, input.inputPath);
|
|
773
|
-
return {
|
|
774
|
-
type: "resolve_managed_path",
|
|
775
|
-
resolved: {
|
|
776
|
-
projectId: manifest.projectId,
|
|
777
|
-
projectPath: manifest.projectPath,
|
|
778
|
-
branchName,
|
|
779
|
-
checkoutPath,
|
|
780
|
-
inputPath: input.inputPath,
|
|
781
|
-
absolutePath: candidate,
|
|
782
|
-
repoRelativePath: repoRelativePath === "." ? "" : repoRelativePath
|
|
783
|
-
}
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
return { type: "resolve_managed_path", resolved: null };
|
|
788
|
-
}
|
|
789
|
-
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
790
|
-
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
791
|
-
return remoteUrl;
|
|
792
|
-
}
|
|
793
|
-
return new URL(remoteUrl, baseUrl).toString();
|
|
794
|
-
}
|
|
795
681
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
796
682
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
797
683
|
}
|
|
798
|
-
function internalGitAuth(baseUrl, token) {
|
|
799
|
-
return {
|
|
800
|
-
extraHeaderUrl: baseUrl,
|
|
801
|
-
header: `Authorization: Bearer ${token}`
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
function internalRemoteUrlFor(baseUrl, projectId, manifest) {
|
|
805
|
-
return resolveRemoteUrl(baseUrl, manifest?.internalRemoteUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId));
|
|
806
|
-
}
|
|
807
684
|
function githubRemoteUrlFor(baseUrl, projectId, manifest) {
|
|
808
|
-
return manifest?.repoHttpUrl ??
|
|
685
|
+
return manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId);
|
|
809
686
|
}
|
|
810
687
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
811
688
|
try {
|
|
@@ -823,12 +700,6 @@ function gitAuthForRemote(remoteUrl, authHeader) {
|
|
|
823
700
|
header: authHeader
|
|
824
701
|
};
|
|
825
702
|
}
|
|
826
|
-
function branchGitDirName(branchName) {
|
|
827
|
-
return `${encodeURIComponent(branchName)}.git`;
|
|
828
|
-
}
|
|
829
|
-
function internalGitDirFor(syncRoot, projectId, branchName) {
|
|
830
|
-
return path.join(syncRoot, projectId, branchGitDirName(branchName));
|
|
831
|
-
}
|
|
832
703
|
function hasNormalVisibleGitDir(branchPath) {
|
|
833
704
|
const gitPath = path.join(branchPath, ".git");
|
|
834
705
|
return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
|
|
@@ -840,13 +711,6 @@ function ensureOriginRemote(branchPath, remoteUrl) {
|
|
|
840
711
|
runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
841
712
|
}
|
|
842
713
|
}
|
|
843
|
-
async function ensureOriginRemoteAsync(branchPath, remoteUrl) {
|
|
844
|
-
if (await tryGitAsync(["remote", "get-url", "origin"], { cwd: branchPath })) {
|
|
845
|
-
await runGitAsync(["remote", "set-url", "origin", remoteUrl], { cwd: branchPath });
|
|
846
|
-
} else {
|
|
847
|
-
await runGitAsync(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
714
|
function shellQuote(value) {
|
|
851
715
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
852
716
|
}
|
|
@@ -1042,217 +906,20 @@ function ensureVisibleGitCheckout(input) {
|
|
|
1042
906
|
configureVisibleGitIdentity(branchPath, visibleGitIdentity, runGit);
|
|
1043
907
|
return branchPath;
|
|
1044
908
|
}
|
|
1045
|
-
function configureInternalRepo(context, remoteUrl) {
|
|
1046
|
-
if (tryInternalGit(context, ["remote", "get-url", R5D_REMOTE_NAME])) {
|
|
1047
|
-
runInternalGit(context, ["remote", "set-url", R5D_REMOTE_NAME, remoteUrl]);
|
|
1048
|
-
} else {
|
|
1049
|
-
runInternalGit(context, ["remote", "add", R5D_REMOTE_NAME, remoteUrl]);
|
|
1050
|
-
}
|
|
1051
|
-
if (tryInternalGit(context, ["remote", "get-url", LEGACY_BUILD_IT_NOW_REMOTE_NAME])) {
|
|
1052
|
-
runInternalGit(context, ["remote", "remove", LEGACY_BUILD_IT_NOW_REMOTE_NAME]);
|
|
1053
|
-
}
|
|
1054
|
-
runInternalGitDir(context, ["config", "user.name", "r5d.dev Worker"]);
|
|
1055
|
-
runInternalGitDir(context, ["config", "user.email", "worker@r5d.dev"]);
|
|
1056
|
-
}
|
|
1057
|
-
function ensureInternalSyncGit(input) {
|
|
1058
|
-
validateBranchName(input.branchName);
|
|
1059
|
-
const gitDir = internalGitDirFor(input.syncRoot, input.projectId, input.branchName);
|
|
1060
|
-
fs.mkdirSync(path.dirname(gitDir), { recursive: true });
|
|
1061
|
-
if (!fs.existsSync(gitDir)) {
|
|
1062
|
-
runGit(["init", "--bare", gitDir]);
|
|
1063
|
-
}
|
|
1064
|
-
const auth = internalGitAuth(input.baseUrl, input.token);
|
|
1065
|
-
const context = { gitDir, workTree: input.branchPath, auth };
|
|
1066
|
-
const remoteUrl = internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1067
|
-
configureInternalRepo(context, remoteUrl);
|
|
1068
|
-
runInternalGit(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1069
|
-
const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`]);
|
|
1070
|
-
const target = hasRemoteBranch ? `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}` : `refs/remotes/${R5D_REMOTE_NAME}/main`;
|
|
1071
|
-
const hasHead = tryInternalGit(context, ["rev-parse", "--verify", "HEAD"]);
|
|
1072
|
-
if (!hasHead || !hasInternalWorktreeChanges(context)) {
|
|
1073
|
-
runInternalGit(context, ["checkout", "-f", "-B", input.branchName, target]);
|
|
1074
|
-
}
|
|
1075
|
-
return context;
|
|
1076
|
-
}
|
|
1077
909
|
function ensureBranchWorkspace(input) {
|
|
1078
910
|
const defaultBranch = input.manifest?.defaultBranch || "main";
|
|
1079
911
|
const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1080
912
|
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
|
|
1081
|
-
const branchPath = ensureVisibleGitCheckout({
|
|
1082
|
-
projectRoot: input.projectRoot,
|
|
1083
|
-
branchName: input.branchName,
|
|
1084
|
-
githubRemoteUrl,
|
|
1085
|
-
githubAuth,
|
|
1086
|
-
githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
|
|
1087
|
-
defaultBranch,
|
|
1088
|
-
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
|
|
1089
|
-
});
|
|
1090
|
-
const internalGit = ensureInternalSyncGit({
|
|
1091
|
-
projectId: input.projectId,
|
|
1092
|
-
baseUrl: input.baseUrl,
|
|
1093
|
-
token: input.token,
|
|
1094
|
-
syncRoot: input.syncRoot,
|
|
1095
|
-
branchPath,
|
|
1096
|
-
branchName: input.branchName,
|
|
1097
|
-
manifest: input.manifest
|
|
1098
|
-
});
|
|
1099
|
-
return { branchPath, internalGit };
|
|
1100
|
-
}
|
|
1101
|
-
async function ensureVisibleGitCheckoutForRepositorySync(input) {
|
|
1102
|
-
validateBranchName(input.branchName);
|
|
1103
|
-
fs.mkdirSync(input.projectRoot, { recursive: true });
|
|
1104
|
-
const branchPath = path.join(input.projectRoot, input.branchName);
|
|
1105
|
-
const createdCheckout = !hasNormalVisibleGitDir(branchPath);
|
|
1106
|
-
if (createdCheckout) {
|
|
1107
|
-
fs.rmSync(branchPath, { recursive: true, force: true });
|
|
1108
|
-
fs.mkdirSync(path.dirname(branchPath), { recursive: true });
|
|
1109
|
-
if (!await tryGitAsync(["clone", "--origin", "origin", input.githubRemoteUrl, branchPath], { auth: input.githubAuth })) {
|
|
1110
|
-
fs.mkdirSync(branchPath, { recursive: true });
|
|
1111
|
-
await runGitAsync(["init"], { cwd: branchPath });
|
|
1112
|
-
}
|
|
1113
|
-
await ensureOriginRemoteAsync(branchPath, input.githubRemoteUrl);
|
|
1114
|
-
configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
|
|
1115
|
-
configureVisibleGitIdentity(branchPath, visibleGitIdentity, runGit);
|
|
1116
|
-
await tryGitAsync(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
|
|
1117
|
-
checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
|
|
1118
|
-
return branchPath;
|
|
1119
|
-
}
|
|
1120
|
-
if (input.reconcileExistingOrigin) {
|
|
1121
|
-
await ensureOriginRemoteAsync(branchPath, input.githubRemoteUrl);
|
|
1122
|
-
}
|
|
1123
|
-
configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
|
|
1124
|
-
configureVisibleGitIdentity(branchPath, visibleGitIdentity, runGit);
|
|
1125
|
-
return branchPath;
|
|
1126
|
-
}
|
|
1127
|
-
async function forceSyncManifestBranchFromInternal(input) {
|
|
1128
|
-
const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1129
|
-
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest.repoAuthHeader);
|
|
1130
|
-
const branchPath = await ensureVisibleGitCheckoutForRepositorySync({
|
|
1131
|
-
projectRoot: input.projectRoot,
|
|
1132
|
-
branchName: input.branchName,
|
|
1133
|
-
githubRemoteUrl,
|
|
1134
|
-
githubAuth,
|
|
1135
|
-
githubAuthHeader: input.manifest.repoAuthHeader,
|
|
1136
|
-
defaultBranch: input.manifest.defaultBranch || "main",
|
|
1137
|
-
reconcileExistingOrigin: Boolean(input.manifest.repoHttpUrl)
|
|
1138
|
-
});
|
|
1139
|
-
const gitDir = internalGitDirFor(input.syncRoot, input.projectId, input.branchName);
|
|
1140
|
-
fs.mkdirSync(path.dirname(gitDir), { recursive: true });
|
|
1141
|
-
if (!fs.existsSync(gitDir)) {
|
|
1142
|
-
await runGitAsync(["init", "--bare", gitDir]);
|
|
1143
|
-
}
|
|
1144
|
-
const context = {
|
|
1145
|
-
gitDir,
|
|
1146
|
-
workTree: branchPath,
|
|
1147
|
-
auth: internalGitAuth(input.baseUrl, input.token)
|
|
1148
|
-
};
|
|
1149
|
-
configureInternalRepo(context, internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest));
|
|
1150
|
-
await runInternalGitAsync(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1151
|
-
const branchTarget = `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`;
|
|
1152
|
-
const mainTarget = `refs/remotes/${R5D_REMOTE_NAME}/main`;
|
|
1153
|
-
const target = tryInternalGit(context, ["show-ref", "--verify", "--quiet", branchTarget]) ? branchTarget : tryInternalGit(context, ["show-ref", "--verify", "--quiet", mainTarget]) ? mainTarget : null;
|
|
1154
|
-
if (!target) {
|
|
1155
|
-
throw new Error(`Internal remote has neither ${input.branchName} nor main`);
|
|
1156
|
-
}
|
|
1157
|
-
if (hasInternalWorktreeChanges(context)) {
|
|
1158
|
-
return null;
|
|
1159
|
-
}
|
|
1160
|
-
await runInternalGitAsync(context, ["reset", "--hard", target]);
|
|
1161
|
-
await runInternalGitAsync(context, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1162
|
-
return getInternalCommitHash(context);
|
|
1163
|
-
}
|
|
1164
|
-
function findRepositorySyncBlockers(input) {
|
|
1165
|
-
const targetByKey = new Map(input.targets.map((target) => [`${target.projectId}:${target.branchName}`, target]));
|
|
1166
|
-
const blockedBy = [];
|
|
1167
|
-
for (const shell of input.shells) {
|
|
1168
|
-
const target = targetByKey.get(`${shell.projectId}:${shell.branchName}`);
|
|
1169
|
-
if (target) blockedBy.push({ ...target, kind: "shell", id: shell.id });
|
|
1170
|
-
}
|
|
1171
|
-
return blockedBy;
|
|
1172
|
-
}
|
|
1173
|
-
function selectManifestSyncTargets(input) {
|
|
1174
|
-
if (!input.requestedTargets) {
|
|
1175
|
-
return input.manifests.flatMap((manifest) => {
|
|
1176
|
-
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
1177
|
-
return [...new Set(branches)].map((branchName) => ({ manifest, branchName }));
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
const manifestsByProjectId = new Map(input.manifests.map((manifest) => [manifest.projectId, manifest]));
|
|
1181
|
-
const selected = /* @__PURE__ */ new Map();
|
|
1182
|
-
for (const target of input.requestedTargets) {
|
|
1183
|
-
const manifest = manifestsByProjectId.get(target.projectId);
|
|
1184
|
-
if (!manifest) {
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
selected.set(`${target.projectId}\0${target.branchName}`, { manifest, branchName: target.branchName });
|
|
1188
|
-
}
|
|
1189
|
-
return [...selected.values()];
|
|
1190
|
-
}
|
|
1191
|
-
async function syncManifestProjectsFromInternal(input) {
|
|
1192
|
-
const targets = selectManifestSyncTargets({
|
|
1193
|
-
manifests: input.manifests,
|
|
1194
|
-
requestedTargets: input.requestedTargets
|
|
1195
|
-
});
|
|
1196
|
-
const blockedBy = findRepositorySyncBlockers({
|
|
1197
|
-
targets: targets.map(({ manifest, branchName }) => ({
|
|
1198
|
-
projectId: manifest.projectId,
|
|
1199
|
-
projectPath: manifest.projectPath,
|
|
1200
|
-
branchName
|
|
1201
|
-
})),
|
|
1202
|
-
shells: [...activePtys].map(([id, active]) => ({
|
|
1203
|
-
id,
|
|
1204
|
-
projectId: active.projectId,
|
|
1205
|
-
branchName: active.branchName
|
|
1206
|
-
}))
|
|
1207
|
-
});
|
|
1208
|
-
const blockedTargetKeys = new Set(blockedBy.map((blocker) => `${blocker.projectId}\0${blocker.branchName}`));
|
|
1209
|
-
const results = [];
|
|
1210
|
-
for (const { manifest, branchName } of targets) {
|
|
1211
|
-
if (blockedTargetKeys.has(`${manifest.projectId}\0${branchName}`)) {
|
|
1212
|
-
continue;
|
|
1213
|
-
}
|
|
1214
|
-
try {
|
|
1215
|
-
const commitHash = await forceSyncManifestBranchFromInternal({
|
|
1216
|
-
projectId: manifest.projectId,
|
|
1217
|
-
baseUrl: input.baseUrl,
|
|
1218
|
-
token: input.token,
|
|
1219
|
-
projectRoot: path.join(input.projectsRoot, manifest.repoSlug || manifest.projectId),
|
|
1220
|
-
syncRoot: input.syncRoot,
|
|
1221
|
-
branchName,
|
|
1222
|
-
manifest
|
|
1223
|
-
});
|
|
1224
|
-
if (commitHash === null) {
|
|
1225
|
-
blockedBy.push({
|
|
1226
|
-
projectId: manifest.projectId,
|
|
1227
|
-
projectPath: manifest.projectPath,
|
|
1228
|
-
branchName,
|
|
1229
|
-
kind: "dirty_checkout",
|
|
1230
|
-
id: `${manifest.projectId}:${branchName}`
|
|
1231
|
-
});
|
|
1232
|
-
continue;
|
|
1233
|
-
}
|
|
1234
|
-
results.push({
|
|
1235
|
-
projectId: manifest.projectId,
|
|
1236
|
-
projectPath: manifest.projectPath,
|
|
1237
|
-
branchName,
|
|
1238
|
-
status: "synced",
|
|
1239
|
-
commitHash
|
|
1240
|
-
});
|
|
1241
|
-
} catch (error) {
|
|
1242
|
-
results.push({
|
|
1243
|
-
projectId: manifest.projectId,
|
|
1244
|
-
projectPath: manifest.projectPath,
|
|
1245
|
-
branchName,
|
|
1246
|
-
status: "failed",
|
|
1247
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1248
|
-
});
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
const hasFailures = results.some((result) => result.status === "failed");
|
|
1252
913
|
return {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
914
|
+
branchPath: ensureVisibleGitCheckout({
|
|
915
|
+
projectRoot: input.projectRoot,
|
|
916
|
+
branchName: input.branchName,
|
|
917
|
+
githubRemoteUrl,
|
|
918
|
+
githubAuth,
|
|
919
|
+
githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
|
|
920
|
+
defaultBranch,
|
|
921
|
+
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
|
|
922
|
+
})
|
|
1256
923
|
};
|
|
1257
924
|
}
|
|
1258
925
|
function resolveCommandCwd(branchPath, cwd) {
|
|
@@ -1303,69 +970,6 @@ async function streamCommandOutput(stream, onData) {
|
|
|
1303
970
|
function ensureOperationBranch(input) {
|
|
1304
971
|
return ensureBranchWorkspace(input);
|
|
1305
972
|
}
|
|
1306
|
-
async function inspectManagedWorkspace(input) {
|
|
1307
|
-
const candidates = input.manifests.flatMap((manifest) => {
|
|
1308
|
-
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
1309
|
-
return branches.map((branchName) => ({
|
|
1310
|
-
manifest,
|
|
1311
|
-
branchName,
|
|
1312
|
-
checkoutPath: path.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName)
|
|
1313
|
-
}));
|
|
1314
|
-
});
|
|
1315
|
-
const inspected = await Promise.all(
|
|
1316
|
-
candidates.map(async ({ manifest, branchName, checkoutPath }) => {
|
|
1317
|
-
const gitDir = internalGitDirFor(input.syncRoot, manifest.projectId, branchName);
|
|
1318
|
-
if (!hasNormalVisibleGitDir(checkoutPath) || !fs.existsSync(gitDir)) return null;
|
|
1319
|
-
const context = {
|
|
1320
|
-
gitDir,
|
|
1321
|
-
workTree: checkoutPath,
|
|
1322
|
-
auth: internalGitAuth(input.baseUrl, input.token)
|
|
1323
|
-
};
|
|
1324
|
-
try {
|
|
1325
|
-
const status = await runInternalGitAsync(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
|
|
1326
|
-
let commitHash = null;
|
|
1327
|
-
for (const revision of [
|
|
1328
|
-
"HEAD",
|
|
1329
|
-
`refs/remotes/${R5D_REMOTE_NAME}/${branchName}`,
|
|
1330
|
-
`refs/remotes/${R5D_REMOTE_NAME}/main`
|
|
1331
|
-
]) {
|
|
1332
|
-
try {
|
|
1333
|
-
commitHash = await runInternalGitAsync(context, ["rev-parse", "--verify", revision]);
|
|
1334
|
-
break;
|
|
1335
|
-
} catch {
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
if (!commitHash) return null;
|
|
1339
|
-
return {
|
|
1340
|
-
projectId: manifest.projectId,
|
|
1341
|
-
projectPath: manifest.projectPath,
|
|
1342
|
-
branchName,
|
|
1343
|
-
checkoutPath,
|
|
1344
|
-
commitHash,
|
|
1345
|
-
status,
|
|
1346
|
-
dirty: status.trim().length > 0
|
|
1347
|
-
};
|
|
1348
|
-
} catch {
|
|
1349
|
-
return null;
|
|
1350
|
-
}
|
|
1351
|
-
})
|
|
1352
|
-
);
|
|
1353
|
-
return {
|
|
1354
|
-
type: "workspace_status",
|
|
1355
|
-
checkouts: inspected.filter((checkout) => checkout !== null).sort(
|
|
1356
|
-
(left, right) => left.projectPath.localeCompare(right.projectPath) || left.branchName.localeCompare(right.branchName)
|
|
1357
|
-
)
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
function internalGitProcessEnv(workspace, env) {
|
|
1361
|
-
if (env?.R5D_USE_INTERNAL_GIT !== "1") {
|
|
1362
|
-
return {};
|
|
1363
|
-
}
|
|
1364
|
-
return {
|
|
1365
|
-
GIT_DIR: workspace.internalGit.gitDir,
|
|
1366
|
-
GIT_WORK_TREE: workspace.branchPath
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
973
|
function formatLineNumberedContent(content, offset, limit) {
|
|
1370
974
|
const allLines = content.split("\n");
|
|
1371
975
|
const totalLines = allLines.length;
|
|
@@ -1474,9 +1078,11 @@ const fileMutationQueues = /* @__PURE__ */ new Map();
|
|
|
1474
1078
|
async function withFileMutationQueue(key, operation) {
|
|
1475
1079
|
const previous = fileMutationQueues.get(key) ?? Promise.resolve();
|
|
1476
1080
|
let release;
|
|
1477
|
-
const current = previous.catch(() => void 0).then(
|
|
1478
|
-
|
|
1479
|
-
|
|
1081
|
+
const current = previous.catch(() => void 0).then(
|
|
1082
|
+
() => new Promise((resolve) => {
|
|
1083
|
+
release = resolve;
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1480
1086
|
fileMutationQueues.set(key, current);
|
|
1481
1087
|
await previous.catch(() => void 0);
|
|
1482
1088
|
try {
|
|
@@ -1571,7 +1177,9 @@ async function executeEditFileOperation(input) {
|
|
|
1571
1177
|
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1572
1178
|
}
|
|
1573
1179
|
if (occurrences > 1) {
|
|
1574
|
-
throw new Error(
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1182
|
+
);
|
|
1575
1183
|
}
|
|
1576
1184
|
const start = originalContent.indexOf(edit.oldText);
|
|
1577
1185
|
return { index, start, end: start + edit.oldText.length, edit };
|
|
@@ -1831,114 +1439,8 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1831
1439
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1832
1440
|
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1833
1441
|
}
|
|
1834
|
-
function stagedBlobSizeBytes(context) {
|
|
1835
|
-
const names = runInternalGit(context, ["diff", "--cached", "--name-only", "-z", "--", ".", ":(exclude).git"]).split("\0").filter(Boolean);
|
|
1836
|
-
let total = 0;
|
|
1837
|
-
for (const name of names) {
|
|
1838
|
-
try {
|
|
1839
|
-
const sizeText = runInternalGit(context, ["cat-file", "-s", `:${name}`]);
|
|
1840
|
-
total += Number.parseInt(sizeText, 10) || 0;
|
|
1841
|
-
} catch {
|
|
1842
|
-
}
|
|
1843
|
-
if (total > MAX_SYNC_DIFF_BYTES) {
|
|
1844
|
-
return MAX_SYNC_DIFF_BYTES + 1;
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
return total;
|
|
1848
|
-
}
|
|
1849
|
-
function syncBranch(input) {
|
|
1850
|
-
const workspace = ensureOperationBranch(input);
|
|
1851
|
-
runInternalGit(workspace.internalGit, ["add", "-A", "--", ".", ":(exclude).git"]);
|
|
1852
|
-
const hasStagedChanges = !tryInternalGit(workspace.internalGit, ["diff", "--cached", "--quiet", "--", ".", ":(exclude).git"]);
|
|
1853
|
-
const status = getInternalStatus(workspace.internalGit);
|
|
1854
|
-
const currentCommit = getInternalCommitHash(workspace.internalGit);
|
|
1855
|
-
if (!hasStagedChanges) {
|
|
1856
|
-
return {
|
|
1857
|
-
type: "sync",
|
|
1858
|
-
changed: false,
|
|
1859
|
-
commitHash: currentCommit,
|
|
1860
|
-
diffSizeBytes: 0,
|
|
1861
|
-
status
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
const diffSizeBytes = stagedBlobSizeBytes(workspace.internalGit);
|
|
1865
|
-
if (diffSizeBytes > MAX_SYNC_DIFF_BYTES && !input.confirmedLargeDiff) {
|
|
1866
|
-
return {
|
|
1867
|
-
type: "sync",
|
|
1868
|
-
changed: true,
|
|
1869
|
-
commitHash: currentCommit,
|
|
1870
|
-
diffSizeBytes,
|
|
1871
|
-
status,
|
|
1872
|
-
largeDiffBlocked: true,
|
|
1873
|
-
trigger: input.trigger
|
|
1874
|
-
};
|
|
1875
|
-
}
|
|
1876
|
-
const message = JSON.stringify({
|
|
1877
|
-
type: "agent_changes",
|
|
1878
|
-
sessionId: input.sessionId,
|
|
1879
|
-
parentNodeId: input.parentNodeId,
|
|
1880
|
-
...input.confirmedLargeDiff ? { confirmedLargeDiff: true, confirmationReason: input.confirmationReason } : {}
|
|
1881
|
-
});
|
|
1882
|
-
runInternalGit(workspace.internalGit, ["commit", "-m", message]);
|
|
1883
|
-
const commitHash = getInternalCommitHash(workspace.internalGit);
|
|
1884
|
-
runInternalGit(workspace.internalGit, ["push", R5D_REMOTE_NAME, `HEAD:refs/heads/${input.branchName}`]);
|
|
1885
|
-
return {
|
|
1886
|
-
type: "sync",
|
|
1887
|
-
changed: true,
|
|
1888
|
-
commitHash,
|
|
1889
|
-
diffSizeBytes,
|
|
1890
|
-
status: getInternalStatus(workspace.internalGit),
|
|
1891
|
-
trigger: input.trigger
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
1894
|
-
function confirmLargeDiff(input) {
|
|
1895
|
-
const reason = input.reason.trim();
|
|
1896
|
-
if (!reason) {
|
|
1897
|
-
throw new Error("confirm_large_diff requires a non-empty reason");
|
|
1898
|
-
}
|
|
1899
|
-
const result = syncBranch({
|
|
1900
|
-
...input,
|
|
1901
|
-
confirmedLargeDiff: true,
|
|
1902
|
-
confirmationReason: reason
|
|
1903
|
-
});
|
|
1904
|
-
return {
|
|
1905
|
-
type: "confirm_large_diff",
|
|
1906
|
-
changed: result.changed,
|
|
1907
|
-
commitHash: result.commitHash,
|
|
1908
|
-
diffSizeBytes: result.diffSizeBytes,
|
|
1909
|
-
status: result.status,
|
|
1910
|
-
reason
|
|
1911
|
-
};
|
|
1912
|
-
}
|
|
1913
|
-
function pullBranch(input) {
|
|
1914
|
-
const workspace = ensureOperationBranch(input);
|
|
1915
|
-
runInternalGit(workspace.internalGit, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1916
|
-
const target = input.commitHash ?? `${R5D_REMOTE_NAME}/${input.branchName}`;
|
|
1917
|
-
runInternalGit(workspace.internalGit, ["reset", "--hard", target]);
|
|
1918
|
-
runInternalGit(workspace.internalGit, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1919
|
-
return {
|
|
1920
|
-
type: "pull_branch",
|
|
1921
|
-
commitHash: getInternalCommitHash(workspace.internalGit)
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
1442
|
async function executeOperation(input) {
|
|
1925
1443
|
switch (input.message.type) {
|
|
1926
|
-
case "resolve_managed_path":
|
|
1927
|
-
return resolveManagedCheckoutPath({
|
|
1928
|
-
projectsRoot: input.projectsRoot,
|
|
1929
|
-
manifests: input.manifests,
|
|
1930
|
-
originProjectId: input.message.projectId,
|
|
1931
|
-
originBranchName: input.message.branchName,
|
|
1932
|
-
inputPath: input.message.inputPath
|
|
1933
|
-
});
|
|
1934
|
-
case "workspace_status":
|
|
1935
|
-
return await inspectManagedWorkspace({
|
|
1936
|
-
baseUrl: input.baseUrl,
|
|
1937
|
-
token: input.token,
|
|
1938
|
-
projectsRoot: input.projectsRoot,
|
|
1939
|
-
syncRoot: input.syncRoot,
|
|
1940
|
-
manifests: input.manifests
|
|
1941
|
-
});
|
|
1942
1444
|
case "read":
|
|
1943
1445
|
return executeReadFileOperation({ ...input, message: input.message });
|
|
1944
1446
|
case "write":
|
|
@@ -1953,43 +1455,6 @@ async function executeOperation(input) {
|
|
|
1953
1455
|
return executeLsOperation({ ...input, message: input.message });
|
|
1954
1456
|
case "view_file_bytes":
|
|
1955
1457
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
1956
|
-
case "sync":
|
|
1957
|
-
return syncBranch({
|
|
1958
|
-
projectId: input.projectId,
|
|
1959
|
-
baseUrl: input.baseUrl,
|
|
1960
|
-
token: input.token,
|
|
1961
|
-
projectRoot: input.projectRoot,
|
|
1962
|
-
syncRoot: input.syncRoot,
|
|
1963
|
-
branchName: input.message.branchName,
|
|
1964
|
-
sessionId: input.message.sessionId,
|
|
1965
|
-
parentNodeId: input.message.parentNodeId,
|
|
1966
|
-
trigger: input.message.trigger,
|
|
1967
|
-
manifest: input.manifest
|
|
1968
|
-
});
|
|
1969
|
-
case "confirm_large_diff":
|
|
1970
|
-
return confirmLargeDiff({
|
|
1971
|
-
projectId: input.projectId,
|
|
1972
|
-
baseUrl: input.baseUrl,
|
|
1973
|
-
token: input.token,
|
|
1974
|
-
projectRoot: input.projectRoot,
|
|
1975
|
-
syncRoot: input.syncRoot,
|
|
1976
|
-
branchName: input.message.branchName,
|
|
1977
|
-
sessionId: input.message.sessionId,
|
|
1978
|
-
parentNodeId: input.message.parentNodeId,
|
|
1979
|
-
reason: input.message.reason,
|
|
1980
|
-
manifest: input.manifest
|
|
1981
|
-
});
|
|
1982
|
-
case "pull_branch":
|
|
1983
|
-
return pullBranch({
|
|
1984
|
-
projectId: input.projectId,
|
|
1985
|
-
baseUrl: input.baseUrl,
|
|
1986
|
-
token: input.token,
|
|
1987
|
-
projectRoot: input.projectRoot,
|
|
1988
|
-
syncRoot: input.syncRoot,
|
|
1989
|
-
branchName: input.message.branchName,
|
|
1990
|
-
commitHash: input.message.commitHash,
|
|
1991
|
-
manifest: input.manifest
|
|
1992
|
-
});
|
|
1993
1458
|
}
|
|
1994
1459
|
}
|
|
1995
1460
|
async function executeCommand(input) {
|
|
@@ -2021,7 +1486,11 @@ async function executeCommand(input) {
|
|
|
2021
1486
|
planRoot: input.planRoot,
|
|
2022
1487
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2023
1488
|
});
|
|
2024
|
-
const
|
|
1489
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1490
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !fs.existsSync(path.join(commandRoot, ".git"))) {
|
|
1491
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1492
|
+
}
|
|
1493
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
2025
1494
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
2026
1495
|
cwd,
|
|
2027
1496
|
stdout: "pipe",
|
|
@@ -2031,7 +1500,6 @@ async function executeCommand(input) {
|
|
|
2031
1500
|
...process.env,
|
|
2032
1501
|
...githubProcessEnv(),
|
|
2033
1502
|
...input.message.env ?? {},
|
|
2034
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
2035
1503
|
...artifactProcessEnv,
|
|
2036
1504
|
...planProcessEnv
|
|
2037
1505
|
}
|
|
@@ -2042,7 +1510,8 @@ async function executeCommand(input) {
|
|
|
2042
1510
|
sessionId: input.message.sessionId ?? "",
|
|
2043
1511
|
branchName: input.message.branchName,
|
|
2044
1512
|
mode: "foreground",
|
|
2045
|
-
|
|
1513
|
+
pid: subprocess.pid,
|
|
1514
|
+
processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
|
|
2046
1515
|
argv: input.message.argv,
|
|
2047
1516
|
command: input.message.argv.join(" "),
|
|
2048
1517
|
cwd,
|
|
@@ -2128,7 +1597,11 @@ async function executeStreamingCommand(input) {
|
|
|
2128
1597
|
planRoot: input.planRoot,
|
|
2129
1598
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2130
1599
|
});
|
|
2131
|
-
const
|
|
1600
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1601
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !fs.existsSync(path.join(commandRoot, ".git"))) {
|
|
1602
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1603
|
+
}
|
|
1604
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
2132
1605
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
2133
1606
|
cwd,
|
|
2134
1607
|
stdout: "pipe",
|
|
@@ -2138,7 +1611,6 @@ async function executeStreamingCommand(input) {
|
|
|
2138
1611
|
...process.env,
|
|
2139
1612
|
...githubProcessEnv(),
|
|
2140
1613
|
...input.message.env ?? {},
|
|
2141
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
2142
1614
|
...artifactProcessEnv,
|
|
2143
1615
|
...planProcessEnv
|
|
2144
1616
|
}
|
|
@@ -2149,16 +1621,11 @@ async function executeStreamingCommand(input) {
|
|
|
2149
1621
|
sessionId: input.message.sessionId,
|
|
2150
1622
|
branchName: input.message.branchName,
|
|
2151
1623
|
mode: input.message.mode,
|
|
2152
|
-
startCommitHash: input.message.startCommitHash,
|
|
2153
|
-
launchWorkspaceActionId: input.message.launchWorkspaceActionId,
|
|
2154
|
-
credentialId: input.message.credentialId,
|
|
2155
1624
|
pid: subprocess.pid,
|
|
2156
|
-
processGroupId: subprocess.pid,
|
|
1625
|
+
processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
|
|
1626
|
+
credentialId: input.message.credentialId,
|
|
2157
1627
|
argv: input.message.argv,
|
|
2158
1628
|
command: input.message.command,
|
|
2159
|
-
// Persist the resolved absolute cwd so reconnect reports and process
|
|
2160
|
-
// history retain the actual execution location, including cross-project
|
|
2161
|
-
// managed paths.
|
|
2162
1629
|
cwd,
|
|
2163
1630
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2164
1631
|
});
|
|
@@ -2169,7 +1636,7 @@ async function executeStreamingCommand(input) {
|
|
|
2169
1636
|
runId: input.message.runId,
|
|
2170
1637
|
cwd,
|
|
2171
1638
|
pid: subprocess.pid,
|
|
2172
|
-
processGroupId: subprocess.pid
|
|
1639
|
+
...process.platform === "win32" ? {} : { processGroupId: subprocess.pid }
|
|
2173
1640
|
});
|
|
2174
1641
|
if (input.message.timeoutMs) {
|
|
2175
1642
|
timeout = setTimeout(() => {
|
|
@@ -2201,22 +1668,26 @@ async function executeStreamingCommand(input) {
|
|
|
2201
1668
|
});
|
|
2202
1669
|
})
|
|
2203
1670
|
]);
|
|
2204
|
-
|
|
1671
|
+
const terminal = {
|
|
2205
1672
|
type: "exec_exit",
|
|
2206
1673
|
runId: input.message.runId,
|
|
2207
1674
|
exitCode,
|
|
2208
1675
|
durationMs: Date.now() - startedAt,
|
|
2209
1676
|
...timedOut ? { timedOut: true } : {}
|
|
2210
|
-
}
|
|
1677
|
+
};
|
|
1678
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1679
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2211
1680
|
} catch (error) {
|
|
2212
1681
|
const message = error instanceof Error ? error.message : String(error);
|
|
2213
1682
|
if (started) {
|
|
2214
|
-
|
|
1683
|
+
const terminal = {
|
|
2215
1684
|
type: "exec_error",
|
|
2216
1685
|
runId: input.message.runId,
|
|
2217
1686
|
error: message,
|
|
2218
1687
|
durationMs: Date.now() - startedAt
|
|
2219
|
-
}
|
|
1688
|
+
};
|
|
1689
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1690
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2220
1691
|
} else {
|
|
2221
1692
|
sendWorkerMessage(input.ws, {
|
|
2222
1693
|
type: "exec_start_error",
|
|
@@ -2248,12 +1719,12 @@ function buildActiveProcessReports() {
|
|
|
2248
1719
|
projectId: active.projectId,
|
|
2249
1720
|
sessionId: active.sessionId,
|
|
2250
1721
|
branchName: active.branchName,
|
|
1722
|
+
platform: process.platform,
|
|
1723
|
+
arch: process.arch,
|
|
2251
1724
|
mode: active.mode,
|
|
2252
|
-
startCommitHash: active.startCommitHash,
|
|
2253
|
-
...active.launchWorkspaceActionId ? { launchWorkspaceActionId: active.launchWorkspaceActionId } : {},
|
|
2254
|
-
...active.credentialId ? { credentialId: active.credentialId } : {},
|
|
2255
1725
|
...active.pid !== void 0 ? { pid: active.pid } : {},
|
|
2256
1726
|
...active.processGroupId !== void 0 ? { processGroupId: active.processGroupId } : {},
|
|
1727
|
+
...active.credentialId ? { credentialId: active.credentialId } : {},
|
|
2257
1728
|
argv: active.argv,
|
|
2258
1729
|
command: active.command,
|
|
2259
1730
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
@@ -2554,10 +2025,7 @@ function resizePty(message) {
|
|
|
2554
2025
|
if (!ptyProcess) {
|
|
2555
2026
|
return;
|
|
2556
2027
|
}
|
|
2557
|
-
ptyProcess.resize(
|
|
2558
|
-
Math.max(1, Math.min(Math.floor(message.cols || 80), 500)),
|
|
2559
|
-
Math.max(1, Math.min(Math.floor(message.rows || 24), 500))
|
|
2560
|
-
);
|
|
2028
|
+
ptyProcess.resize(Math.max(1, Math.min(Math.floor(message.cols || 80), 500)), Math.max(1, Math.min(Math.floor(message.rows || 24), 500)));
|
|
2561
2029
|
}
|
|
2562
2030
|
function closePty(message) {
|
|
2563
2031
|
const ptyProcess = activePtys.get(message.ptyId);
|
|
@@ -2589,6 +2057,7 @@ async function startWorker(options) {
|
|
|
2589
2057
|
validateLabel(label);
|
|
2590
2058
|
const projectsRoot = path.resolve(options.projectRoot ?? defaultProjectsRoot());
|
|
2591
2059
|
const syncRoot = path.resolve(options.syncRoot ?? process.env.R5D_SYNC_ROOT ?? defaultSyncRoot());
|
|
2060
|
+
const workspaceShadowRoot = path.join(syncRoot, "workspace");
|
|
2592
2061
|
const artifactRoot = path.resolve(options.artifactRoot ?? process.env.R5D_ARTIFACTS_ROOT ?? defaultArtifactRoot());
|
|
2593
2062
|
const planRoot = path.resolve(options.planRoot ?? process.env.R5D_PLANS_ROOT ?? defaultPlanRoot());
|
|
2594
2063
|
process.stdout.write(`[r5d-worker] label: ${label}
|
|
@@ -2610,10 +2079,104 @@ async function startWorker(options) {
|
|
|
2610
2079
|
fs.mkdirSync(artifactRoot, { recursive: true });
|
|
2611
2080
|
fs.mkdirSync(planRoot, { recursive: true });
|
|
2612
2081
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
2613
|
-
|
|
2614
|
-
let
|
|
2082
|
+
const workspaceSyncSingleFlight = new WorkspaceSyncSingleFlight();
|
|
2083
|
+
let workspaceRemoteUrl = null;
|
|
2084
|
+
let activeWorkspaceIncidentId = null;
|
|
2085
|
+
let previousPeriodicFingerprint = null;
|
|
2086
|
+
let periodicWorkspaceScan;
|
|
2087
|
+
let periodicWorkspaceScanInFlight = false;
|
|
2088
|
+
let terminalReplayTimer;
|
|
2089
|
+
let workspaceSyncRequestsInFlight = 0;
|
|
2615
2090
|
let cliUpdateInProgress = false;
|
|
2616
2091
|
let reloadAfterClose = false;
|
|
2092
|
+
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2093
|
+
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2094
|
+
return {
|
|
2095
|
+
workerLabel: label,
|
|
2096
|
+
remoteUrl: workspaceRemoteUrl,
|
|
2097
|
+
authHeader: `Authorization: Bearer ${token}`,
|
|
2098
|
+
projectsRoot,
|
|
2099
|
+
plansRoot: planRoot,
|
|
2100
|
+
shadowRoot: workspaceShadowRoot,
|
|
2101
|
+
projects: [...manifestByProjectId.values()],
|
|
2102
|
+
trigger,
|
|
2103
|
+
...overrides
|
|
2104
|
+
};
|
|
2105
|
+
};
|
|
2106
|
+
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2107
|
+
sendWorkerMessage(ws, { type: "workspace_sync_result", requestId, result });
|
|
2108
|
+
};
|
|
2109
|
+
const ensureAllVisibleWorkspaceCheckouts = () => {
|
|
2110
|
+
const created = [];
|
|
2111
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2112
|
+
for (const branchName of manifest.branches) {
|
|
2113
|
+
const branchPath = path.join(projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId), branchName);
|
|
2114
|
+
const existed = fs.existsSync(path.join(branchPath, ".git"));
|
|
2115
|
+
const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
|
|
2116
|
+
ensureVisibleGitCheckout({
|
|
2117
|
+
projectRoot: projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId),
|
|
2118
|
+
branchName,
|
|
2119
|
+
githubRemoteUrl,
|
|
2120
|
+
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2121
|
+
githubAuthHeader: manifest.repoAuthHeader,
|
|
2122
|
+
defaultBranch: manifest.defaultBranch || "main",
|
|
2123
|
+
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2124
|
+
});
|
|
2125
|
+
if (!existed) created.push({ projectId: manifest.projectId, branchName });
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
return created;
|
|
2129
|
+
};
|
|
2130
|
+
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2131
|
+
workspaceSyncRequestsInFlight += 1;
|
|
2132
|
+
try {
|
|
2133
|
+
if (trigger.type === "plan_write" || trigger.type === "task_status" || trigger.type === "connect" || trigger.type === "inbound_head" || trigger.type === "manual") {
|
|
2134
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2135
|
+
for (const branchName of manifest.branches) {
|
|
2136
|
+
await syncProjectPlans({
|
|
2137
|
+
baseUrl,
|
|
2138
|
+
token,
|
|
2139
|
+
projectId: manifest.projectId,
|
|
2140
|
+
branchName,
|
|
2141
|
+
planRoot
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
const newVisibleCheckouts = trigger.type === "connect" ? ensureAllVisibleWorkspaceCheckouts() : [];
|
|
2147
|
+
const result = await workspaceSyncSingleFlight.run(
|
|
2148
|
+
workspaceSyncInput(trigger, {
|
|
2149
|
+
...overrides,
|
|
2150
|
+
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2151
|
+
})
|
|
2152
|
+
);
|
|
2153
|
+
previousPeriodicFingerprint = null;
|
|
2154
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2155
|
+
return result;
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
const result = {
|
|
2158
|
+
type: "workspace_sync",
|
|
2159
|
+
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2160
|
+
workerLabel: label,
|
|
2161
|
+
trigger,
|
|
2162
|
+
outcome: "failed",
|
|
2163
|
+
startingHead: null,
|
|
2164
|
+
rebaseCount: 0,
|
|
2165
|
+
diffSizeBytes: 0,
|
|
2166
|
+
gitStatus: "",
|
|
2167
|
+
affectedProjects: [],
|
|
2168
|
+
affectedPaths: [],
|
|
2169
|
+
discardedPaths: [],
|
|
2170
|
+
localChangesDiscarded: false,
|
|
2171
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2172
|
+
};
|
|
2173
|
+
previousPeriodicFingerprint = null;
|
|
2174
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2175
|
+
return result;
|
|
2176
|
+
} finally {
|
|
2177
|
+
workspaceSyncRequestsInFlight -= 1;
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2617
2180
|
const ws = new WebSocket(websocketUrl(baseUrl, label), {
|
|
2618
2181
|
headers: {
|
|
2619
2182
|
Authorization: `Bearer ${token}`
|
|
@@ -2658,6 +2221,15 @@ async function startWorker(options) {
|
|
|
2658
2221
|
};
|
|
2659
2222
|
ws.send(JSON.stringify(hello));
|
|
2660
2223
|
sendActiveProcessReport(ws);
|
|
2224
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2225
|
+
sendWorkerMessage(ws, terminal);
|
|
2226
|
+
}
|
|
2227
|
+
terminalReplayTimer = setInterval(() => {
|
|
2228
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2229
|
+
sendWorkerMessage(ws, terminal);
|
|
2230
|
+
}
|
|
2231
|
+
}, 5e3);
|
|
2232
|
+
terminalReplayTimer.unref();
|
|
2661
2233
|
process.stdout.write("[r5d-worker] connected\n");
|
|
2662
2234
|
});
|
|
2663
2235
|
ws.addEventListener("message", (event) => {
|
|
@@ -2666,91 +2238,68 @@ async function startWorker(options) {
|
|
|
2666
2238
|
if (message.type === "connected") {
|
|
2667
2239
|
return;
|
|
2668
2240
|
}
|
|
2669
|
-
if (message.type === "
|
|
2241
|
+
if (message.type === "workspace_manifest") {
|
|
2670
2242
|
configureGitHubAuth(message.githubCredential);
|
|
2671
2243
|
visibleGitIdentity = message.gitIdentity;
|
|
2244
|
+
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2672
2245
|
manifestByProjectId.clear();
|
|
2673
|
-
for (const project of message.projects)
|
|
2674
|
-
|
|
2675
|
-
}
|
|
2676
|
-
process.stdout.write(`[r5d-worker] project manifest: ${message.projects.length} projects
|
|
2246
|
+
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2247
|
+
process.stdout.write(`[r5d-worker] workspace manifest: ${message.projects.length} projects
|
|
2677
2248
|
`);
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
}
|
|
2690
|
-
});
|
|
2691
|
-
return;
|
|
2692
|
-
}
|
|
2693
|
-
let syncResult = { status: "completed", results: [], blockedBy: [] };
|
|
2694
|
-
const executeSync = async () => {
|
|
2695
|
-
repositorySyncInProgress = true;
|
|
2696
|
-
try {
|
|
2697
|
-
syncResult = await syncManifestProjectsFromInternal({
|
|
2698
|
-
baseUrl,
|
|
2699
|
-
token,
|
|
2700
|
-
projectsRoot,
|
|
2701
|
-
syncRoot,
|
|
2702
|
-
manifests: [...manifestByProjectId.values()],
|
|
2703
|
-
requestedTargets: message.targets
|
|
2704
|
-
});
|
|
2705
|
-
for (const result of syncResult.results) {
|
|
2706
|
-
if (result.status !== "synced") continue;
|
|
2707
|
-
try {
|
|
2708
|
-
await syncProjectPlans({
|
|
2709
|
-
baseUrl,
|
|
2710
|
-
token,
|
|
2711
|
-
projectId: result.projectId,
|
|
2712
|
-
branchName: result.branchName,
|
|
2713
|
-
planRoot
|
|
2714
|
-
});
|
|
2715
|
-
} catch (error) {
|
|
2716
|
-
process.stderr.write(
|
|
2717
|
-
`[r5d-worker] plan sync skipped for ${result.projectId}/${result.branchName}: ${error instanceof Error ? error.message : String(error)}
|
|
2718
|
-
`
|
|
2719
|
-
);
|
|
2249
|
+
if (!periodicWorkspaceScan) {
|
|
2250
|
+
const emptyFingerprint = createHash("sha256").update("").digest("hex");
|
|
2251
|
+
periodicWorkspaceScan = setInterval(() => {
|
|
2252
|
+
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2253
|
+
periodicWorkspaceScanInFlight = true;
|
|
2254
|
+
void (async () => {
|
|
2255
|
+
const input = workspaceSyncInput({ type: "periodic" });
|
|
2256
|
+
const fingerprint = await workspaceSyncSingleFlight.fingerprint(input);
|
|
2257
|
+
if (fingerprint === emptyFingerprint) {
|
|
2258
|
+
previousPeriodicFingerprint = null;
|
|
2259
|
+
return;
|
|
2720
2260
|
}
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
}
|
|
2725
|
-
};
|
|
2726
|
-
const queued = repositorySyncQueue.then(executeSync, executeSync);
|
|
2727
|
-
repositorySyncQueue = queued.then(
|
|
2728
|
-
() => void 0,
|
|
2729
|
-
() => void 0
|
|
2730
|
-
);
|
|
2731
|
-
try {
|
|
2732
|
-
await queued;
|
|
2733
|
-
} catch (error) {
|
|
2734
|
-
syncResult = {
|
|
2735
|
-
status: "partial",
|
|
2736
|
-
results: [
|
|
2737
|
-
{
|
|
2738
|
-
projectId: "worker",
|
|
2739
|
-
projectPath: "worker",
|
|
2740
|
-
branchName: "all",
|
|
2741
|
-
status: "failed",
|
|
2742
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2261
|
+
if (previousPeriodicFingerprint !== fingerprint) {
|
|
2262
|
+
previousPeriodicFingerprint = fingerprint;
|
|
2263
|
+
return;
|
|
2743
2264
|
}
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2265
|
+
previousPeriodicFingerprint = null;
|
|
2266
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "periodic" });
|
|
2267
|
+
})().catch((error) => {
|
|
2268
|
+
process.stderr.write(
|
|
2269
|
+
`[r5d-worker] periodic workspace synchronization failed: ${error instanceof Error ? error.message : String(error)}
|
|
2270
|
+
`
|
|
2271
|
+
);
|
|
2272
|
+
}).finally(() => {
|
|
2273
|
+
periodicWorkspaceScanInFlight = false;
|
|
2274
|
+
});
|
|
2275
|
+
}, WORKSPACE_PERIODIC_SCAN_INTERVAL_MS);
|
|
2276
|
+
periodicWorkspaceScan.unref();
|
|
2747
2277
|
}
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
if (message.type === "sync_workspace") {
|
|
2281
|
+
await runRequestedWorkspaceSync(message.requestId, message.trigger, {
|
|
2282
|
+
attemptId: message.attemptId,
|
|
2283
|
+
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
2284
|
+
confirmationReason: message.confirmationReason,
|
|
2285
|
+
resetToCanonical: message.resetToCanonical,
|
|
2286
|
+
skipVisibleMirror: message.skipVisibleMirror
|
|
2752
2287
|
});
|
|
2753
|
-
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
if (message.type === "workspace_head_updated") {
|
|
2291
|
+
if (!activeWorkspaceIncidentId) {
|
|
2292
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "inbound_head", detail: message.commitHash });
|
|
2293
|
+
}
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
if (message.type === "workspace_incident_updated") {
|
|
2297
|
+
activeWorkspaceIncidentId = message.status === "remediating" || message.status === "waiting_for_worker" ? message.incidentId : null;
|
|
2298
|
+
previousPeriodicFingerprint = null;
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
if (message.type === "exec_terminal_ack") {
|
|
2302
|
+
pendingProcessTerminals.delete(message.runId);
|
|
2754
2303
|
return;
|
|
2755
2304
|
}
|
|
2756
2305
|
if (message.type === "update_clis") {
|
|
@@ -2762,7 +2311,7 @@ async function startWorker(options) {
|
|
|
2762
2311
|
});
|
|
2763
2312
|
return;
|
|
2764
2313
|
}
|
|
2765
|
-
if (activeProcesses.size > 0 || activePtys.size > 0 ||
|
|
2314
|
+
if (activeProcesses.size > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0) {
|
|
2766
2315
|
sendWorkerMessage(ws, {
|
|
2767
2316
|
type: "update_clis_result",
|
|
2768
2317
|
requestId: message.requestId,
|
|
@@ -2832,7 +2381,6 @@ async function startWorker(options) {
|
|
|
2832
2381
|
});
|
|
2833
2382
|
return;
|
|
2834
2383
|
}
|
|
2835
|
-
await repositorySyncQueue;
|
|
2836
2384
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2837
2385
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2838
2386
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.projectId}/${message.branchName}
|
|
@@ -2863,7 +2411,6 @@ async function startWorker(options) {
|
|
|
2863
2411
|
});
|
|
2864
2412
|
return;
|
|
2865
2413
|
}
|
|
2866
|
-
await repositorySyncQueue;
|
|
2867
2414
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2868
2415
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2869
2416
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
@@ -2877,6 +2424,7 @@ async function startWorker(options) {
|
|
|
2877
2424
|
syncRoot,
|
|
2878
2425
|
artifactRoot,
|
|
2879
2426
|
planRoot,
|
|
2427
|
+
workspaceShadowRoot,
|
|
2880
2428
|
manifest
|
|
2881
2429
|
});
|
|
2882
2430
|
ws.send(JSON.stringify(result));
|
|
@@ -2892,7 +2440,6 @@ async function startWorker(options) {
|
|
|
2892
2440
|
});
|
|
2893
2441
|
return;
|
|
2894
2442
|
}
|
|
2895
|
-
await repositorySyncQueue;
|
|
2896
2443
|
sendWorkerMessage(ws, {
|
|
2897
2444
|
type: "exec_accepted",
|
|
2898
2445
|
requestId: message.requestId,
|
|
@@ -2913,6 +2460,7 @@ async function startWorker(options) {
|
|
|
2913
2460
|
syncRoot,
|
|
2914
2461
|
artifactRoot,
|
|
2915
2462
|
planRoot,
|
|
2463
|
+
workspaceShadowRoot,
|
|
2916
2464
|
manifest
|
|
2917
2465
|
}).catch((error) => {
|
|
2918
2466
|
sendWorkerMessage(ws, {
|
|
@@ -2932,9 +2480,8 @@ async function startWorker(options) {
|
|
|
2932
2480
|
}
|
|
2933
2481
|
return;
|
|
2934
2482
|
}
|
|
2935
|
-
if (message.type === "
|
|
2483
|
+
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
|
|
2936
2484
|
try {
|
|
2937
|
-
await repositorySyncQueue;
|
|
2938
2485
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2939
2486
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2940
2487
|
const result = await executeOperation({
|
|
@@ -2945,8 +2492,6 @@ async function startWorker(options) {
|
|
|
2945
2492
|
projectRoot,
|
|
2946
2493
|
syncRoot,
|
|
2947
2494
|
artifactRoot,
|
|
2948
|
-
projectsRoot,
|
|
2949
|
-
manifests: [...manifestByProjectId.values()],
|
|
2950
2495
|
manifest
|
|
2951
2496
|
});
|
|
2952
2497
|
ws.send(
|
|
@@ -2984,6 +2529,14 @@ async function startWorker(options) {
|
|
|
2984
2529
|
});
|
|
2985
2530
|
ws.addEventListener("close", (event) => {
|
|
2986
2531
|
stopHeartbeatWatchdog();
|
|
2532
|
+
if (periodicWorkspaceScan) {
|
|
2533
|
+
clearInterval(periodicWorkspaceScan);
|
|
2534
|
+
periodicWorkspaceScan = void 0;
|
|
2535
|
+
}
|
|
2536
|
+
if (terminalReplayTimer) {
|
|
2537
|
+
clearInterval(terminalReplayTimer);
|
|
2538
|
+
terminalReplayTimer = void 0;
|
|
2539
|
+
}
|
|
2987
2540
|
if (currentWorkerSocket === ws) {
|
|
2988
2541
|
currentWorkerSocket = null;
|
|
2989
2542
|
}
|
|
@@ -2994,15 +2547,17 @@ async function startWorker(options) {
|
|
|
2994
2547
|
if (reloadAfterClose) {
|
|
2995
2548
|
process.exit(WORKER_RELOAD_EXIT_CODE);
|
|
2996
2549
|
}
|
|
2997
|
-
if (activeProcesses.size > 0) {
|
|
2550
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
2998
2551
|
const delayMs = 2e3;
|
|
2999
|
-
process.stderr.write(
|
|
3000
|
-
`);
|
|
2552
|
+
process.stderr.write(
|
|
2553
|
+
`[r5d-worker] ${activeProcesses.size} process(es) active and ${pendingProcessTerminals.size} terminal report(s) pending; reconnecting in ${delayMs}ms
|
|
2554
|
+
`
|
|
2555
|
+
);
|
|
3001
2556
|
const reconnect = () => {
|
|
3002
2557
|
void startWorker(options).catch((error) => {
|
|
3003
2558
|
process.stderr.write(`[r5d-worker] reconnect failed: ${error instanceof Error ? error.message : String(error)}
|
|
3004
2559
|
`);
|
|
3005
|
-
if (activeProcesses.size > 0) {
|
|
2560
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
3006
2561
|
setTimeout(reconnect, delayMs);
|
|
3007
2562
|
return;
|
|
3008
2563
|
}
|
|
@@ -3057,11 +2612,9 @@ if (isCliEntrypoint()) {
|
|
|
3057
2612
|
}
|
|
3058
2613
|
export {
|
|
3059
2614
|
ensureVisibleGitCheckout,
|
|
3060
|
-
findRepositorySyncBlockers,
|
|
3061
2615
|
findWorkerFiles,
|
|
3062
2616
|
githubCliEnv,
|
|
3063
2617
|
grepWorkerFiles,
|
|
3064
|
-
inspectManagedWorkspace,
|
|
3065
2618
|
isArtifactEnvPath,
|
|
3066
2619
|
listWorkerDirectory,
|
|
3067
2620
|
prepareArtifactEnvForShell,
|
|
@@ -3069,11 +2622,8 @@ export {
|
|
|
3069
2622
|
readWorkerImageFile,
|
|
3070
2623
|
readWorkerTextFile,
|
|
3071
2624
|
resolveHostShell,
|
|
3072
|
-
resolveManagedCheckoutPath,
|
|
3073
2625
|
resolveProjectFilePath,
|
|
3074
2626
|
resolveWorkerFilePath,
|
|
3075
|
-
selectManifestSyncTargets,
|
|
3076
|
-
syncManifestProjectsFromInternal,
|
|
3077
2627
|
syncProjectPlans,
|
|
3078
2628
|
syncSessionArtifacts
|
|
3079
2629
|
};
|