@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/cjs/main.cjs
CHANGED
|
@@ -30,11 +30,9 @@ 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,
|
|
37
|
-
inspectManagedWorkspace: () => inspectManagedWorkspace,
|
|
38
36
|
isArtifactEnvPath: () => isArtifactEnvPath,
|
|
39
37
|
listWorkerDirectory: () => listWorkerDirectory,
|
|
40
38
|
prepareArtifactEnvForShell: () => prepareArtifactEnvForShell,
|
|
@@ -42,11 +40,8 @@ __export(main_exports, {
|
|
|
42
40
|
readWorkerImageFile: () => readWorkerImageFile,
|
|
43
41
|
readWorkerTextFile: () => readWorkerTextFile,
|
|
44
42
|
resolveHostShell: () => resolveHostShell,
|
|
45
|
-
resolveManagedCheckoutPath: () => resolveManagedCheckoutPath,
|
|
46
43
|
resolveProjectFilePath: () => resolveProjectFilePath,
|
|
47
44
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
48
|
-
selectManifestSyncTargets: () => selectManifestSyncTargets,
|
|
49
|
-
syncManifestProjectsFromInternal: () => syncManifestProjectsFromInternal,
|
|
50
45
|
syncProjectPlans: () => syncProjectPlans,
|
|
51
46
|
syncSessionArtifacts: () => syncSessionArtifacts
|
|
52
47
|
});
|
|
@@ -61,6 +56,7 @@ var import_git_identity = require("./git-identity.cjs");
|
|
|
61
56
|
var import_heartbeat = require("./heartbeat.cjs");
|
|
62
57
|
var import_process_tree = require("./process-tree.cjs");
|
|
63
58
|
var import_supervisor = require("./supervisor.cjs");
|
|
59
|
+
var import_workspace_sync = require("./workspace-sync.cjs");
|
|
64
60
|
const import_meta = {};
|
|
65
61
|
const DEFAULT_BASE_URL = "https://r5d.dev";
|
|
66
62
|
const WORKER_PACKAGE_NAME = "@ricsam/r5d-worker";
|
|
@@ -69,10 +65,8 @@ const BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]$|^[A-Za-z0-9]$/;
|
|
|
69
65
|
const DEFAULT_READ_LIMIT = 2e3;
|
|
70
66
|
const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
71
67
|
const MAX_LINE_LENGTH = 2e3;
|
|
72
|
-
const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
|
|
73
|
-
const R5D_REMOTE_NAME = "r5d";
|
|
74
|
-
const LEGACY_BUILD_IT_NOW_REMOTE_NAME = "build-it-now";
|
|
75
68
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
69
|
+
const pendingProcessTerminals = /* @__PURE__ */ new Map();
|
|
76
70
|
const cancelledProcessRuns = /* @__PURE__ */ new Set();
|
|
77
71
|
const activePtys = /* @__PURE__ */ new Map();
|
|
78
72
|
let currentWorkerSocket = null;
|
|
@@ -531,8 +525,10 @@ async function prepareArtifactEnvForShell(input) {
|
|
|
531
525
|
artifactRoot: input.artifactRoot
|
|
532
526
|
});
|
|
533
527
|
} catch (error) {
|
|
534
|
-
process.stderr.write(
|
|
535
|
-
`)
|
|
528
|
+
process.stderr.write(
|
|
529
|
+
`[r5d-worker] artifact sync skipped for ${input.sessionId}: ${error instanceof Error ? error.message : String(error)}
|
|
530
|
+
`
|
|
531
|
+
);
|
|
536
532
|
}
|
|
537
533
|
const artifactsDir = sessionArtifactDir(input.artifactRoot, input.sessionId);
|
|
538
534
|
import_node_fs.default.mkdirSync(artifactsDir, { recursive: true });
|
|
@@ -588,7 +584,9 @@ function resolveCredentials(options, config) {
|
|
|
588
584
|
throw new Error("Authentication required. Run `r5dctl auth login` or set R5D_WORKER_TOKEN/R5D_API_KEY.");
|
|
589
585
|
}
|
|
590
586
|
return {
|
|
591
|
-
baseUrl: normalizeBaseUrl(
|
|
587
|
+
baseUrl: normalizeBaseUrl(
|
|
588
|
+
options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL
|
|
589
|
+
),
|
|
592
590
|
token
|
|
593
591
|
};
|
|
594
592
|
}
|
|
@@ -642,27 +640,6 @@ function runGit(args, options = {}) {
|
|
|
642
640
|
}
|
|
643
641
|
return result.stdout.toString().trim();
|
|
644
642
|
}
|
|
645
|
-
async function runGitAsync(args, options = {}) {
|
|
646
|
-
const command = ["git", ...gitAuthArgs(options.auth), ...args];
|
|
647
|
-
const subprocess = Bun.spawn(command, {
|
|
648
|
-
cwd: options.cwd,
|
|
649
|
-
stdout: "pipe",
|
|
650
|
-
stderr: "pipe",
|
|
651
|
-
env: {
|
|
652
|
-
...process.env,
|
|
653
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
657
|
-
collectStream(subprocess.stdout),
|
|
658
|
-
collectStream(subprocess.stderr),
|
|
659
|
-
subprocess.exited
|
|
660
|
-
]);
|
|
661
|
-
if (exitCode !== 0) {
|
|
662
|
-
throw new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim() || `exit ${exitCode}`}`);
|
|
663
|
-
}
|
|
664
|
-
return stdout.trim();
|
|
665
|
-
}
|
|
666
643
|
function tryGit(args, options = {}) {
|
|
667
644
|
try {
|
|
668
645
|
runGit(args, options);
|
|
@@ -671,34 +648,6 @@ function tryGit(args, options = {}) {
|
|
|
671
648
|
return false;
|
|
672
649
|
}
|
|
673
650
|
}
|
|
674
|
-
async function tryGitAsync(args, options = {}) {
|
|
675
|
-
try {
|
|
676
|
-
await runGitAsync(args, options);
|
|
677
|
-
return true;
|
|
678
|
-
} catch {
|
|
679
|
-
return false;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
function runInternalGit(context, args) {
|
|
683
|
-
return runGit(["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], { auth: context.auth });
|
|
684
|
-
}
|
|
685
|
-
function runInternalGitAsync(context, args) {
|
|
686
|
-
return runGitAsync(["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], { auth: context.auth });
|
|
687
|
-
}
|
|
688
|
-
function runInternalGitDir(context, args) {
|
|
689
|
-
return runGit(["--git-dir", context.gitDir, ...args], { auth: context.auth });
|
|
690
|
-
}
|
|
691
|
-
function tryInternalGit(context, args) {
|
|
692
|
-
try {
|
|
693
|
-
runInternalGit(context, args);
|
|
694
|
-
return true;
|
|
695
|
-
} catch {
|
|
696
|
-
return false;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
function getInternalCommitHash(context) {
|
|
700
|
-
return runInternalGit(context, ["rev-parse", "HEAD"]);
|
|
701
|
-
}
|
|
702
651
|
function getGitBlobHashForContent(content) {
|
|
703
652
|
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
704
653
|
return (0, import_node_crypto.createHash)("sha1").update(`blob ${buffer.length}\0`).update(buffer).digest("hex");
|
|
@@ -706,12 +655,6 @@ function getGitBlobHashForContent(content) {
|
|
|
706
655
|
function hasWorktreeChanges(cwd) {
|
|
707
656
|
return runGit(["status", "--porcelain"], { cwd }).trim().length > 0;
|
|
708
657
|
}
|
|
709
|
-
function getInternalStatus(context) {
|
|
710
|
-
return runInternalGit(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
|
|
711
|
-
}
|
|
712
|
-
function hasInternalWorktreeChanges(context) {
|
|
713
|
-
return getInternalStatus(context).trim().length > 0;
|
|
714
|
-
}
|
|
715
658
|
function isInsideBranchPath(branchPath, filePath) {
|
|
716
659
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(branchPath), import_node_path.default.resolve(filePath));
|
|
717
660
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
@@ -746,11 +689,6 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
746
689
|
const displayPath = toProjectDisplayPath(resolvedBranchPath, absolutePath2);
|
|
747
690
|
const repoRelativePath2 = displayPath === "." ? "" : displayPath;
|
|
748
691
|
assertAllowedProjectPath(repoRelativePath2, inputPath);
|
|
749
|
-
const realBranchPath2 = import_node_fs.default.existsSync(resolvedBranchPath) ? import_node_fs.default.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
750
|
-
const realTargetPath2 = resolveThroughExistingAncestor(absolutePath2);
|
|
751
|
-
if (!isInsideBranchPath(realBranchPath2, realTargetPath2)) {
|
|
752
|
-
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
753
|
-
}
|
|
754
692
|
return {
|
|
755
693
|
absolutePath: absolutePath2,
|
|
756
694
|
displayPath,
|
|
@@ -769,11 +707,6 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
769
707
|
if (!isInsideBranchPath(resolvedBranchPath, absolutePath)) {
|
|
770
708
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
771
709
|
}
|
|
772
|
-
const realBranchPath = import_node_fs.default.existsSync(resolvedBranchPath) ? import_node_fs.default.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
773
|
-
const realTargetPath = resolveThroughExistingAncestor(absolutePath);
|
|
774
|
-
if (!isInsideBranchPath(realBranchPath, realTargetPath)) {
|
|
775
|
-
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
776
|
-
}
|
|
777
710
|
return {
|
|
778
711
|
absolutePath,
|
|
779
712
|
displayPath: repoRelativePath || ".",
|
|
@@ -784,81 +717,15 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
784
717
|
function resolveProjectFilePath(branchPath, inputPath) {
|
|
785
718
|
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
786
719
|
if (resolved.scope === "host") {
|
|
787
|
-
throw new Error(
|
|
788
|
-
`Project file writes must resolve inside the selected managed checkout: ${inputPath}`
|
|
789
|
-
);
|
|
720
|
+
throw new Error(`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`);
|
|
790
721
|
}
|
|
791
722
|
return resolved;
|
|
792
723
|
}
|
|
793
|
-
function resolveThroughExistingAncestor(inputPath) {
|
|
794
|
-
const suffix = [];
|
|
795
|
-
let cursor = import_node_path.default.resolve(inputPath);
|
|
796
|
-
while (!import_node_fs.default.existsSync(cursor)) {
|
|
797
|
-
const parent = import_node_path.default.dirname(cursor);
|
|
798
|
-
if (parent === cursor) return import_node_path.default.resolve(inputPath);
|
|
799
|
-
suffix.unshift(import_node_path.default.basename(cursor));
|
|
800
|
-
cursor = parent;
|
|
801
|
-
}
|
|
802
|
-
return import_node_path.default.resolve(import_node_fs.default.realpathSync(cursor), ...suffix);
|
|
803
|
-
}
|
|
804
|
-
function resolveManagedCheckoutPath(input) {
|
|
805
|
-
const originManifest = input.manifests.find((manifest) => manifest.projectId === input.originProjectId);
|
|
806
|
-
const candidate = import_node_path.default.isAbsolute(input.inputPath) ? import_node_path.default.resolve(input.inputPath) : import_node_path.default.resolve(
|
|
807
|
-
input.projectsRoot,
|
|
808
|
-
originManifest?.repoSlug ?? input.originProjectId,
|
|
809
|
-
input.originBranchName,
|
|
810
|
-
input.inputPath
|
|
811
|
-
);
|
|
812
|
-
const resolvedCandidate = resolveThroughExistingAncestor(candidate);
|
|
813
|
-
for (const manifest of input.manifests) {
|
|
814
|
-
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
815
|
-
for (const branchName of branches) {
|
|
816
|
-
const checkoutPath = import_node_path.default.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName);
|
|
817
|
-
if (!hasNormalVisibleGitDir(checkoutPath)) continue;
|
|
818
|
-
const resolvedCheckoutPath = import_node_fs.default.existsSync(checkoutPath) ? import_node_fs.default.realpathSync(checkoutPath) : checkoutPath;
|
|
819
|
-
const matchesLexically = isInsideBranchPath(checkoutPath, candidate);
|
|
820
|
-
if (!matchesLexically) continue;
|
|
821
|
-
if (!isInsideBranchPath(resolvedCheckoutPath, resolvedCandidate)) {
|
|
822
|
-
throw new Error(`Managed path escapes its checkout through a symbolic link: ${input.inputPath}`);
|
|
823
|
-
}
|
|
824
|
-
const repoRelativePath = toProjectDisplayPath(resolvedCheckoutPath, resolvedCandidate);
|
|
825
|
-
assertAllowedProjectPath(repoRelativePath === "." ? "" : repoRelativePath, input.inputPath);
|
|
826
|
-
return {
|
|
827
|
-
type: "resolve_managed_path",
|
|
828
|
-
resolved: {
|
|
829
|
-
projectId: manifest.projectId,
|
|
830
|
-
projectPath: manifest.projectPath,
|
|
831
|
-
branchName,
|
|
832
|
-
checkoutPath,
|
|
833
|
-
inputPath: input.inputPath,
|
|
834
|
-
absolutePath: candidate,
|
|
835
|
-
repoRelativePath: repoRelativePath === "." ? "" : repoRelativePath
|
|
836
|
-
}
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
return { type: "resolve_managed_path", resolved: null };
|
|
841
|
-
}
|
|
842
|
-
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
843
|
-
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
844
|
-
return remoteUrl;
|
|
845
|
-
}
|
|
846
|
-
return new URL(remoteUrl, baseUrl).toString();
|
|
847
|
-
}
|
|
848
724
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
849
725
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
850
726
|
}
|
|
851
|
-
function internalGitAuth(baseUrl, token) {
|
|
852
|
-
return {
|
|
853
|
-
extraHeaderUrl: baseUrl,
|
|
854
|
-
header: `Authorization: Bearer ${token}`
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
function internalRemoteUrlFor(baseUrl, projectId, manifest) {
|
|
858
|
-
return resolveRemoteUrl(baseUrl, manifest?.internalRemoteUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId));
|
|
859
|
-
}
|
|
860
727
|
function githubRemoteUrlFor(baseUrl, projectId, manifest) {
|
|
861
|
-
return manifest?.repoHttpUrl ??
|
|
728
|
+
return manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId);
|
|
862
729
|
}
|
|
863
730
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
864
731
|
try {
|
|
@@ -876,12 +743,6 @@ function gitAuthForRemote(remoteUrl, authHeader) {
|
|
|
876
743
|
header: authHeader
|
|
877
744
|
};
|
|
878
745
|
}
|
|
879
|
-
function branchGitDirName(branchName) {
|
|
880
|
-
return `${encodeURIComponent(branchName)}.git`;
|
|
881
|
-
}
|
|
882
|
-
function internalGitDirFor(syncRoot, projectId, branchName) {
|
|
883
|
-
return import_node_path.default.join(syncRoot, projectId, branchGitDirName(branchName));
|
|
884
|
-
}
|
|
885
746
|
function hasNormalVisibleGitDir(branchPath) {
|
|
886
747
|
const gitPath = import_node_path.default.join(branchPath, ".git");
|
|
887
748
|
return import_node_fs.default.existsSync(gitPath) && import_node_fs.default.statSync(gitPath).isDirectory();
|
|
@@ -893,13 +754,6 @@ function ensureOriginRemote(branchPath, remoteUrl) {
|
|
|
893
754
|
runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
894
755
|
}
|
|
895
756
|
}
|
|
896
|
-
async function ensureOriginRemoteAsync(branchPath, remoteUrl) {
|
|
897
|
-
if (await tryGitAsync(["remote", "get-url", "origin"], { cwd: branchPath })) {
|
|
898
|
-
await runGitAsync(["remote", "set-url", "origin", remoteUrl], { cwd: branchPath });
|
|
899
|
-
} else {
|
|
900
|
-
await runGitAsync(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
757
|
function shellQuote(value) {
|
|
904
758
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
905
759
|
}
|
|
@@ -1095,217 +949,20 @@ function ensureVisibleGitCheckout(input) {
|
|
|
1095
949
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1096
950
|
return branchPath;
|
|
1097
951
|
}
|
|
1098
|
-
function configureInternalRepo(context, remoteUrl) {
|
|
1099
|
-
if (tryInternalGit(context, ["remote", "get-url", R5D_REMOTE_NAME])) {
|
|
1100
|
-
runInternalGit(context, ["remote", "set-url", R5D_REMOTE_NAME, remoteUrl]);
|
|
1101
|
-
} else {
|
|
1102
|
-
runInternalGit(context, ["remote", "add", R5D_REMOTE_NAME, remoteUrl]);
|
|
1103
|
-
}
|
|
1104
|
-
if (tryInternalGit(context, ["remote", "get-url", LEGACY_BUILD_IT_NOW_REMOTE_NAME])) {
|
|
1105
|
-
runInternalGit(context, ["remote", "remove", LEGACY_BUILD_IT_NOW_REMOTE_NAME]);
|
|
1106
|
-
}
|
|
1107
|
-
runInternalGitDir(context, ["config", "user.name", "r5d.dev Worker"]);
|
|
1108
|
-
runInternalGitDir(context, ["config", "user.email", "worker@r5d.dev"]);
|
|
1109
|
-
}
|
|
1110
|
-
function ensureInternalSyncGit(input) {
|
|
1111
|
-
validateBranchName(input.branchName);
|
|
1112
|
-
const gitDir = internalGitDirFor(input.syncRoot, input.projectId, input.branchName);
|
|
1113
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(gitDir), { recursive: true });
|
|
1114
|
-
if (!import_node_fs.default.existsSync(gitDir)) {
|
|
1115
|
-
runGit(["init", "--bare", gitDir]);
|
|
1116
|
-
}
|
|
1117
|
-
const auth = internalGitAuth(input.baseUrl, input.token);
|
|
1118
|
-
const context = { gitDir, workTree: input.branchPath, auth };
|
|
1119
|
-
const remoteUrl = internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1120
|
-
configureInternalRepo(context, remoteUrl);
|
|
1121
|
-
runInternalGit(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1122
|
-
const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`]);
|
|
1123
|
-
const target = hasRemoteBranch ? `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}` : `refs/remotes/${R5D_REMOTE_NAME}/main`;
|
|
1124
|
-
const hasHead = tryInternalGit(context, ["rev-parse", "--verify", "HEAD"]);
|
|
1125
|
-
if (!hasHead || !hasInternalWorktreeChanges(context)) {
|
|
1126
|
-
runInternalGit(context, ["checkout", "-f", "-B", input.branchName, target]);
|
|
1127
|
-
}
|
|
1128
|
-
return context;
|
|
1129
|
-
}
|
|
1130
952
|
function ensureBranchWorkspace(input) {
|
|
1131
953
|
const defaultBranch = input.manifest?.defaultBranch || "main";
|
|
1132
954
|
const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1133
955
|
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
|
|
1134
|
-
const branchPath = ensureVisibleGitCheckout({
|
|
1135
|
-
projectRoot: input.projectRoot,
|
|
1136
|
-
branchName: input.branchName,
|
|
1137
|
-
githubRemoteUrl,
|
|
1138
|
-
githubAuth,
|
|
1139
|
-
githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
|
|
1140
|
-
defaultBranch,
|
|
1141
|
-
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
|
|
1142
|
-
});
|
|
1143
|
-
const internalGit = ensureInternalSyncGit({
|
|
1144
|
-
projectId: input.projectId,
|
|
1145
|
-
baseUrl: input.baseUrl,
|
|
1146
|
-
token: input.token,
|
|
1147
|
-
syncRoot: input.syncRoot,
|
|
1148
|
-
branchPath,
|
|
1149
|
-
branchName: input.branchName,
|
|
1150
|
-
manifest: input.manifest
|
|
1151
|
-
});
|
|
1152
|
-
return { branchPath, internalGit };
|
|
1153
|
-
}
|
|
1154
|
-
async function ensureVisibleGitCheckoutForRepositorySync(input) {
|
|
1155
|
-
validateBranchName(input.branchName);
|
|
1156
|
-
import_node_fs.default.mkdirSync(input.projectRoot, { recursive: true });
|
|
1157
|
-
const branchPath = import_node_path.default.join(input.projectRoot, input.branchName);
|
|
1158
|
-
const createdCheckout = !hasNormalVisibleGitDir(branchPath);
|
|
1159
|
-
if (createdCheckout) {
|
|
1160
|
-
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1161
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(branchPath), { recursive: true });
|
|
1162
|
-
if (!await tryGitAsync(["clone", "--origin", "origin", input.githubRemoteUrl, branchPath], { auth: input.githubAuth })) {
|
|
1163
|
-
import_node_fs.default.mkdirSync(branchPath, { recursive: true });
|
|
1164
|
-
await runGitAsync(["init"], { cwd: branchPath });
|
|
1165
|
-
}
|
|
1166
|
-
await ensureOriginRemoteAsync(branchPath, input.githubRemoteUrl);
|
|
1167
|
-
configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
|
|
1168
|
-
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1169
|
-
await tryGitAsync(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
|
|
1170
|
-
checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
|
|
1171
|
-
return branchPath;
|
|
1172
|
-
}
|
|
1173
|
-
if (input.reconcileExistingOrigin) {
|
|
1174
|
-
await ensureOriginRemoteAsync(branchPath, input.githubRemoteUrl);
|
|
1175
|
-
}
|
|
1176
|
-
configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
|
|
1177
|
-
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1178
|
-
return branchPath;
|
|
1179
|
-
}
|
|
1180
|
-
async function forceSyncManifestBranchFromInternal(input) {
|
|
1181
|
-
const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
|
|
1182
|
-
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest.repoAuthHeader);
|
|
1183
|
-
const branchPath = await ensureVisibleGitCheckoutForRepositorySync({
|
|
1184
|
-
projectRoot: input.projectRoot,
|
|
1185
|
-
branchName: input.branchName,
|
|
1186
|
-
githubRemoteUrl,
|
|
1187
|
-
githubAuth,
|
|
1188
|
-
githubAuthHeader: input.manifest.repoAuthHeader,
|
|
1189
|
-
defaultBranch: input.manifest.defaultBranch || "main",
|
|
1190
|
-
reconcileExistingOrigin: Boolean(input.manifest.repoHttpUrl)
|
|
1191
|
-
});
|
|
1192
|
-
const gitDir = internalGitDirFor(input.syncRoot, input.projectId, input.branchName);
|
|
1193
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(gitDir), { recursive: true });
|
|
1194
|
-
if (!import_node_fs.default.existsSync(gitDir)) {
|
|
1195
|
-
await runGitAsync(["init", "--bare", gitDir]);
|
|
1196
|
-
}
|
|
1197
|
-
const context = {
|
|
1198
|
-
gitDir,
|
|
1199
|
-
workTree: branchPath,
|
|
1200
|
-
auth: internalGitAuth(input.baseUrl, input.token)
|
|
1201
|
-
};
|
|
1202
|
-
configureInternalRepo(context, internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest));
|
|
1203
|
-
await runInternalGitAsync(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1204
|
-
const branchTarget = `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`;
|
|
1205
|
-
const mainTarget = `refs/remotes/${R5D_REMOTE_NAME}/main`;
|
|
1206
|
-
const target = tryInternalGit(context, ["show-ref", "--verify", "--quiet", branchTarget]) ? branchTarget : tryInternalGit(context, ["show-ref", "--verify", "--quiet", mainTarget]) ? mainTarget : null;
|
|
1207
|
-
if (!target) {
|
|
1208
|
-
throw new Error(`Internal remote has neither ${input.branchName} nor main`);
|
|
1209
|
-
}
|
|
1210
|
-
if (hasInternalWorktreeChanges(context)) {
|
|
1211
|
-
return null;
|
|
1212
|
-
}
|
|
1213
|
-
await runInternalGitAsync(context, ["reset", "--hard", target]);
|
|
1214
|
-
await runInternalGitAsync(context, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1215
|
-
return getInternalCommitHash(context);
|
|
1216
|
-
}
|
|
1217
|
-
function findRepositorySyncBlockers(input) {
|
|
1218
|
-
const targetByKey = new Map(input.targets.map((target) => [`${target.projectId}:${target.branchName}`, target]));
|
|
1219
|
-
const blockedBy = [];
|
|
1220
|
-
for (const shell of input.shells) {
|
|
1221
|
-
const target = targetByKey.get(`${shell.projectId}:${shell.branchName}`);
|
|
1222
|
-
if (target) blockedBy.push({ ...target, kind: "shell", id: shell.id });
|
|
1223
|
-
}
|
|
1224
|
-
return blockedBy;
|
|
1225
|
-
}
|
|
1226
|
-
function selectManifestSyncTargets(input) {
|
|
1227
|
-
if (!input.requestedTargets) {
|
|
1228
|
-
return input.manifests.flatMap((manifest) => {
|
|
1229
|
-
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
1230
|
-
return [...new Set(branches)].map((branchName) => ({ manifest, branchName }));
|
|
1231
|
-
});
|
|
1232
|
-
}
|
|
1233
|
-
const manifestsByProjectId = new Map(input.manifests.map((manifest) => [manifest.projectId, manifest]));
|
|
1234
|
-
const selected = /* @__PURE__ */ new Map();
|
|
1235
|
-
for (const target of input.requestedTargets) {
|
|
1236
|
-
const manifest = manifestsByProjectId.get(target.projectId);
|
|
1237
|
-
if (!manifest) {
|
|
1238
|
-
continue;
|
|
1239
|
-
}
|
|
1240
|
-
selected.set(`${target.projectId}\0${target.branchName}`, { manifest, branchName: target.branchName });
|
|
1241
|
-
}
|
|
1242
|
-
return [...selected.values()];
|
|
1243
|
-
}
|
|
1244
|
-
async function syncManifestProjectsFromInternal(input) {
|
|
1245
|
-
const targets = selectManifestSyncTargets({
|
|
1246
|
-
manifests: input.manifests,
|
|
1247
|
-
requestedTargets: input.requestedTargets
|
|
1248
|
-
});
|
|
1249
|
-
const blockedBy = findRepositorySyncBlockers({
|
|
1250
|
-
targets: targets.map(({ manifest, branchName }) => ({
|
|
1251
|
-
projectId: manifest.projectId,
|
|
1252
|
-
projectPath: manifest.projectPath,
|
|
1253
|
-
branchName
|
|
1254
|
-
})),
|
|
1255
|
-
shells: [...activePtys].map(([id, active]) => ({
|
|
1256
|
-
id,
|
|
1257
|
-
projectId: active.projectId,
|
|
1258
|
-
branchName: active.branchName
|
|
1259
|
-
}))
|
|
1260
|
-
});
|
|
1261
|
-
const blockedTargetKeys = new Set(blockedBy.map((blocker) => `${blocker.projectId}\0${blocker.branchName}`));
|
|
1262
|
-
const results = [];
|
|
1263
|
-
for (const { manifest, branchName } of targets) {
|
|
1264
|
-
if (blockedTargetKeys.has(`${manifest.projectId}\0${branchName}`)) {
|
|
1265
|
-
continue;
|
|
1266
|
-
}
|
|
1267
|
-
try {
|
|
1268
|
-
const commitHash = await forceSyncManifestBranchFromInternal({
|
|
1269
|
-
projectId: manifest.projectId,
|
|
1270
|
-
baseUrl: input.baseUrl,
|
|
1271
|
-
token: input.token,
|
|
1272
|
-
projectRoot: import_node_path.default.join(input.projectsRoot, manifest.repoSlug || manifest.projectId),
|
|
1273
|
-
syncRoot: input.syncRoot,
|
|
1274
|
-
branchName,
|
|
1275
|
-
manifest
|
|
1276
|
-
});
|
|
1277
|
-
if (commitHash === null) {
|
|
1278
|
-
blockedBy.push({
|
|
1279
|
-
projectId: manifest.projectId,
|
|
1280
|
-
projectPath: manifest.projectPath,
|
|
1281
|
-
branchName,
|
|
1282
|
-
kind: "dirty_checkout",
|
|
1283
|
-
id: `${manifest.projectId}:${branchName}`
|
|
1284
|
-
});
|
|
1285
|
-
continue;
|
|
1286
|
-
}
|
|
1287
|
-
results.push({
|
|
1288
|
-
projectId: manifest.projectId,
|
|
1289
|
-
projectPath: manifest.projectPath,
|
|
1290
|
-
branchName,
|
|
1291
|
-
status: "synced",
|
|
1292
|
-
commitHash
|
|
1293
|
-
});
|
|
1294
|
-
} catch (error) {
|
|
1295
|
-
results.push({
|
|
1296
|
-
projectId: manifest.projectId,
|
|
1297
|
-
projectPath: manifest.projectPath,
|
|
1298
|
-
branchName,
|
|
1299
|
-
status: "failed",
|
|
1300
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
const hasFailures = results.some((result) => result.status === "failed");
|
|
1305
956
|
return {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
957
|
+
branchPath: ensureVisibleGitCheckout({
|
|
958
|
+
projectRoot: input.projectRoot,
|
|
959
|
+
branchName: input.branchName,
|
|
960
|
+
githubRemoteUrl,
|
|
961
|
+
githubAuth,
|
|
962
|
+
githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
|
|
963
|
+
defaultBranch,
|
|
964
|
+
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
|
|
965
|
+
})
|
|
1309
966
|
};
|
|
1310
967
|
}
|
|
1311
968
|
function resolveCommandCwd(branchPath, cwd) {
|
|
@@ -1356,69 +1013,6 @@ async function streamCommandOutput(stream, onData) {
|
|
|
1356
1013
|
function ensureOperationBranch(input) {
|
|
1357
1014
|
return ensureBranchWorkspace(input);
|
|
1358
1015
|
}
|
|
1359
|
-
async function inspectManagedWorkspace(input) {
|
|
1360
|
-
const candidates = input.manifests.flatMap((manifest) => {
|
|
1361
|
-
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
1362
|
-
return branches.map((branchName) => ({
|
|
1363
|
-
manifest,
|
|
1364
|
-
branchName,
|
|
1365
|
-
checkoutPath: import_node_path.default.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName)
|
|
1366
|
-
}));
|
|
1367
|
-
});
|
|
1368
|
-
const inspected = await Promise.all(
|
|
1369
|
-
candidates.map(async ({ manifest, branchName, checkoutPath }) => {
|
|
1370
|
-
const gitDir = internalGitDirFor(input.syncRoot, manifest.projectId, branchName);
|
|
1371
|
-
if (!hasNormalVisibleGitDir(checkoutPath) || !import_node_fs.default.existsSync(gitDir)) return null;
|
|
1372
|
-
const context = {
|
|
1373
|
-
gitDir,
|
|
1374
|
-
workTree: checkoutPath,
|
|
1375
|
-
auth: internalGitAuth(input.baseUrl, input.token)
|
|
1376
|
-
};
|
|
1377
|
-
try {
|
|
1378
|
-
const status = await runInternalGitAsync(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
|
|
1379
|
-
let commitHash = null;
|
|
1380
|
-
for (const revision of [
|
|
1381
|
-
"HEAD",
|
|
1382
|
-
`refs/remotes/${R5D_REMOTE_NAME}/${branchName}`,
|
|
1383
|
-
`refs/remotes/${R5D_REMOTE_NAME}/main`
|
|
1384
|
-
]) {
|
|
1385
|
-
try {
|
|
1386
|
-
commitHash = await runInternalGitAsync(context, ["rev-parse", "--verify", revision]);
|
|
1387
|
-
break;
|
|
1388
|
-
} catch {
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
if (!commitHash) return null;
|
|
1392
|
-
return {
|
|
1393
|
-
projectId: manifest.projectId,
|
|
1394
|
-
projectPath: manifest.projectPath,
|
|
1395
|
-
branchName,
|
|
1396
|
-
checkoutPath,
|
|
1397
|
-
commitHash,
|
|
1398
|
-
status,
|
|
1399
|
-
dirty: status.trim().length > 0
|
|
1400
|
-
};
|
|
1401
|
-
} catch {
|
|
1402
|
-
return null;
|
|
1403
|
-
}
|
|
1404
|
-
})
|
|
1405
|
-
);
|
|
1406
|
-
return {
|
|
1407
|
-
type: "workspace_status",
|
|
1408
|
-
checkouts: inspected.filter((checkout) => checkout !== null).sort(
|
|
1409
|
-
(left, right) => left.projectPath.localeCompare(right.projectPath) || left.branchName.localeCompare(right.branchName)
|
|
1410
|
-
)
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
function internalGitProcessEnv(workspace, env) {
|
|
1414
|
-
if (env?.R5D_USE_INTERNAL_GIT !== "1") {
|
|
1415
|
-
return {};
|
|
1416
|
-
}
|
|
1417
|
-
return {
|
|
1418
|
-
GIT_DIR: workspace.internalGit.gitDir,
|
|
1419
|
-
GIT_WORK_TREE: workspace.branchPath
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
1016
|
function formatLineNumberedContent(content, offset, limit) {
|
|
1423
1017
|
const allLines = content.split("\n");
|
|
1424
1018
|
const totalLines = allLines.length;
|
|
@@ -1527,9 +1121,11 @@ const fileMutationQueues = /* @__PURE__ */ new Map();
|
|
|
1527
1121
|
async function withFileMutationQueue(key, operation) {
|
|
1528
1122
|
const previous = fileMutationQueues.get(key) ?? Promise.resolve();
|
|
1529
1123
|
let release;
|
|
1530
|
-
const current = previous.catch(() => void 0).then(
|
|
1531
|
-
|
|
1532
|
-
|
|
1124
|
+
const current = previous.catch(() => void 0).then(
|
|
1125
|
+
() => new Promise((resolve) => {
|
|
1126
|
+
release = resolve;
|
|
1127
|
+
})
|
|
1128
|
+
);
|
|
1533
1129
|
fileMutationQueues.set(key, current);
|
|
1534
1130
|
await previous.catch(() => void 0);
|
|
1535
1131
|
try {
|
|
@@ -1624,7 +1220,9 @@ async function executeEditFileOperation(input) {
|
|
|
1624
1220
|
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1625
1221
|
}
|
|
1626
1222
|
if (occurrences > 1) {
|
|
1627
|
-
throw new Error(
|
|
1223
|
+
throw new Error(
|
|
1224
|
+
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1225
|
+
);
|
|
1628
1226
|
}
|
|
1629
1227
|
const start = originalContent.indexOf(edit.oldText);
|
|
1630
1228
|
return { index, start, end: start + edit.oldText.length, edit };
|
|
@@ -1884,114 +1482,8 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1884
1482
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1885
1483
|
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1886
1484
|
}
|
|
1887
|
-
function stagedBlobSizeBytes(context) {
|
|
1888
|
-
const names = runInternalGit(context, ["diff", "--cached", "--name-only", "-z", "--", ".", ":(exclude).git"]).split("\0").filter(Boolean);
|
|
1889
|
-
let total = 0;
|
|
1890
|
-
for (const name of names) {
|
|
1891
|
-
try {
|
|
1892
|
-
const sizeText = runInternalGit(context, ["cat-file", "-s", `:${name}`]);
|
|
1893
|
-
total += Number.parseInt(sizeText, 10) || 0;
|
|
1894
|
-
} catch {
|
|
1895
|
-
}
|
|
1896
|
-
if (total > MAX_SYNC_DIFF_BYTES) {
|
|
1897
|
-
return MAX_SYNC_DIFF_BYTES + 1;
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
return total;
|
|
1901
|
-
}
|
|
1902
|
-
function syncBranch(input) {
|
|
1903
|
-
const workspace = ensureOperationBranch(input);
|
|
1904
|
-
runInternalGit(workspace.internalGit, ["add", "-A", "--", ".", ":(exclude).git"]);
|
|
1905
|
-
const hasStagedChanges = !tryInternalGit(workspace.internalGit, ["diff", "--cached", "--quiet", "--", ".", ":(exclude).git"]);
|
|
1906
|
-
const status = getInternalStatus(workspace.internalGit);
|
|
1907
|
-
const currentCommit = getInternalCommitHash(workspace.internalGit);
|
|
1908
|
-
if (!hasStagedChanges) {
|
|
1909
|
-
return {
|
|
1910
|
-
type: "sync",
|
|
1911
|
-
changed: false,
|
|
1912
|
-
commitHash: currentCommit,
|
|
1913
|
-
diffSizeBytes: 0,
|
|
1914
|
-
status
|
|
1915
|
-
};
|
|
1916
|
-
}
|
|
1917
|
-
const diffSizeBytes = stagedBlobSizeBytes(workspace.internalGit);
|
|
1918
|
-
if (diffSizeBytes > MAX_SYNC_DIFF_BYTES && !input.confirmedLargeDiff) {
|
|
1919
|
-
return {
|
|
1920
|
-
type: "sync",
|
|
1921
|
-
changed: true,
|
|
1922
|
-
commitHash: currentCommit,
|
|
1923
|
-
diffSizeBytes,
|
|
1924
|
-
status,
|
|
1925
|
-
largeDiffBlocked: true,
|
|
1926
|
-
trigger: input.trigger
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
const message = JSON.stringify({
|
|
1930
|
-
type: "agent_changes",
|
|
1931
|
-
sessionId: input.sessionId,
|
|
1932
|
-
parentNodeId: input.parentNodeId,
|
|
1933
|
-
...input.confirmedLargeDiff ? { confirmedLargeDiff: true, confirmationReason: input.confirmationReason } : {}
|
|
1934
|
-
});
|
|
1935
|
-
runInternalGit(workspace.internalGit, ["commit", "-m", message]);
|
|
1936
|
-
const commitHash = getInternalCommitHash(workspace.internalGit);
|
|
1937
|
-
runInternalGit(workspace.internalGit, ["push", R5D_REMOTE_NAME, `HEAD:refs/heads/${input.branchName}`]);
|
|
1938
|
-
return {
|
|
1939
|
-
type: "sync",
|
|
1940
|
-
changed: true,
|
|
1941
|
-
commitHash,
|
|
1942
|
-
diffSizeBytes,
|
|
1943
|
-
status: getInternalStatus(workspace.internalGit),
|
|
1944
|
-
trigger: input.trigger
|
|
1945
|
-
};
|
|
1946
|
-
}
|
|
1947
|
-
function confirmLargeDiff(input) {
|
|
1948
|
-
const reason = input.reason.trim();
|
|
1949
|
-
if (!reason) {
|
|
1950
|
-
throw new Error("confirm_large_diff requires a non-empty reason");
|
|
1951
|
-
}
|
|
1952
|
-
const result = syncBranch({
|
|
1953
|
-
...input,
|
|
1954
|
-
confirmedLargeDiff: true,
|
|
1955
|
-
confirmationReason: reason
|
|
1956
|
-
});
|
|
1957
|
-
return {
|
|
1958
|
-
type: "confirm_large_diff",
|
|
1959
|
-
changed: result.changed,
|
|
1960
|
-
commitHash: result.commitHash,
|
|
1961
|
-
diffSizeBytes: result.diffSizeBytes,
|
|
1962
|
-
status: result.status,
|
|
1963
|
-
reason
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
function pullBranch(input) {
|
|
1967
|
-
const workspace = ensureOperationBranch(input);
|
|
1968
|
-
runInternalGit(workspace.internalGit, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
|
|
1969
|
-
const target = input.commitHash ?? `${R5D_REMOTE_NAME}/${input.branchName}`;
|
|
1970
|
-
runInternalGit(workspace.internalGit, ["reset", "--hard", target]);
|
|
1971
|
-
runInternalGit(workspace.internalGit, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1972
|
-
return {
|
|
1973
|
-
type: "pull_branch",
|
|
1974
|
-
commitHash: getInternalCommitHash(workspace.internalGit)
|
|
1975
|
-
};
|
|
1976
|
-
}
|
|
1977
1485
|
async function executeOperation(input) {
|
|
1978
1486
|
switch (input.message.type) {
|
|
1979
|
-
case "resolve_managed_path":
|
|
1980
|
-
return resolveManagedCheckoutPath({
|
|
1981
|
-
projectsRoot: input.projectsRoot,
|
|
1982
|
-
manifests: input.manifests,
|
|
1983
|
-
originProjectId: input.message.projectId,
|
|
1984
|
-
originBranchName: input.message.branchName,
|
|
1985
|
-
inputPath: input.message.inputPath
|
|
1986
|
-
});
|
|
1987
|
-
case "workspace_status":
|
|
1988
|
-
return await inspectManagedWorkspace({
|
|
1989
|
-
baseUrl: input.baseUrl,
|
|
1990
|
-
token: input.token,
|
|
1991
|
-
projectsRoot: input.projectsRoot,
|
|
1992
|
-
syncRoot: input.syncRoot,
|
|
1993
|
-
manifests: input.manifests
|
|
1994
|
-
});
|
|
1995
1487
|
case "read":
|
|
1996
1488
|
return executeReadFileOperation({ ...input, message: input.message });
|
|
1997
1489
|
case "write":
|
|
@@ -2006,43 +1498,6 @@ async function executeOperation(input) {
|
|
|
2006
1498
|
return executeLsOperation({ ...input, message: input.message });
|
|
2007
1499
|
case "view_file_bytes":
|
|
2008
1500
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
2009
|
-
case "sync":
|
|
2010
|
-
return syncBranch({
|
|
2011
|
-
projectId: input.projectId,
|
|
2012
|
-
baseUrl: input.baseUrl,
|
|
2013
|
-
token: input.token,
|
|
2014
|
-
projectRoot: input.projectRoot,
|
|
2015
|
-
syncRoot: input.syncRoot,
|
|
2016
|
-
branchName: input.message.branchName,
|
|
2017
|
-
sessionId: input.message.sessionId,
|
|
2018
|
-
parentNodeId: input.message.parentNodeId,
|
|
2019
|
-
trigger: input.message.trigger,
|
|
2020
|
-
manifest: input.manifest
|
|
2021
|
-
});
|
|
2022
|
-
case "confirm_large_diff":
|
|
2023
|
-
return confirmLargeDiff({
|
|
2024
|
-
projectId: input.projectId,
|
|
2025
|
-
baseUrl: input.baseUrl,
|
|
2026
|
-
token: input.token,
|
|
2027
|
-
projectRoot: input.projectRoot,
|
|
2028
|
-
syncRoot: input.syncRoot,
|
|
2029
|
-
branchName: input.message.branchName,
|
|
2030
|
-
sessionId: input.message.sessionId,
|
|
2031
|
-
parentNodeId: input.message.parentNodeId,
|
|
2032
|
-
reason: input.message.reason,
|
|
2033
|
-
manifest: input.manifest
|
|
2034
|
-
});
|
|
2035
|
-
case "pull_branch":
|
|
2036
|
-
return pullBranch({
|
|
2037
|
-
projectId: input.projectId,
|
|
2038
|
-
baseUrl: input.baseUrl,
|
|
2039
|
-
token: input.token,
|
|
2040
|
-
projectRoot: input.projectRoot,
|
|
2041
|
-
syncRoot: input.syncRoot,
|
|
2042
|
-
branchName: input.message.branchName,
|
|
2043
|
-
commitHash: input.message.commitHash,
|
|
2044
|
-
manifest: input.manifest
|
|
2045
|
-
});
|
|
2046
1501
|
}
|
|
2047
1502
|
}
|
|
2048
1503
|
async function executeCommand(input) {
|
|
@@ -2074,7 +1529,11 @@ async function executeCommand(input) {
|
|
|
2074
1529
|
planRoot: input.planRoot,
|
|
2075
1530
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2076
1531
|
});
|
|
2077
|
-
const
|
|
1532
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1533
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
|
|
1534
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1535
|
+
}
|
|
1536
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
2078
1537
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
2079
1538
|
cwd,
|
|
2080
1539
|
stdout: "pipe",
|
|
@@ -2084,7 +1543,6 @@ async function executeCommand(input) {
|
|
|
2084
1543
|
...process.env,
|
|
2085
1544
|
...githubProcessEnv(),
|
|
2086
1545
|
...input.message.env ?? {},
|
|
2087
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
2088
1546
|
...artifactProcessEnv,
|
|
2089
1547
|
...planProcessEnv
|
|
2090
1548
|
}
|
|
@@ -2095,7 +1553,8 @@ async function executeCommand(input) {
|
|
|
2095
1553
|
sessionId: input.message.sessionId ?? "",
|
|
2096
1554
|
branchName: input.message.branchName,
|
|
2097
1555
|
mode: "foreground",
|
|
2098
|
-
|
|
1556
|
+
pid: subprocess.pid,
|
|
1557
|
+
processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
|
|
2099
1558
|
argv: input.message.argv,
|
|
2100
1559
|
command: input.message.argv.join(" "),
|
|
2101
1560
|
cwd,
|
|
@@ -2181,7 +1640,11 @@ async function executeStreamingCommand(input) {
|
|
|
2181
1640
|
planRoot: input.planRoot,
|
|
2182
1641
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2183
1642
|
});
|
|
2184
|
-
const
|
|
1643
|
+
const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
|
|
1644
|
+
if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
|
|
1645
|
+
throw new Error("The canonical workspace checkout is not initialized on this worker");
|
|
1646
|
+
}
|
|
1647
|
+
const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
|
|
2185
1648
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
2186
1649
|
cwd,
|
|
2187
1650
|
stdout: "pipe",
|
|
@@ -2191,7 +1654,6 @@ async function executeStreamingCommand(input) {
|
|
|
2191
1654
|
...process.env,
|
|
2192
1655
|
...githubProcessEnv(),
|
|
2193
1656
|
...input.message.env ?? {},
|
|
2194
|
-
...internalGitProcessEnv(workspace, input.message.env),
|
|
2195
1657
|
...artifactProcessEnv,
|
|
2196
1658
|
...planProcessEnv
|
|
2197
1659
|
}
|
|
@@ -2202,16 +1664,11 @@ async function executeStreamingCommand(input) {
|
|
|
2202
1664
|
sessionId: input.message.sessionId,
|
|
2203
1665
|
branchName: input.message.branchName,
|
|
2204
1666
|
mode: input.message.mode,
|
|
2205
|
-
startCommitHash: input.message.startCommitHash,
|
|
2206
|
-
launchWorkspaceActionId: input.message.launchWorkspaceActionId,
|
|
2207
|
-
credentialId: input.message.credentialId,
|
|
2208
1667
|
pid: subprocess.pid,
|
|
2209
|
-
processGroupId: subprocess.pid,
|
|
1668
|
+
processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
|
|
1669
|
+
credentialId: input.message.credentialId,
|
|
2210
1670
|
argv: input.message.argv,
|
|
2211
1671
|
command: input.message.command,
|
|
2212
|
-
// Persist the resolved absolute cwd so reconnect reports and process
|
|
2213
|
-
// history retain the actual execution location, including cross-project
|
|
2214
|
-
// managed paths.
|
|
2215
1672
|
cwd,
|
|
2216
1673
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2217
1674
|
});
|
|
@@ -2222,7 +1679,7 @@ async function executeStreamingCommand(input) {
|
|
|
2222
1679
|
runId: input.message.runId,
|
|
2223
1680
|
cwd,
|
|
2224
1681
|
pid: subprocess.pid,
|
|
2225
|
-
processGroupId: subprocess.pid
|
|
1682
|
+
...process.platform === "win32" ? {} : { processGroupId: subprocess.pid }
|
|
2226
1683
|
});
|
|
2227
1684
|
if (input.message.timeoutMs) {
|
|
2228
1685
|
timeout = setTimeout(() => {
|
|
@@ -2254,22 +1711,26 @@ async function executeStreamingCommand(input) {
|
|
|
2254
1711
|
});
|
|
2255
1712
|
})
|
|
2256
1713
|
]);
|
|
2257
|
-
|
|
1714
|
+
const terminal = {
|
|
2258
1715
|
type: "exec_exit",
|
|
2259
1716
|
runId: input.message.runId,
|
|
2260
1717
|
exitCode,
|
|
2261
1718
|
durationMs: Date.now() - startedAt,
|
|
2262
1719
|
...timedOut ? { timedOut: true } : {}
|
|
2263
|
-
}
|
|
1720
|
+
};
|
|
1721
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1722
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2264
1723
|
} catch (error) {
|
|
2265
1724
|
const message = error instanceof Error ? error.message : String(error);
|
|
2266
1725
|
if (started) {
|
|
2267
|
-
|
|
1726
|
+
const terminal = {
|
|
2268
1727
|
type: "exec_error",
|
|
2269
1728
|
runId: input.message.runId,
|
|
2270
1729
|
error: message,
|
|
2271
1730
|
durationMs: Date.now() - startedAt
|
|
2272
|
-
}
|
|
1731
|
+
};
|
|
1732
|
+
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
1733
|
+
sendWorkerMessage(input.ws, terminal);
|
|
2273
1734
|
} else {
|
|
2274
1735
|
sendWorkerMessage(input.ws, {
|
|
2275
1736
|
type: "exec_start_error",
|
|
@@ -2301,12 +1762,12 @@ function buildActiveProcessReports() {
|
|
|
2301
1762
|
projectId: active.projectId,
|
|
2302
1763
|
sessionId: active.sessionId,
|
|
2303
1764
|
branchName: active.branchName,
|
|
1765
|
+
platform: process.platform,
|
|
1766
|
+
arch: process.arch,
|
|
2304
1767
|
mode: active.mode,
|
|
2305
|
-
startCommitHash: active.startCommitHash,
|
|
2306
|
-
...active.launchWorkspaceActionId ? { launchWorkspaceActionId: active.launchWorkspaceActionId } : {},
|
|
2307
|
-
...active.credentialId ? { credentialId: active.credentialId } : {},
|
|
2308
1768
|
...active.pid !== void 0 ? { pid: active.pid } : {},
|
|
2309
1769
|
...active.processGroupId !== void 0 ? { processGroupId: active.processGroupId } : {},
|
|
1770
|
+
...active.credentialId ? { credentialId: active.credentialId } : {},
|
|
2310
1771
|
argv: active.argv,
|
|
2311
1772
|
command: active.command,
|
|
2312
1773
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
@@ -2607,10 +2068,7 @@ function resizePty(message) {
|
|
|
2607
2068
|
if (!ptyProcess) {
|
|
2608
2069
|
return;
|
|
2609
2070
|
}
|
|
2610
|
-
ptyProcess.resize(
|
|
2611
|
-
Math.max(1, Math.min(Math.floor(message.cols || 80), 500)),
|
|
2612
|
-
Math.max(1, Math.min(Math.floor(message.rows || 24), 500))
|
|
2613
|
-
);
|
|
2071
|
+
ptyProcess.resize(Math.max(1, Math.min(Math.floor(message.cols || 80), 500)), Math.max(1, Math.min(Math.floor(message.rows || 24), 500)));
|
|
2614
2072
|
}
|
|
2615
2073
|
function closePty(message) {
|
|
2616
2074
|
const ptyProcess = activePtys.get(message.ptyId);
|
|
@@ -2642,6 +2100,7 @@ async function startWorker(options) {
|
|
|
2642
2100
|
validateLabel(label);
|
|
2643
2101
|
const projectsRoot = import_node_path.default.resolve(options.projectRoot ?? defaultProjectsRoot());
|
|
2644
2102
|
const syncRoot = import_node_path.default.resolve(options.syncRoot ?? process.env.R5D_SYNC_ROOT ?? defaultSyncRoot());
|
|
2103
|
+
const workspaceShadowRoot = import_node_path.default.join(syncRoot, "workspace");
|
|
2645
2104
|
const artifactRoot = import_node_path.default.resolve(options.artifactRoot ?? process.env.R5D_ARTIFACTS_ROOT ?? defaultArtifactRoot());
|
|
2646
2105
|
const planRoot = import_node_path.default.resolve(options.planRoot ?? process.env.R5D_PLANS_ROOT ?? defaultPlanRoot());
|
|
2647
2106
|
process.stdout.write(`[r5d-worker] label: ${label}
|
|
@@ -2663,10 +2122,104 @@ async function startWorker(options) {
|
|
|
2663
2122
|
import_node_fs.default.mkdirSync(artifactRoot, { recursive: true });
|
|
2664
2123
|
import_node_fs.default.mkdirSync(planRoot, { recursive: true });
|
|
2665
2124
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
2666
|
-
|
|
2667
|
-
let
|
|
2125
|
+
const workspaceSyncSingleFlight = new import_workspace_sync.WorkspaceSyncSingleFlight();
|
|
2126
|
+
let workspaceRemoteUrl = null;
|
|
2127
|
+
let activeWorkspaceIncidentId = null;
|
|
2128
|
+
let previousPeriodicFingerprint = null;
|
|
2129
|
+
let periodicWorkspaceScan;
|
|
2130
|
+
let periodicWorkspaceScanInFlight = false;
|
|
2131
|
+
let terminalReplayTimer;
|
|
2132
|
+
let workspaceSyncRequestsInFlight = 0;
|
|
2668
2133
|
let cliUpdateInProgress = false;
|
|
2669
2134
|
let reloadAfterClose = false;
|
|
2135
|
+
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2136
|
+
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2137
|
+
return {
|
|
2138
|
+
workerLabel: label,
|
|
2139
|
+
remoteUrl: workspaceRemoteUrl,
|
|
2140
|
+
authHeader: `Authorization: Bearer ${token}`,
|
|
2141
|
+
projectsRoot,
|
|
2142
|
+
plansRoot: planRoot,
|
|
2143
|
+
shadowRoot: workspaceShadowRoot,
|
|
2144
|
+
projects: [...manifestByProjectId.values()],
|
|
2145
|
+
trigger,
|
|
2146
|
+
...overrides
|
|
2147
|
+
};
|
|
2148
|
+
};
|
|
2149
|
+
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2150
|
+
sendWorkerMessage(ws, { type: "workspace_sync_result", requestId, result });
|
|
2151
|
+
};
|
|
2152
|
+
const ensureAllVisibleWorkspaceCheckouts = () => {
|
|
2153
|
+
const created = [];
|
|
2154
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2155
|
+
for (const branchName of manifest.branches) {
|
|
2156
|
+
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId), branchName);
|
|
2157
|
+
const existed = import_node_fs.default.existsSync(import_node_path.default.join(branchPath, ".git"));
|
|
2158
|
+
const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
|
|
2159
|
+
ensureVisibleGitCheckout({
|
|
2160
|
+
projectRoot: projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId),
|
|
2161
|
+
branchName,
|
|
2162
|
+
githubRemoteUrl,
|
|
2163
|
+
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2164
|
+
githubAuthHeader: manifest.repoAuthHeader,
|
|
2165
|
+
defaultBranch: manifest.defaultBranch || "main",
|
|
2166
|
+
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2167
|
+
});
|
|
2168
|
+
if (!existed) created.push({ projectId: manifest.projectId, branchName });
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return created;
|
|
2172
|
+
};
|
|
2173
|
+
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2174
|
+
workspaceSyncRequestsInFlight += 1;
|
|
2175
|
+
try {
|
|
2176
|
+
if (trigger.type === "plan_write" || trigger.type === "task_status" || trigger.type === "connect" || trigger.type === "inbound_head" || trigger.type === "manual") {
|
|
2177
|
+
for (const manifest of manifestByProjectId.values()) {
|
|
2178
|
+
for (const branchName of manifest.branches) {
|
|
2179
|
+
await syncProjectPlans({
|
|
2180
|
+
baseUrl,
|
|
2181
|
+
token,
|
|
2182
|
+
projectId: manifest.projectId,
|
|
2183
|
+
branchName,
|
|
2184
|
+
planRoot
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
const newVisibleCheckouts = trigger.type === "connect" ? ensureAllVisibleWorkspaceCheckouts() : [];
|
|
2190
|
+
const result = await workspaceSyncSingleFlight.run(
|
|
2191
|
+
workspaceSyncInput(trigger, {
|
|
2192
|
+
...overrides,
|
|
2193
|
+
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2194
|
+
})
|
|
2195
|
+
);
|
|
2196
|
+
previousPeriodicFingerprint = null;
|
|
2197
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2198
|
+
return result;
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
const result = {
|
|
2201
|
+
type: "workspace_sync",
|
|
2202
|
+
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2203
|
+
workerLabel: label,
|
|
2204
|
+
trigger,
|
|
2205
|
+
outcome: "failed",
|
|
2206
|
+
startingHead: null,
|
|
2207
|
+
rebaseCount: 0,
|
|
2208
|
+
diffSizeBytes: 0,
|
|
2209
|
+
gitStatus: "",
|
|
2210
|
+
affectedProjects: [],
|
|
2211
|
+
affectedPaths: [],
|
|
2212
|
+
discardedPaths: [],
|
|
2213
|
+
localChangesDiscarded: false,
|
|
2214
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2215
|
+
};
|
|
2216
|
+
previousPeriodicFingerprint = null;
|
|
2217
|
+
sendWorkspaceSyncResult(requestId, result);
|
|
2218
|
+
return result;
|
|
2219
|
+
} finally {
|
|
2220
|
+
workspaceSyncRequestsInFlight -= 1;
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2670
2223
|
const ws = new WebSocket(websocketUrl(baseUrl, label), {
|
|
2671
2224
|
headers: {
|
|
2672
2225
|
Authorization: `Bearer ${token}`
|
|
@@ -2711,6 +2264,15 @@ async function startWorker(options) {
|
|
|
2711
2264
|
};
|
|
2712
2265
|
ws.send(JSON.stringify(hello));
|
|
2713
2266
|
sendActiveProcessReport(ws);
|
|
2267
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2268
|
+
sendWorkerMessage(ws, terminal);
|
|
2269
|
+
}
|
|
2270
|
+
terminalReplayTimer = setInterval(() => {
|
|
2271
|
+
for (const terminal of pendingProcessTerminals.values()) {
|
|
2272
|
+
sendWorkerMessage(ws, terminal);
|
|
2273
|
+
}
|
|
2274
|
+
}, 5e3);
|
|
2275
|
+
terminalReplayTimer.unref();
|
|
2714
2276
|
process.stdout.write("[r5d-worker] connected\n");
|
|
2715
2277
|
});
|
|
2716
2278
|
ws.addEventListener("message", (event) => {
|
|
@@ -2719,91 +2281,68 @@ async function startWorker(options) {
|
|
|
2719
2281
|
if (message.type === "connected") {
|
|
2720
2282
|
return;
|
|
2721
2283
|
}
|
|
2722
|
-
if (message.type === "
|
|
2284
|
+
if (message.type === "workspace_manifest") {
|
|
2723
2285
|
configureGitHubAuth(message.githubCredential);
|
|
2724
2286
|
visibleGitIdentity = message.gitIdentity;
|
|
2287
|
+
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2725
2288
|
manifestByProjectId.clear();
|
|
2726
|
-
for (const project of message.projects)
|
|
2727
|
-
|
|
2728
|
-
}
|
|
2729
|
-
process.stdout.write(`[r5d-worker] project manifest: ${message.projects.length} projects
|
|
2289
|
+
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2290
|
+
process.stdout.write(`[r5d-worker] workspace manifest: ${message.projects.length} projects
|
|
2730
2291
|
`);
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
}
|
|
2743
|
-
});
|
|
2744
|
-
return;
|
|
2745
|
-
}
|
|
2746
|
-
let syncResult = { status: "completed", results: [], blockedBy: [] };
|
|
2747
|
-
const executeSync = async () => {
|
|
2748
|
-
repositorySyncInProgress = true;
|
|
2749
|
-
try {
|
|
2750
|
-
syncResult = await syncManifestProjectsFromInternal({
|
|
2751
|
-
baseUrl,
|
|
2752
|
-
token,
|
|
2753
|
-
projectsRoot,
|
|
2754
|
-
syncRoot,
|
|
2755
|
-
manifests: [...manifestByProjectId.values()],
|
|
2756
|
-
requestedTargets: message.targets
|
|
2757
|
-
});
|
|
2758
|
-
for (const result of syncResult.results) {
|
|
2759
|
-
if (result.status !== "synced") continue;
|
|
2760
|
-
try {
|
|
2761
|
-
await syncProjectPlans({
|
|
2762
|
-
baseUrl,
|
|
2763
|
-
token,
|
|
2764
|
-
projectId: result.projectId,
|
|
2765
|
-
branchName: result.branchName,
|
|
2766
|
-
planRoot
|
|
2767
|
-
});
|
|
2768
|
-
} catch (error) {
|
|
2769
|
-
process.stderr.write(
|
|
2770
|
-
`[r5d-worker] plan sync skipped for ${result.projectId}/${result.branchName}: ${error instanceof Error ? error.message : String(error)}
|
|
2771
|
-
`
|
|
2772
|
-
);
|
|
2292
|
+
if (!periodicWorkspaceScan) {
|
|
2293
|
+
const emptyFingerprint = (0, import_node_crypto.createHash)("sha256").update("").digest("hex");
|
|
2294
|
+
periodicWorkspaceScan = setInterval(() => {
|
|
2295
|
+
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2296
|
+
periodicWorkspaceScanInFlight = true;
|
|
2297
|
+
void (async () => {
|
|
2298
|
+
const input = workspaceSyncInput({ type: "periodic" });
|
|
2299
|
+
const fingerprint = await workspaceSyncSingleFlight.fingerprint(input);
|
|
2300
|
+
if (fingerprint === emptyFingerprint) {
|
|
2301
|
+
previousPeriodicFingerprint = null;
|
|
2302
|
+
return;
|
|
2773
2303
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
}
|
|
2778
|
-
};
|
|
2779
|
-
const queued = repositorySyncQueue.then(executeSync, executeSync);
|
|
2780
|
-
repositorySyncQueue = queued.then(
|
|
2781
|
-
() => void 0,
|
|
2782
|
-
() => void 0
|
|
2783
|
-
);
|
|
2784
|
-
try {
|
|
2785
|
-
await queued;
|
|
2786
|
-
} catch (error) {
|
|
2787
|
-
syncResult = {
|
|
2788
|
-
status: "partial",
|
|
2789
|
-
results: [
|
|
2790
|
-
{
|
|
2791
|
-
projectId: "worker",
|
|
2792
|
-
projectPath: "worker",
|
|
2793
|
-
branchName: "all",
|
|
2794
|
-
status: "failed",
|
|
2795
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2304
|
+
if (previousPeriodicFingerprint !== fingerprint) {
|
|
2305
|
+
previousPeriodicFingerprint = fingerprint;
|
|
2306
|
+
return;
|
|
2796
2307
|
}
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2308
|
+
previousPeriodicFingerprint = null;
|
|
2309
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "periodic" });
|
|
2310
|
+
})().catch((error) => {
|
|
2311
|
+
process.stderr.write(
|
|
2312
|
+
`[r5d-worker] periodic workspace synchronization failed: ${error instanceof Error ? error.message : String(error)}
|
|
2313
|
+
`
|
|
2314
|
+
);
|
|
2315
|
+
}).finally(() => {
|
|
2316
|
+
periodicWorkspaceScanInFlight = false;
|
|
2317
|
+
});
|
|
2318
|
+
}, import_workspace_sync.WORKSPACE_PERIODIC_SCAN_INTERVAL_MS);
|
|
2319
|
+
periodicWorkspaceScan.unref();
|
|
2800
2320
|
}
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
if (message.type === "sync_workspace") {
|
|
2324
|
+
await runRequestedWorkspaceSync(message.requestId, message.trigger, {
|
|
2325
|
+
attemptId: message.attemptId,
|
|
2326
|
+
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
2327
|
+
confirmationReason: message.confirmationReason,
|
|
2328
|
+
resetToCanonical: message.resetToCanonical,
|
|
2329
|
+
skipVisibleMirror: message.skipVisibleMirror
|
|
2805
2330
|
});
|
|
2806
|
-
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
if (message.type === "workspace_head_updated") {
|
|
2334
|
+
if (!activeWorkspaceIncidentId) {
|
|
2335
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "inbound_head", detail: message.commitHash });
|
|
2336
|
+
}
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
if (message.type === "workspace_incident_updated") {
|
|
2340
|
+
activeWorkspaceIncidentId = message.status === "remediating" || message.status === "waiting_for_worker" ? message.incidentId : null;
|
|
2341
|
+
previousPeriodicFingerprint = null;
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
if (message.type === "exec_terminal_ack") {
|
|
2345
|
+
pendingProcessTerminals.delete(message.runId);
|
|
2807
2346
|
return;
|
|
2808
2347
|
}
|
|
2809
2348
|
if (message.type === "update_clis") {
|
|
@@ -2815,7 +2354,7 @@ async function startWorker(options) {
|
|
|
2815
2354
|
});
|
|
2816
2355
|
return;
|
|
2817
2356
|
}
|
|
2818
|
-
if (activeProcesses.size > 0 || activePtys.size > 0 ||
|
|
2357
|
+
if (activeProcesses.size > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0) {
|
|
2819
2358
|
sendWorkerMessage(ws, {
|
|
2820
2359
|
type: "update_clis_result",
|
|
2821
2360
|
requestId: message.requestId,
|
|
@@ -2885,7 +2424,6 @@ async function startWorker(options) {
|
|
|
2885
2424
|
});
|
|
2886
2425
|
return;
|
|
2887
2426
|
}
|
|
2888
|
-
await repositorySyncQueue;
|
|
2889
2427
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2890
2428
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2891
2429
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.projectId}/${message.branchName}
|
|
@@ -2916,7 +2454,6 @@ async function startWorker(options) {
|
|
|
2916
2454
|
});
|
|
2917
2455
|
return;
|
|
2918
2456
|
}
|
|
2919
|
-
await repositorySyncQueue;
|
|
2920
2457
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2921
2458
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2922
2459
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
@@ -2930,6 +2467,7 @@ async function startWorker(options) {
|
|
|
2930
2467
|
syncRoot,
|
|
2931
2468
|
artifactRoot,
|
|
2932
2469
|
planRoot,
|
|
2470
|
+
workspaceShadowRoot,
|
|
2933
2471
|
manifest
|
|
2934
2472
|
});
|
|
2935
2473
|
ws.send(JSON.stringify(result));
|
|
@@ -2945,7 +2483,6 @@ async function startWorker(options) {
|
|
|
2945
2483
|
});
|
|
2946
2484
|
return;
|
|
2947
2485
|
}
|
|
2948
|
-
await repositorySyncQueue;
|
|
2949
2486
|
sendWorkerMessage(ws, {
|
|
2950
2487
|
type: "exec_accepted",
|
|
2951
2488
|
requestId: message.requestId,
|
|
@@ -2966,6 +2503,7 @@ async function startWorker(options) {
|
|
|
2966
2503
|
syncRoot,
|
|
2967
2504
|
artifactRoot,
|
|
2968
2505
|
planRoot,
|
|
2506
|
+
workspaceShadowRoot,
|
|
2969
2507
|
manifest
|
|
2970
2508
|
}).catch((error) => {
|
|
2971
2509
|
sendWorkerMessage(ws, {
|
|
@@ -2985,9 +2523,8 @@ async function startWorker(options) {
|
|
|
2985
2523
|
}
|
|
2986
2524
|
return;
|
|
2987
2525
|
}
|
|
2988
|
-
if (message.type === "
|
|
2526
|
+
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
|
|
2989
2527
|
try {
|
|
2990
|
-
await repositorySyncQueue;
|
|
2991
2528
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2992
2529
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
2993
2530
|
const result = await executeOperation({
|
|
@@ -2998,8 +2535,6 @@ async function startWorker(options) {
|
|
|
2998
2535
|
projectRoot,
|
|
2999
2536
|
syncRoot,
|
|
3000
2537
|
artifactRoot,
|
|
3001
|
-
projectsRoot,
|
|
3002
|
-
manifests: [...manifestByProjectId.values()],
|
|
3003
2538
|
manifest
|
|
3004
2539
|
});
|
|
3005
2540
|
ws.send(
|
|
@@ -3037,6 +2572,14 @@ async function startWorker(options) {
|
|
|
3037
2572
|
});
|
|
3038
2573
|
ws.addEventListener("close", (event) => {
|
|
3039
2574
|
stopHeartbeatWatchdog();
|
|
2575
|
+
if (periodicWorkspaceScan) {
|
|
2576
|
+
clearInterval(periodicWorkspaceScan);
|
|
2577
|
+
periodicWorkspaceScan = void 0;
|
|
2578
|
+
}
|
|
2579
|
+
if (terminalReplayTimer) {
|
|
2580
|
+
clearInterval(terminalReplayTimer);
|
|
2581
|
+
terminalReplayTimer = void 0;
|
|
2582
|
+
}
|
|
3040
2583
|
if (currentWorkerSocket === ws) {
|
|
3041
2584
|
currentWorkerSocket = null;
|
|
3042
2585
|
}
|
|
@@ -3047,15 +2590,17 @@ async function startWorker(options) {
|
|
|
3047
2590
|
if (reloadAfterClose) {
|
|
3048
2591
|
process.exit(import_supervisor.WORKER_RELOAD_EXIT_CODE);
|
|
3049
2592
|
}
|
|
3050
|
-
if (activeProcesses.size > 0) {
|
|
2593
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
3051
2594
|
const delayMs = 2e3;
|
|
3052
|
-
process.stderr.write(
|
|
3053
|
-
`);
|
|
2595
|
+
process.stderr.write(
|
|
2596
|
+
`[r5d-worker] ${activeProcesses.size} process(es) active and ${pendingProcessTerminals.size} terminal report(s) pending; reconnecting in ${delayMs}ms
|
|
2597
|
+
`
|
|
2598
|
+
);
|
|
3054
2599
|
const reconnect = () => {
|
|
3055
2600
|
void startWorker(options).catch((error) => {
|
|
3056
2601
|
process.stderr.write(`[r5d-worker] reconnect failed: ${error instanceof Error ? error.message : String(error)}
|
|
3057
2602
|
`);
|
|
3058
|
-
if (activeProcesses.size > 0) {
|
|
2603
|
+
if (activeProcesses.size > 0 || pendingProcessTerminals.size > 0) {
|
|
3059
2604
|
setTimeout(reconnect, delayMs);
|
|
3060
2605
|
return;
|
|
3061
2606
|
}
|
|
@@ -3111,11 +2656,9 @@ if (isCliEntrypoint()) {
|
|
|
3111
2656
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3112
2657
|
0 && (module.exports = {
|
|
3113
2658
|
ensureVisibleGitCheckout,
|
|
3114
|
-
findRepositorySyncBlockers,
|
|
3115
2659
|
findWorkerFiles,
|
|
3116
2660
|
githubCliEnv,
|
|
3117
2661
|
grepWorkerFiles,
|
|
3118
|
-
inspectManagedWorkspace,
|
|
3119
2662
|
isArtifactEnvPath,
|
|
3120
2663
|
listWorkerDirectory,
|
|
3121
2664
|
prepareArtifactEnvForShell,
|
|
@@ -3123,11 +2666,8 @@ if (isCliEntrypoint()) {
|
|
|
3123
2666
|
readWorkerImageFile,
|
|
3124
2667
|
readWorkerTextFile,
|
|
3125
2668
|
resolveHostShell,
|
|
3126
|
-
resolveManagedCheckoutPath,
|
|
3127
2669
|
resolveProjectFilePath,
|
|
3128
2670
|
resolveWorkerFilePath,
|
|
3129
|
-
selectManifestSyncTargets,
|
|
3130
|
-
syncManifestProjectsFromInternal,
|
|
3131
2671
|
syncProjectPlans,
|
|
3132
2672
|
syncSessionArtifacts
|
|
3133
2673
|
});
|