@ricsam/r5d-worker 0.0.74 → 0.0.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/cjs/main.cjs +1031 -976
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-workspace-state.cjs +777 -0
- package/dist/cjs/project-worktrees.cjs +559 -0
- package/dist/cjs/working-tree-mirror.cjs +225 -0
- package/dist/cjs/workspace-git-sync.cjs +565 -0
- package/dist/cjs/workspace-incident-state.cjs +2 -38
- package/dist/mjs/main.mjs +1048 -987
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-workspace-state.mjs +745 -0
- package/dist/mjs/project-worktrees.mjs +513 -0
- package/dist/mjs/working-tree-mirror.mjs +190 -0
- package/dist/mjs/workspace-git-sync.mjs +524 -0
- package/dist/mjs/workspace-incident-state.mjs +1 -35
- package/dist/types/main.d.ts +35 -55
- package/dist/types/project-workspace-state.d.ts +144 -0
- package/dist/types/project-worktrees.d.ts +112 -0
- package/dist/types/working-tree-mirror.d.ts +24 -0
- package/dist/types/workspace-git-sync.d.ts +97 -0
- package/dist/types/workspace-incident-state.d.ts +0 -17
- package/dist/types/workspace-mutation-gate.d.ts +3 -3
- package/package.json +1 -1
- package/dist/cjs/workspace-convergence.cjs +0 -280
- package/dist/cjs/workspace-manifest-admission.cjs +0 -60
- package/dist/cjs/workspace-sync.cjs +0 -2469
- package/dist/mjs/workspace-convergence.mjs +0 -236
- package/dist/mjs/workspace-manifest-admission.mjs +0 -26
- package/dist/mjs/workspace-sync.mjs +0 -2417
- package/dist/types/workspace-convergence.d.ts +0 -93
- package/dist/types/workspace-manifest-admission.d.ts +0 -22
- package/dist/types/workspace-sync.d.ts +0 -167
package/dist/cjs/main.cjs
CHANGED
|
@@ -31,25 +31,23 @@ var main_exports = {};
|
|
|
31
31
|
__export(main_exports, {
|
|
32
32
|
describeWorkerSessionTarget: () => describeWorkerSessionTarget,
|
|
33
33
|
editWorkerTextFile: () => editWorkerTextFile,
|
|
34
|
-
ensureVisibleGitCheckout: () => ensureVisibleGitCheckout,
|
|
35
34
|
findWorkerFiles: () => findWorkerFiles,
|
|
36
35
|
githubCliEnv: () => githubCliEnv,
|
|
37
36
|
grepWorkerFiles: () => grepWorkerFiles,
|
|
38
37
|
isArtifactEnvPath: () => isArtifactEnvPath,
|
|
38
|
+
listWorkerCodeDirectory: () => listWorkerCodeDirectory,
|
|
39
39
|
listWorkerDirectory: () => listWorkerDirectory,
|
|
40
40
|
prepareArtifactEnvForShell: () => prepareArtifactEnvForShell,
|
|
41
41
|
prepareBuiltInToolPathsForTarget: () => prepareBuiltInToolPathsForTarget,
|
|
42
42
|
preparePlanEnvForShell: () => preparePlanEnvForShell,
|
|
43
43
|
prepareShellEnvForTarget: () => prepareShellEnvForTarget,
|
|
44
|
+
readWorkerCodeFile: () => readWorkerCodeFile,
|
|
44
45
|
readWorkerImageFile: () => readWorkerImageFile,
|
|
45
46
|
readWorkerTextFile: () => readWorkerTextFile,
|
|
46
47
|
resolveHostShell: () => resolveHostShell,
|
|
47
48
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
48
49
|
resolveWorkerSessionTarget: () => resolveWorkerSessionTarget,
|
|
49
50
|
syncSessionArtifacts: () => syncSessionArtifacts,
|
|
50
|
-
visibleCheckoutGitTestHarness: () => visibleCheckoutGitTestHarness,
|
|
51
|
-
visibleCheckoutRemoteFor: () => visibleCheckoutRemoteFor,
|
|
52
|
-
workspaceSyncCheckoutTargets: () => workspaceSyncCheckoutTargets,
|
|
53
51
|
writeWorkerTextFile: () => writeWorkerTextFile
|
|
54
52
|
});
|
|
55
53
|
module.exports = __toCommonJS(main_exports);
|
|
@@ -59,18 +57,18 @@ var import_node_crypto = require("node:crypto");
|
|
|
59
57
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
60
58
|
var import_node_child_process = require("node:child_process");
|
|
61
59
|
var import_cli_update = require("./cli-update.cjs");
|
|
62
|
-
var import_git_identity = require("./git-identity.cjs");
|
|
63
60
|
var import_heartbeat = require("./heartbeat.cjs");
|
|
64
61
|
var import_process_tree = require("./process-tree.cjs");
|
|
65
62
|
var import_pty_output_coalescer = require("./pty-output-coalescer.cjs");
|
|
66
63
|
var import_port_forward_client = require("./port-forward-client.cjs");
|
|
67
64
|
var import_workspace_incident_state = require("./workspace-incident-state.cjs");
|
|
68
65
|
var import_workspace_command_sync_policy = require("./workspace-command-sync-policy.cjs");
|
|
69
|
-
var
|
|
70
|
-
var import_workspace_manifest_admission = require("./workspace-manifest-admission.cjs");
|
|
66
|
+
var import_workspace_mutation_gate = require("./workspace-mutation-gate.cjs");
|
|
71
67
|
var import_supervisor = require("./supervisor.cjs");
|
|
72
68
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
73
|
-
var
|
|
69
|
+
var import_project_worktrees = require("./project-worktrees.cjs");
|
|
70
|
+
var import_project_workspace_state = require("./project-workspace-state.cjs");
|
|
71
|
+
var import_workspace_git_sync = require("./workspace-git-sync.cjs");
|
|
74
72
|
const import_meta = {};
|
|
75
73
|
class WorkerServerUnavailableError extends Error {
|
|
76
74
|
name = "WorkerServerUnavailableError";
|
|
@@ -81,12 +79,34 @@ const LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
|
|
|
81
79
|
const DEFAULT_READ_LIMIT = 2e3;
|
|
82
80
|
const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
83
81
|
const MAX_LINE_LENGTH = 2e3;
|
|
82
|
+
const WORKSPACE_GIT_QUIET_MS = 5e3;
|
|
83
|
+
const WORKSPACE_GIT_PERIODIC_MS = 6e4;
|
|
84
84
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
85
85
|
const pendingProcessTerminals = /* @__PURE__ */ new Map();
|
|
86
86
|
const cancelledProcessRuns = /* @__PURE__ */ new Set();
|
|
87
87
|
const activePtys = /* @__PURE__ */ new Map();
|
|
88
88
|
let currentWorkerSocket = null;
|
|
89
|
-
const
|
|
89
|
+
const workspaceMutationGate = new import_workspace_mutation_gate.WorkspaceMutationGate();
|
|
90
|
+
let workspaceSyncQueue = Promise.resolve();
|
|
91
|
+
const workspaceSyncSingleFlight = {
|
|
92
|
+
runExclusive(operation) {
|
|
93
|
+
const queued = workspaceMutationGate.runSync(operation);
|
|
94
|
+
workspaceSyncQueue = queued.then(
|
|
95
|
+
() => void 0,
|
|
96
|
+
() => void 0
|
|
97
|
+
);
|
|
98
|
+
return queued;
|
|
99
|
+
},
|
|
100
|
+
runMutation(operation) {
|
|
101
|
+
return workspaceMutationGate.runMutation(operation);
|
|
102
|
+
},
|
|
103
|
+
acquireMutation() {
|
|
104
|
+
return workspaceMutationGate.acquireMutation();
|
|
105
|
+
},
|
|
106
|
+
afterCurrent() {
|
|
107
|
+
return workspaceSyncQueue;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
90
110
|
let githubCredential = null;
|
|
91
111
|
let visibleGitIdentity = null;
|
|
92
112
|
function defaultConfigPath() {
|
|
@@ -649,20 +669,11 @@ function gitAuthArgs(auth) {
|
|
|
649
669
|
const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
|
|
650
670
|
return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
|
|
651
671
|
}
|
|
652
|
-
const
|
|
653
|
-
gitAuthArgs,
|
|
654
|
-
commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...gitAuthArgs(auth), ...args],
|
|
655
|
-
cloneArgs: (remoteUrl, branchPath) => ["clone", "--no-recurse-submodules", "--origin", "origin", remoteUrl, branchPath],
|
|
656
|
-
fetchArgs: (...args) => ["fetch", "--no-recurse-submodules", ...args]
|
|
672
|
+
const workerGitCommand = {
|
|
673
|
+
commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...gitAuthArgs(auth), ...args]
|
|
657
674
|
};
|
|
658
|
-
function visibleCheckoutCloneArgs(remoteUrl, branchPath) {
|
|
659
|
-
return visibleCheckoutGitTestHarness.cloneArgs(remoteUrl, branchPath);
|
|
660
|
-
}
|
|
661
|
-
function visibleCheckoutFetchArgs(...args) {
|
|
662
|
-
return visibleCheckoutGitTestHarness.fetchArgs(...args);
|
|
663
|
-
}
|
|
664
675
|
function runGit(args, options = {}) {
|
|
665
|
-
const command =
|
|
676
|
+
const command = workerGitCommand.commandArgs(args, options.auth);
|
|
666
677
|
const result = Bun.spawnSync(command, {
|
|
667
678
|
cwd: options.cwd,
|
|
668
679
|
stdout: "pipe",
|
|
@@ -691,9 +702,6 @@ function getGitBlobHashForContent(content) {
|
|
|
691
702
|
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
692
703
|
return (0, import_node_crypto.createHash)("sha1").update(`blob ${buffer.length}\0`).update(buffer).digest("hex");
|
|
693
704
|
}
|
|
694
|
-
function hasWorktreeChanges(cwd) {
|
|
695
|
-
return runGit(["status", "--porcelain"], { cwd }).trim().length > 0;
|
|
696
|
-
}
|
|
697
705
|
function isInsideBranchPath(branchPath, filePath) {
|
|
698
706
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(branchPath), import_node_path.default.resolve(filePath));
|
|
699
707
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
@@ -803,33 +811,9 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
|
|
|
803
811
|
scope: "project"
|
|
804
812
|
};
|
|
805
813
|
}
|
|
806
|
-
function
|
|
814
|
+
function canonicalProjectRemoteUrl(baseUrl, projectId) {
|
|
807
815
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
808
816
|
}
|
|
809
|
-
function visibleCheckoutRemoteFor(input) {
|
|
810
|
-
const canonicalCheckout = input.manifest?.canonicalCheckouts?.find((checkout) => checkout.branchName === input.branchName);
|
|
811
|
-
if (!canonicalCheckout && input.manifest) {
|
|
812
|
-
if (!input.manifest.repoHttpUrl) {
|
|
813
|
-
throw new Error(`Project ${input.projectId} has no connected repository; connect one before opening its checkouts`);
|
|
814
|
-
}
|
|
815
|
-
return {
|
|
816
|
-
remoteUrl: input.manifest.repoHttpUrl,
|
|
817
|
-
authHeader: input.manifest.repoAuthHeader ?? null,
|
|
818
|
-
persistAuth: true,
|
|
819
|
-
reconcileExistingOrigin: true,
|
|
820
|
-
requireRemoteBranch: false,
|
|
821
|
-
requiredBaseCommit: null
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
return {
|
|
825
|
-
remoteUrl: fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
|
|
826
|
-
authHeader: `Authorization: Bearer ${input.token}`,
|
|
827
|
-
persistAuth: false,
|
|
828
|
-
reconcileExistingOrigin: true,
|
|
829
|
-
requireRemoteBranch: true,
|
|
830
|
-
requiredBaseCommit: canonicalCheckout?.scaffoldCommitHash ?? null
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
817
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
834
818
|
try {
|
|
835
819
|
return new URL(remoteUrl).origin;
|
|
@@ -846,44 +830,25 @@ function gitAuthForRemote(remoteUrl, authHeader) {
|
|
|
846
830
|
header: authHeader
|
|
847
831
|
};
|
|
848
832
|
}
|
|
849
|
-
function hasNormalVisibleGitDir(branchPath) {
|
|
850
|
-
const gitPath = import_node_path.default.join(branchPath, ".git");
|
|
851
|
-
return import_node_fs.default.existsSync(gitPath) && import_node_fs.default.statSync(gitPath).isDirectory();
|
|
852
|
-
}
|
|
853
|
-
function ensureOriginRemote(branchPath, remoteUrl) {
|
|
854
|
-
if (tryGit(["remote", "get-url", "origin"], { cwd: branchPath })) {
|
|
855
|
-
runGit(["remote", "set-url", "origin", remoteUrl], { cwd: branchPath });
|
|
856
|
-
} else {
|
|
857
|
-
runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
function originRemoteUrl(branchPath) {
|
|
861
|
-
if (!tryGit(["remote", "get-url", "origin"], { cwd: branchPath })) return null;
|
|
862
|
-
return runGit(["remote", "get-url", "origin"], { cwd: branchPath });
|
|
863
|
-
}
|
|
864
833
|
function shellQuote(value) {
|
|
865
834
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
866
835
|
}
|
|
867
836
|
function githubCredentialsPath() {
|
|
868
837
|
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "github-credentials");
|
|
869
838
|
}
|
|
870
|
-
function
|
|
839
|
+
function decodeGitAuthHeader(authHeader) {
|
|
871
840
|
const match = /^Authorization:\s*Basic\s+(.+)$/i.exec(authHeader.trim());
|
|
872
|
-
if (
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
if (separatorIndex <= 0) {
|
|
878
|
-
return null;
|
|
841
|
+
if (match) {
|
|
842
|
+
const decoded = Buffer.from(match[1], "base64").toString("utf8");
|
|
843
|
+
const separatorIndex = decoded.indexOf(":");
|
|
844
|
+
if (separatorIndex <= 0) return null;
|
|
845
|
+
return { username: decoded.slice(0, separatorIndex), password: decoded.slice(separatorIndex + 1) };
|
|
879
846
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
password: decoded.slice(separatorIndex + 1)
|
|
883
|
-
};
|
|
847
|
+
const bearer = /^Authorization:\s*Bearer\s+(.+)$/i.exec(authHeader.trim());
|
|
848
|
+
return bearer?.[1] ? { username: "r5d-worker", password: bearer[1] } : null;
|
|
884
849
|
}
|
|
885
850
|
function writeCredentialStoreEntry(remoteUrl, authHeader) {
|
|
886
|
-
const credentials =
|
|
851
|
+
const credentials = decodeGitAuthHeader(authHeader);
|
|
887
852
|
if (!credentials) {
|
|
888
853
|
return null;
|
|
889
854
|
}
|
|
@@ -917,29 +882,9 @@ function writeCredentialStoreEntry(remoteUrl, authHeader) {
|
|
|
917
882
|
import_node_fs.default.chmodSync(storePath, 384);
|
|
918
883
|
return storePath;
|
|
919
884
|
}
|
|
920
|
-
function
|
|
921
|
-
if (!authHeader) {
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
885
|
+
function credentialHelperForRemote(remoteUrl, authHeader) {
|
|
924
886
|
const credentialStore = writeCredentialStoreEntry(remoteUrl, authHeader);
|
|
925
|
-
|
|
926
|
-
runGit(["config", "credential.helper", `store --file=${shellQuote(credentialStore)}`], { cwd: branchPath });
|
|
927
|
-
runGit(["config", "credential.useHttpPath", "false"], { cwd: branchPath });
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
runGit(["config", `${gitExtraHeaderConfigKey(gitExtraHeaderUrlForRemote(remoteUrl))}`, authHeader], { cwd: branchPath });
|
|
931
|
-
}
|
|
932
|
-
function clearVisibleGitAuthentication(branchPath) {
|
|
933
|
-
let extraHeaderKeys = [];
|
|
934
|
-
try {
|
|
935
|
-
extraHeaderKeys = runGit(["config", "--local", "--name-only", "--get-regexp", "^http\\..*extraheader$"], { cwd: branchPath }).split(/\r?\n/).filter(Boolean);
|
|
936
|
-
} catch {
|
|
937
|
-
}
|
|
938
|
-
for (const key of extraHeaderKeys) {
|
|
939
|
-
tryGit(["config", "--local", "--unset-all", key], { cwd: branchPath });
|
|
940
|
-
}
|
|
941
|
-
runGit(["config", "--local", "--replace-all", "credential.helper", ""], { cwd: branchPath });
|
|
942
|
-
tryGit(["config", "--local", "--unset-all", "credential.useHttpPath"], { cwd: branchPath });
|
|
887
|
+
return credentialStore ? `store --file=${shellQuote(credentialStore)}` : null;
|
|
943
888
|
}
|
|
944
889
|
function dockerConfigPath() {
|
|
945
890
|
return import_node_path.default.join(import_node_os.default.homedir(), ".docker", "config.json");
|
|
@@ -1019,188 +964,26 @@ function githubProcessEnv() {
|
|
|
1019
964
|
...githubCliEnv(githubCredential?.token)
|
|
1020
965
|
};
|
|
1021
966
|
}
|
|
1022
|
-
function
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
const defaultRemoteExists = tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.defaultBranch}`], {
|
|
1028
|
-
cwd: input.branchPath
|
|
1029
|
-
});
|
|
1030
|
-
const clean = !hasWorktreeChanges(input.branchPath);
|
|
1031
|
-
if (remoteBranchExists && clean) {
|
|
1032
|
-
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1033
|
-
cwd: input.branchPath
|
|
1034
|
-
});
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
if (branchExists) {
|
|
1038
|
-
runGit(["checkout", "--no-recurse-submodules", input.branchName], { cwd: input.branchPath });
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
if (defaultRemoteExists) {
|
|
1042
|
-
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.defaultBranch}`], {
|
|
1043
|
-
cwd: input.branchPath
|
|
1044
|
-
});
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName], { cwd: input.branchPath });
|
|
1048
|
-
}
|
|
1049
|
-
function reconcileRewrittenCanonicalHistory(input) {
|
|
1050
|
-
const remoteRef = `refs/remotes/origin/${input.branchName}`;
|
|
1051
|
-
if (!tryGit(["rev-parse", "--verify", "HEAD"], { cwd: input.branchPath })) return;
|
|
1052
|
-
if (!tryGit(["show-ref", "--verify", "--quiet", remoteRef], { cwd: input.branchPath })) return;
|
|
1053
|
-
if (tryGit(["merge-base", "HEAD", remoteRef], { cwd: input.branchPath })) return;
|
|
1054
|
-
if (hasWorktreeChanges(input.branchPath)) return;
|
|
1055
|
-
const localTree = runGit(["rev-parse", "HEAD^{tree}"], { cwd: input.branchPath });
|
|
1056
|
-
const remoteTree = runGit(["rev-parse", `${remoteRef}^{tree}`], { cwd: input.branchPath });
|
|
1057
|
-
if (localTree !== remoteTree) return;
|
|
1058
|
-
const previousHead = runGit(["rev-parse", "HEAD"], { cwd: input.branchPath });
|
|
1059
|
-
runGit(["update-ref", `refs/r5d/pre-canonical-checkout/${previousHead}`, previousHead], { cwd: input.branchPath });
|
|
1060
|
-
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, remoteRef], { cwd: input.branchPath });
|
|
1061
|
-
}
|
|
1062
|
-
function migrateExistingCheckoutToRequiredRemote(input) {
|
|
1063
|
-
if (originRemoteUrl(input.branchPath) === input.remoteUrl) return;
|
|
1064
|
-
const dirtyWorkingTree = hasWorktreeChanges(input.branchPath);
|
|
1065
|
-
if (dirtyWorkingTree && input.requiredBaseCommit) {
|
|
1066
|
-
throw new Error(`Cannot migrate dirty checkout for ${input.branchName} to its canonical remote`);
|
|
1067
|
-
}
|
|
1068
|
-
const preserveWorkingTree = dirtyWorkingTree;
|
|
1069
|
-
runGit(
|
|
1070
|
-
visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
|
|
1071
|
-
{
|
|
1072
|
-
cwd: input.branchPath,
|
|
1073
|
-
auth: input.remoteAuth
|
|
1074
|
-
}
|
|
1075
|
-
);
|
|
1076
|
-
const canonicalRemoteRef = `refs/remotes/origin/${input.branchName}`;
|
|
1077
|
-
if (!tryGit(["show-ref", "--verify", "--quiet", canonicalRemoteRef], { cwd: input.branchPath })) {
|
|
1078
|
-
throw new Error(`Canonical branch ${input.branchName} is unavailable during checkout migration`);
|
|
1079
|
-
}
|
|
1080
|
-
if (input.requiredBaseCommit && (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, canonicalRemoteRef], { cwd: input.branchPath }))) {
|
|
1081
|
-
throw new Error(`Canonical branch ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
|
|
1082
|
-
}
|
|
1083
|
-
ensureOriginRemote(input.branchPath, input.remoteUrl);
|
|
1084
|
-
if (preserveWorkingTree) return;
|
|
1085
|
-
if (tryGit(["rev-parse", "--verify", "HEAD"], { cwd: input.branchPath })) {
|
|
1086
|
-
const previousHead = runGit(["rev-parse", "HEAD"], { cwd: input.branchPath });
|
|
1087
|
-
runGit(["update-ref", `refs/r5d/pre-canonical-checkout/${previousHead}`, previousHead], { cwd: input.branchPath });
|
|
1088
|
-
}
|
|
1089
|
-
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1090
|
-
cwd: input.branchPath
|
|
1091
|
-
});
|
|
967
|
+
function primaryProjectBranch(config) {
|
|
968
|
+
if (config.branches.length === 0) throw new Error(`Project ${config.projectId} has no configured branches`);
|
|
969
|
+
if (config.branches.some(({ branchName }) => branchName === "main")) return "main";
|
|
970
|
+
if (config.branches.some(({ branchName }) => branchName === config.defaultBranch)) return config.defaultBranch;
|
|
971
|
+
return config.branches[0].branchName;
|
|
1092
972
|
}
|
|
1093
|
-
function
|
|
1094
|
-
|
|
1095
|
-
for (const revision of [remoteRevision, "HEAD"]) {
|
|
1096
|
-
if (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, revision], { cwd: input.branchPath })) {
|
|
1097
|
-
throw new Error(`Canonical checkout ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
973
|
+
function configuredProjectRoot(projectsRoot, config) {
|
|
974
|
+
return (0, import_managed_paths.managedProjectRoot)(projectsRoot, config.checkoutPathSegments);
|
|
1100
975
|
}
|
|
1101
|
-
function
|
|
1102
|
-
validateBranchName(
|
|
1103
|
-
|
|
1104
|
-
const branchPath = import_node_path.default.join(input.projectRoot, input.branchName);
|
|
1105
|
-
const createdCheckout = !hasNormalVisibleGitDir(branchPath);
|
|
1106
|
-
if (createdCheckout) {
|
|
1107
|
-
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1108
|
-
import_node_fs.default.mkdirSync(import_node_path.default.dirname(branchPath), { recursive: true });
|
|
1109
|
-
const cloned = tryGit(visibleCheckoutCloneArgs(input.remoteUrl, branchPath), {
|
|
1110
|
-
auth: input.remoteAuth
|
|
1111
|
-
});
|
|
1112
|
-
if (!cloned && input.requireRemoteBranch) {
|
|
1113
|
-
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1114
|
-
throw new Error(`Failed to clone canonical checkout for branch ${input.branchName}`);
|
|
1115
|
-
}
|
|
1116
|
-
if (!cloned) {
|
|
1117
|
-
import_node_fs.default.mkdirSync(branchPath, { recursive: true });
|
|
1118
|
-
runGit(["init"], { cwd: branchPath });
|
|
1119
|
-
}
|
|
1120
|
-
ensureOriginRemote(branchPath, input.remoteUrl);
|
|
1121
|
-
if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
|
|
1122
|
-
else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
|
|
1123
|
-
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1124
|
-
tryGit(visibleCheckoutFetchArgs("origin", "--prune"), { cwd: branchPath, auth: input.remoteAuth });
|
|
1125
|
-
if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
|
|
1126
|
-
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1127
|
-
throw new Error(`Canonical branch ${input.branchName} is unavailable after clone`);
|
|
1128
|
-
}
|
|
1129
|
-
checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
|
|
1130
|
-
if (input.requiredBaseCommit) {
|
|
1131
|
-
try {
|
|
1132
|
-
assertCheckoutDerivedFromRequiredBase({
|
|
1133
|
-
branchPath,
|
|
1134
|
-
branchName: input.branchName,
|
|
1135
|
-
requiredBaseCommit: input.requiredBaseCommit
|
|
1136
|
-
});
|
|
1137
|
-
} catch (error) {
|
|
1138
|
-
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1139
|
-
throw error;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
return branchPath;
|
|
1143
|
-
}
|
|
1144
|
-
if (input.requireRemoteBranch) {
|
|
1145
|
-
if (input.allowRequiredRemoteMigration === false && originRemoteUrl(branchPath) !== input.remoteUrl) {
|
|
1146
|
-
throw new Error(`Canonical checkout ${input.branchName} no longer uses its required remote`);
|
|
1147
|
-
}
|
|
1148
|
-
migrateExistingCheckoutToRequiredRemote({
|
|
1149
|
-
branchPath,
|
|
1150
|
-
branchName: input.branchName,
|
|
1151
|
-
remoteUrl: input.remoteUrl,
|
|
1152
|
-
remoteAuth: input.remoteAuth,
|
|
1153
|
-
requiredBaseCommit: input.requiredBaseCommit
|
|
1154
|
-
});
|
|
1155
|
-
runGit(
|
|
1156
|
-
visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
|
|
1157
|
-
{
|
|
1158
|
-
cwd: branchPath,
|
|
1159
|
-
auth: input.remoteAuth
|
|
1160
|
-
}
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
if (input.reconcileExistingOrigin) {
|
|
1164
|
-
ensureOriginRemote(branchPath, input.remoteUrl);
|
|
1165
|
-
}
|
|
1166
|
-
if (!input.requireRemoteBranch) {
|
|
1167
|
-
tryGit(visibleCheckoutFetchArgs("origin", "--prune"), { cwd: branchPath, auth: input.remoteAuth });
|
|
1168
|
-
}
|
|
1169
|
-
if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
|
|
1170
|
-
throw new Error(`Existing checkout for ${input.branchName} does not contain its canonical remote branch`);
|
|
1171
|
-
}
|
|
1172
|
-
if (input.requireRemoteBranch) {
|
|
1173
|
-
reconcileRewrittenCanonicalHistory({ branchPath, branchName: input.branchName });
|
|
1174
|
-
}
|
|
1175
|
-
if (input.requiredBaseCommit) {
|
|
1176
|
-
assertCheckoutDerivedFromRequiredBase({
|
|
1177
|
-
branchPath,
|
|
1178
|
-
branchName: input.branchName,
|
|
1179
|
-
requiredBaseCommit: input.requiredBaseCommit
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
|
|
1183
|
-
else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
|
|
1184
|
-
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
1185
|
-
return branchPath;
|
|
976
|
+
function configuredProjectBranchPath(projectsRoot, config, branchName) {
|
|
977
|
+
validateBranchName(branchName);
|
|
978
|
+
return import_node_path.default.join(configuredProjectRoot(projectsRoot, config), ...branchName.split("/"));
|
|
1186
979
|
}
|
|
1187
|
-
function
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
remoteUrl: remote.remoteUrl,
|
|
1195
|
-
remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
|
|
1196
|
-
persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
|
|
1197
|
-
defaultBranch,
|
|
1198
|
-
reconcileExistingOrigin: remote.reconcileExistingOrigin,
|
|
1199
|
-
requireRemoteBranch: remote.requireRemoteBranch,
|
|
1200
|
-
requiredBaseCommit: remote.requiredBaseCommit,
|
|
1201
|
-
clearPersistentAuth: !remote.persistAuth
|
|
1202
|
-
})
|
|
1203
|
-
};
|
|
980
|
+
function hasProjectWorktree(checkoutPath) {
|
|
981
|
+
try {
|
|
982
|
+
const stat = import_node_fs.default.lstatSync(import_node_path.default.join(checkoutPath, ".git"));
|
|
983
|
+
return stat.isFile() || stat.isDirectory();
|
|
984
|
+
} catch {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
1204
987
|
}
|
|
1205
988
|
function describeWorkerSessionTarget(target) {
|
|
1206
989
|
return target.type === "project" ? `${target.projectId}/${target.branchName}` : `${target.ownerUserId}/${target.rootProfile}`;
|
|
@@ -1211,40 +994,21 @@ function resolveWorkerSessionTarget(input) {
|
|
|
1211
994
|
return { target: input.target, rootPath: input.projectsRoot };
|
|
1212
995
|
}
|
|
1213
996
|
if (!import_node_fs.default.existsSync(import_node_path.default.join(input.workspaceShadowRoot, ".git"))) {
|
|
1214
|
-
throw new Error("The
|
|
997
|
+
throw new Error("The workspace sync checkout is not initialized on this worker");
|
|
1215
998
|
}
|
|
1216
999
|
return { target: input.target, rootPath: input.workspaceShadowRoot };
|
|
1217
1000
|
}
|
|
1218
|
-
const
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
token: input.token,
|
|
1224
|
-
projectRoot,
|
|
1225
|
-
syncRoot: input.syncRoot,
|
|
1226
|
-
branchName: input.target.branchName,
|
|
1227
|
-
manifest
|
|
1228
|
-
});
|
|
1229
|
-
return { target: input.target, rootPath: worktree.branchPath, manifest };
|
|
1230
|
-
}
|
|
1231
|
-
function workspaceSyncCheckoutTargets(input) {
|
|
1232
|
-
const selected = /* @__PURE__ */ new Map();
|
|
1233
|
-
const add = (target) => {
|
|
1234
|
-
selected.set(`${target.projectId}\0${target.branchName}`, target);
|
|
1235
|
-
};
|
|
1236
|
-
if (input.canonicalCheckoutOnly) {
|
|
1237
|
-
add(input.canonicalCheckoutOnly);
|
|
1238
|
-
} else if (input.includeAllManifestCheckouts) {
|
|
1239
|
-
for (const project of input.projects) {
|
|
1240
|
-
for (const branchName of project.branches) add({ projectId: project.projectId, branchName });
|
|
1241
|
-
}
|
|
1242
|
-
} else {
|
|
1243
|
-
for (const target of input.pendingTargets) add(target);
|
|
1001
|
+
const target = input.target;
|
|
1002
|
+
const config = input.projectConfigById.get(target.projectId);
|
|
1003
|
+
if (!config) throw new Error(`Project ${target.projectId} is missing from the worker workspace configuration`);
|
|
1004
|
+
if (!config.branches.some(({ branchName }) => branchName === target.branchName)) {
|
|
1005
|
+
throw new Error(`Project branch ${target.projectId}/${target.branchName} is missing from the worker workspace configuration`);
|
|
1244
1006
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1007
|
+
const branchPath = configuredProjectBranchPath(input.projectsRoot, config, target.branchName);
|
|
1008
|
+
if (!hasProjectWorktree(branchPath)) {
|
|
1009
|
+
throw new Error(`Project branch ${target.projectId}/${target.branchName} is not initialized on this worker`);
|
|
1010
|
+
}
|
|
1011
|
+
return { target, rootPath: branchPath, config };
|
|
1248
1012
|
}
|
|
1249
1013
|
function resolveCommandCwd(branchPath, cwd) {
|
|
1250
1014
|
if (!cwd) {
|
|
@@ -1768,6 +1532,77 @@ async function executeLsOperation(input) {
|
|
|
1768
1532
|
});
|
|
1769
1533
|
return listWorkerDirectory(input.resolvedTarget.rootPath, input.message.path, input.message.limit, builtInPaths);
|
|
1770
1534
|
}
|
|
1535
|
+
function resolveWorkerCodePath(branchPath, inputPath) {
|
|
1536
|
+
if (typeof inputPath !== "string" || !inputPath.startsWith("/") || inputPath.includes("\0")) {
|
|
1537
|
+
throw new Error("Code path must be an absolute virtual path");
|
|
1538
|
+
}
|
|
1539
|
+
const rawSegments = inputPath.replace(/\\/g, "/").split("/");
|
|
1540
|
+
if (rawSegments.includes("..")) throw new Error(`Invalid code path: ${inputPath}`);
|
|
1541
|
+
const displayPath = import_node_path.default.posix.normalize(inputPath.replace(/\\/g, "/"));
|
|
1542
|
+
const relativePath = displayPath.replace(/^\/+/, "");
|
|
1543
|
+
const pathSegments = relativePath ? relativePath.split("/") : [];
|
|
1544
|
+
if (pathSegments.some((segment) => segment.toLowerCase() === ".git")) {
|
|
1545
|
+
throw new Error("Code paths cannot access Git metadata");
|
|
1546
|
+
}
|
|
1547
|
+
const resolvedBranchPath = import_node_path.default.resolve(branchPath);
|
|
1548
|
+
const absolutePath = import_node_path.default.resolve(resolvedBranchPath, ...pathSegments);
|
|
1549
|
+
assertInsideRoot(resolvedBranchPath, absolutePath, "Code path");
|
|
1550
|
+
if (!import_node_fs.default.existsSync(absolutePath)) throw new Error(`Code path not found: ${displayPath}`);
|
|
1551
|
+
const realBranchPath = import_node_fs.default.realpathSync(resolvedBranchPath);
|
|
1552
|
+
const realPath = import_node_fs.default.realpathSync(absolutePath);
|
|
1553
|
+
assertInsideRoot(realBranchPath, realPath, "Code path");
|
|
1554
|
+
const realRelativePath = import_node_path.default.relative(realBranchPath, realPath).split(import_node_path.default.sep);
|
|
1555
|
+
if (realRelativePath.some((segment) => segment.toLowerCase() === ".git")) {
|
|
1556
|
+
throw new Error("Code paths cannot access Git metadata");
|
|
1557
|
+
}
|
|
1558
|
+
return { absolutePath, displayPath };
|
|
1559
|
+
}
|
|
1560
|
+
function listWorkerCodeDirectory(branchPath, inputPath) {
|
|
1561
|
+
const resolved = resolveWorkerCodePath(branchPath, inputPath);
|
|
1562
|
+
if (!import_node_fs.default.statSync(resolved.absolutePath).isDirectory()) {
|
|
1563
|
+
throw new Error(`Code directory not found: ${resolved.displayPath}`);
|
|
1564
|
+
}
|
|
1565
|
+
const entries = import_node_fs.default.readdirSync(resolved.absolutePath, { withFileTypes: true }).filter((entry) => entry.name.toLowerCase() !== ".git").flatMap((entry) => {
|
|
1566
|
+
const entryPath = import_node_path.default.posix.join(resolved.displayPath, entry.name);
|
|
1567
|
+
try {
|
|
1568
|
+
const resolvedEntry = resolveWorkerCodePath(branchPath, entryPath);
|
|
1569
|
+
const stats = import_node_fs.default.statSync(resolvedEntry.absolutePath);
|
|
1570
|
+
if (!stats.isFile() && !stats.isDirectory()) return [];
|
|
1571
|
+
return [
|
|
1572
|
+
{
|
|
1573
|
+
name: entry.name,
|
|
1574
|
+
type: stats.isDirectory() ? "directory" : "file",
|
|
1575
|
+
size: stats.isDirectory() ? null : stats.size,
|
|
1576
|
+
mtime: stats.mtime.toISOString()
|
|
1577
|
+
}
|
|
1578
|
+
];
|
|
1579
|
+
} catch {
|
|
1580
|
+
return [];
|
|
1581
|
+
}
|
|
1582
|
+
}).sort((left, right) => Number(right.type === "directory") - Number(left.type === "directory") || left.name.localeCompare(right.name));
|
|
1583
|
+
return { type: "code_list", path: resolved.displayPath, entries };
|
|
1584
|
+
}
|
|
1585
|
+
function readWorkerCodeFile(branchPath, inputPath) {
|
|
1586
|
+
const resolved = resolveWorkerCodePath(branchPath, inputPath);
|
|
1587
|
+
const stats = import_node_fs.default.statSync(resolved.absolutePath);
|
|
1588
|
+
if (!stats.isFile()) throw new Error(`Code file not found: ${resolved.displayPath}`);
|
|
1589
|
+
const bytes = import_node_fs.default.readFileSync(resolved.absolutePath);
|
|
1590
|
+
return {
|
|
1591
|
+
type: "code_read",
|
|
1592
|
+
path: resolved.displayPath,
|
|
1593
|
+
size: bytes.length,
|
|
1594
|
+
mtime: stats.mtime.toISOString(),
|
|
1595
|
+
base64: bytes.toString("base64")
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function executeCodeListOperation(input) {
|
|
1599
|
+
if (input.resolvedTarget.target.type !== "project") throw new Error("Code listing requires a project branch target");
|
|
1600
|
+
return listWorkerCodeDirectory(input.resolvedTarget.rootPath, input.message.path);
|
|
1601
|
+
}
|
|
1602
|
+
function executeCodeReadOperation(input) {
|
|
1603
|
+
if (input.resolvedTarget.target.type !== "project") throw new Error("Code reading requires a project branch target");
|
|
1604
|
+
return readWorkerCodeFile(input.resolvedTarget.rootPath, input.message.path);
|
|
1605
|
+
}
|
|
1771
1606
|
function readWorkerImageFile(branchPath, filePath, builtInPaths) {
|
|
1772
1607
|
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1773
1608
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
@@ -1820,6 +1655,10 @@ async function executeOperation(input) {
|
|
|
1820
1655
|
return executeLsOperation({ ...input, message: input.message });
|
|
1821
1656
|
case "view_file_bytes":
|
|
1822
1657
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
1658
|
+
case "code_list":
|
|
1659
|
+
return executeCodeListOperation({ ...input, message: input.message });
|
|
1660
|
+
case "code_read":
|
|
1661
|
+
return executeCodeReadOperation({ ...input, message: input.message });
|
|
1823
1662
|
}
|
|
1824
1663
|
}
|
|
1825
1664
|
function targetIdentityEnv(target) {
|
|
@@ -2419,19 +2258,16 @@ function websocketUrl(baseUrl, label) {
|
|
|
2419
2258
|
url.searchParams.set("version", getWorkerVersion());
|
|
2420
2259
|
return url.toString();
|
|
2421
2260
|
}
|
|
2422
|
-
function projectRootFor(projectsRoot, projectId, manifestByProjectId) {
|
|
2423
|
-
const manifest = manifestByProjectId.get(projectId);
|
|
2424
|
-
if (!manifest) throw new Error(`Project ${projectId} is missing from the worker workspace manifest`);
|
|
2425
|
-
return (0, import_managed_paths.managedProjectRoot)(projectsRoot, manifest.checkoutPathSegments);
|
|
2426
|
-
}
|
|
2427
2261
|
async function startWorker(options) {
|
|
2428
2262
|
process.on("uncaughtException", (error) => {
|
|
2429
2263
|
process.stderr.write(`[r5d-worker] uncaught exception: ${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
2430
2264
|
`);
|
|
2431
2265
|
});
|
|
2432
2266
|
process.on("unhandledRejection", (reason) => {
|
|
2433
|
-
process.stderr.write(
|
|
2434
|
-
`)
|
|
2267
|
+
process.stderr.write(
|
|
2268
|
+
`[r5d-worker] unhandled rejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}
|
|
2269
|
+
`
|
|
2270
|
+
);
|
|
2435
2271
|
});
|
|
2436
2272
|
const config = readConfig(options.configPath);
|
|
2437
2273
|
const { baseUrl, token } = resolveCredentials(options, config);
|
|
@@ -2454,408 +2290,800 @@ async function startWorker(options) {
|
|
|
2454
2290
|
`);
|
|
2455
2291
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
2456
2292
|
`);
|
|
2293
|
+
const snapshotCleanup = (0, import_project_worktrees.cleanupStaleProjectWorktreeSnapshots)();
|
|
2294
|
+
if (snapshotCleanup.removed.length > 0) {
|
|
2295
|
+
process.stdout.write(`[r5d-worker] removed ${snapshotCleanup.removed.length} stale project snapshot(s)
|
|
2296
|
+
`);
|
|
2297
|
+
}
|
|
2298
|
+
for (const failure of snapshotCleanup.failed) {
|
|
2299
|
+
process.stderr.write(`[r5d-worker] failed to remove stale project snapshot ${failure.path}: ${failure.error}
|
|
2300
|
+
`);
|
|
2301
|
+
}
|
|
2457
2302
|
runGit(["--version"]);
|
|
2458
2303
|
await verifyR5dctlAuth(baseUrl, token);
|
|
2459
2304
|
import_node_fs.default.mkdirSync(projectsRoot, { recursive: true });
|
|
2460
2305
|
import_node_fs.default.mkdirSync(syncRoot, { recursive: true });
|
|
2461
2306
|
import_node_fs.default.mkdirSync(artifactRoot, { recursive: true });
|
|
2462
2307
|
import_node_fs.default.mkdirSync(planRoot, { recursive: true });
|
|
2463
|
-
const
|
|
2464
|
-
|
|
2308
|
+
const projectWorkspaceStateStore = new import_project_workspace_state.ProjectWorkspaceStateStore({
|
|
2309
|
+
stateRoot: import_node_path.default.join(syncRoot, "project-state"),
|
|
2310
|
+
projectsRoot
|
|
2311
|
+
});
|
|
2312
|
+
let projectWorkspaceState = projectWorkspaceStateStore.read();
|
|
2313
|
+
const projectConfigById = /* @__PURE__ */ new Map();
|
|
2314
|
+
const pendingCheckouts = /* @__PURE__ */ new Map();
|
|
2315
|
+
const readyProjectIds = /* @__PURE__ */ new Set();
|
|
2316
|
+
const reconciledProjectConfigFingerprints = /* @__PURE__ */ new Map();
|
|
2317
|
+
const lastObservedProjectHeads = /* @__PURE__ */ new Map();
|
|
2318
|
+
const pendingMirrorDeletes = /* @__PURE__ */ new Map();
|
|
2465
2319
|
let workspaceRemoteUrl = null;
|
|
2320
|
+
let workspaceGitIdentity = null;
|
|
2321
|
+
let workspaceConfigured = false;
|
|
2466
2322
|
let activeWorkspaceIncidentId = null;
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
let
|
|
2471
|
-
let workspacePublicationTimer;
|
|
2472
|
-
let workspaceSafetyScan;
|
|
2473
|
-
let workspaceManifestHydrationTimer;
|
|
2474
|
-
let workspaceManifestHydrationInFlight = false;
|
|
2475
|
-
let workspaceSafetyScanInFlight = false;
|
|
2476
|
-
let workspaceHeadConvergenceInFlight = false;
|
|
2477
|
-
let pendingWorkspaceHead;
|
|
2478
|
-
let pendingPublication;
|
|
2479
|
-
const explicitWorkspacePublications = new import_workspace_convergence.ExplicitWorkspacePublicationQueue();
|
|
2323
|
+
let workspaceAutomaticTimer;
|
|
2324
|
+
let workspacePeriodicTimer;
|
|
2325
|
+
let pendingAutomaticTrigger;
|
|
2326
|
+
let automaticSyncInFlight = false;
|
|
2480
2327
|
let terminalReplayTimer;
|
|
2481
2328
|
let workspaceSyncRequestsInFlight = 0;
|
|
2482
2329
|
let sessionArtifactSyncRequestsInFlight = 0;
|
|
2483
2330
|
let cliUpdateInProgress = false;
|
|
2484
2331
|
let reloadAfterClose = false;
|
|
2485
2332
|
let shutdownAfterClose = false;
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2333
|
+
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
2334
|
+
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
2335
|
+
const projectMountId = (projectId, branchName) => `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
2336
|
+
const projectPlanMountId = (projectId, branchName) => `plan:${projectId}:${encodeURIComponent(branchName)}`;
|
|
2337
|
+
const workspaceProjectRelativePath = (projectId, branchName) => import_node_path.default.posix.join("projects", projectId, "branches", encodeURIComponent(branchName));
|
|
2338
|
+
const workspacePlanRelativePath = (projectId, branchName) => import_node_path.default.posix.join("plans", projectId, encodeURIComponent(branchName));
|
|
2339
|
+
const projectConnection = (project) => {
|
|
2340
|
+
const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, project.projectId);
|
|
2341
|
+
const mirrorAuth = gitAuthForRemote(mirrorUrl, bearerAuthHeader);
|
|
2342
|
+
const originUrl = project.repoHttpUrl ?? mirrorUrl;
|
|
2343
|
+
const originHeader = project.repoHttpUrl ? project.repoAuthHeader : bearerAuthHeader;
|
|
2344
|
+
const originAuth = gitAuthForRemote(originUrl, originHeader);
|
|
2345
|
+
const credentialHelper = originHeader ? credentialHelperForRemote(originUrl, originHeader) : null;
|
|
2346
|
+
return { mirrorUrl, mirrorAuth, originUrl, originAuth, credentialHelper };
|
|
2347
|
+
};
|
|
2348
|
+
const durableDeletionForBranch = (projectId, checkoutPathSegments, branchName) => projectWorkspaceState.tombstones.find(
|
|
2349
|
+
(tombstone) => tombstone.projectId === projectId && tombstone.checkoutPathSegments[0] === checkoutPathSegments[0] && tombstone.checkoutPathSegments[1] === checkoutPathSegments[1] && (tombstone.kind === "project" || tombstone.branchName === branchName)
|
|
2350
|
+
);
|
|
2351
|
+
const stageProjectBranchDeletion = (project, branchName, requireLocalBranch) => {
|
|
2352
|
+
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
2353
|
+
const primaryBranchName = primaryProjectBranch(project);
|
|
2354
|
+
const connection = projectConnection(project);
|
|
2355
|
+
const sourcePath = configuredProjectBranchPath(projectsRoot, project, branchName);
|
|
2356
|
+
if (requireLocalBranch || hasProjectWorktree(sourcePath)) {
|
|
2357
|
+
(0, import_project_worktrees.deleteLinkedProjectBranch)({ projectRoot, primaryBranchName, branchName });
|
|
2358
|
+
}
|
|
2359
|
+
const planSourcePath = import_node_path.default.join(planRoot, project.projectId, ...branchName.split("/"));
|
|
2360
|
+
import_node_fs.default.rmSync(planSourcePath, { recursive: true, force: true });
|
|
2361
|
+
const key = projectBranchKey(project.projectId, branchName);
|
|
2362
|
+
const durableDeletion = durableDeletionForBranch(project.projectId, project.checkoutPathSegments, branchName);
|
|
2363
|
+
pendingCheckouts.delete(key);
|
|
2364
|
+
pendingMirrorDeletes.set(key, {
|
|
2365
|
+
id: projectMountId(project.projectId, branchName),
|
|
2366
|
+
projectId: project.projectId,
|
|
2367
|
+
branchName,
|
|
2368
|
+
gitDirectory: configuredProjectBranchPath(projectsRoot, project, primaryBranchName),
|
|
2369
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2370
|
+
mirrorAuth: connection.mirrorAuth,
|
|
2371
|
+
sourcePath,
|
|
2372
|
+
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branchName),
|
|
2373
|
+
planSourcePath,
|
|
2374
|
+
planWorkspaceRelativePath: workspacePlanRelativePath(project.projectId, branchName),
|
|
2375
|
+
...durableDeletion ? { tombstoneId: durableDeletion.id } : {},
|
|
2376
|
+
...durableDeletion?.kind === "project" ? { projectDeleted: !projectWorkspaceState.desiredProjects.some(({ projectId }) => projectId === project.projectId) } : {}
|
|
2377
|
+
});
|
|
2378
|
+
};
|
|
2379
|
+
const stageProjectDeletion = (project) => {
|
|
2380
|
+
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
2381
|
+
const connection = projectConnection(project);
|
|
2382
|
+
const branches = project.branches.map(({ branchName }) => ({
|
|
2383
|
+
branchName,
|
|
2384
|
+
sourcePath: configuredProjectBranchPath(projectsRoot, project, branchName),
|
|
2385
|
+
planSourcePath: import_node_path.default.join(planRoot, project.projectId, ...branchName.split("/"))
|
|
2386
|
+
}));
|
|
2387
|
+
(0, import_project_worktrees.removeProjectWorktrees)({ projectRoot, branchNames: branches.map(({ branchName }) => branchName) });
|
|
2388
|
+
for (const branch of branches) {
|
|
2389
|
+
import_node_fs.default.rmSync(branch.planSourcePath, { recursive: true, force: true });
|
|
2390
|
+
const key = projectBranchKey(project.projectId, branch.branchName);
|
|
2391
|
+
const durableDeletion = durableDeletionForBranch(project.projectId, project.checkoutPathSegments, branch.branchName);
|
|
2392
|
+
pendingCheckouts.delete(key);
|
|
2393
|
+
pendingMirrorDeletes.set(key, {
|
|
2394
|
+
id: projectMountId(project.projectId, branch.branchName),
|
|
2395
|
+
projectId: project.projectId,
|
|
2396
|
+
branchName: branch.branchName,
|
|
2397
|
+
// The project Git directory is intentionally gone before the outer
|
|
2398
|
+
// tree is published. Use the outer workspace repository to delete the
|
|
2399
|
+
// hidden ref only after that publication succeeds.
|
|
2400
|
+
gitDirectory: workspaceShadowRoot,
|
|
2401
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2402
|
+
mirrorAuth: connection.mirrorAuth,
|
|
2403
|
+
sourcePath: branch.sourcePath,
|
|
2404
|
+
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branch.branchName),
|
|
2405
|
+
planSourcePath: branch.planSourcePath,
|
|
2406
|
+
planWorkspaceRelativePath: workspacePlanRelativePath(project.projectId, branch.branchName),
|
|
2407
|
+
...durableDeletion ? { tombstoneId: durableDeletion.id } : {},
|
|
2408
|
+
...durableDeletion?.kind === "project" ? { projectDeleted: !projectWorkspaceState.desiredProjects.some(({ projectId }) => projectId === project.projectId) } : {}
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
readyProjectIds.delete(project.projectId);
|
|
2412
|
+
reconciledProjectConfigFingerprints.delete(project.projectId);
|
|
2413
|
+
};
|
|
2414
|
+
const cleanupConfigForDurableDeletion = (deletion) => {
|
|
2415
|
+
const desiredLayout = projectWorkspaceState.desiredProjects.find(
|
|
2416
|
+
(project) => project.projectId === deletion.projectId && project.checkoutPathSegments[0] === deletion.checkoutPathSegments[0] && project.checkoutPathSegments[1] === deletion.checkoutPathSegments[1]
|
|
2417
|
+
);
|
|
2418
|
+
const liveConfig = projectConfigById.get(deletion.projectId);
|
|
2419
|
+
const branchNames = deletion.kind === "project" ? deletion.branchNames : desiredLayout?.branches.map(({ branchName }) => branchName) ?? [];
|
|
2420
|
+
const defaultBranch = desiredLayout?.defaultBranch ?? (branchNames.includes("main") ? "main" : branchNames[0] ?? "main");
|
|
2489
2421
|
return {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2422
|
+
projectId: deletion.projectId,
|
|
2423
|
+
checkoutPathSegments: [...deletion.checkoutPathSegments],
|
|
2424
|
+
projectPath: liveConfig?.projectPath ?? deletion.checkoutPathSegments.join("/"),
|
|
2425
|
+
repoHttpUrl: liveConfig?.repoHttpUrl ?? null,
|
|
2426
|
+
repoAuthHeader: liveConfig?.repoAuthHeader ?? null,
|
|
2427
|
+
defaultBranch,
|
|
2428
|
+
branches: branchNames.map(
|
|
2429
|
+
(branchName) => liveConfig?.branches.find((branch) => branch.branchName === branchName) ?? {
|
|
2430
|
+
branchName,
|
|
2431
|
+
sourceBranchName: null,
|
|
2432
|
+
// Deletion never consumes the baseline, but retain a structurally
|
|
2433
|
+
// valid value for the shared worker-project shape.
|
|
2434
|
+
baseCommitHash: "0".repeat(40)
|
|
2435
|
+
}
|
|
2436
|
+
)
|
|
2499
2437
|
};
|
|
2500
2438
|
};
|
|
2501
|
-
const
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2439
|
+
const stageDurableWorkspaceDeletions = () => {
|
|
2440
|
+
for (const deletion of projectWorkspaceState.pendingTreeDeletions) {
|
|
2441
|
+
const cleanupConfig = cleanupConfigForDurableDeletion(deletion);
|
|
2442
|
+
if (deletion.kind === "project") {
|
|
2443
|
+
if (!deletion.projectDeleted) {
|
|
2444
|
+
if (import_node_fs.default.existsSync(deletion.managedPath)) {
|
|
2445
|
+
(0, import_project_worktrees.removeProjectWorktrees)({ projectRoot: deletion.managedPath, branchNames: deletion.branchNames });
|
|
2446
|
+
}
|
|
2447
|
+
readyProjectIds.delete(deletion.projectId);
|
|
2448
|
+
reconciledProjectConfigFingerprints.delete(deletion.projectId);
|
|
2449
|
+
continue;
|
|
2450
|
+
}
|
|
2451
|
+
stageProjectDeletion(cleanupConfig);
|
|
2452
|
+
continue;
|
|
2453
|
+
}
|
|
2454
|
+
const primaryPath = deletion.primaryBranchName ? configuredProjectBranchPath(projectsRoot, cleanupConfig, deletion.primaryBranchName) : null;
|
|
2455
|
+
stageProjectBranchDeletion(cleanupConfig, deletion.branchName, Boolean(primaryPath && hasProjectWorktree(primaryPath)));
|
|
2456
|
+
}
|
|
2457
|
+
for (const deletion of projectWorkspaceState.pendingMirrorRefDeletions) {
|
|
2458
|
+
const key = projectBranchKey(deletion.projectId, deletion.branchName);
|
|
2459
|
+
const planSourcePath = import_node_path.default.join(planRoot, deletion.projectId, ...deletion.branchName.split("/"));
|
|
2460
|
+
import_node_fs.default.rmSync(planSourcePath, { recursive: true, force: true });
|
|
2461
|
+
pendingMirrorDeletes.set(key, {
|
|
2462
|
+
id: projectMountId(deletion.projectId, deletion.branchName),
|
|
2463
|
+
projectId: deletion.projectId,
|
|
2464
|
+
branchName: deletion.branchName,
|
|
2465
|
+
gitDirectory: workspaceShadowRoot,
|
|
2466
|
+
mirrorUrl: canonicalProjectRemoteUrl(baseUrl, deletion.projectId),
|
|
2467
|
+
mirrorAuth: gitAuthForRemote(canonicalProjectRemoteUrl(baseUrl, deletion.projectId), bearerAuthHeader),
|
|
2468
|
+
sourcePath: deletion.managedPath,
|
|
2469
|
+
workspaceRelativePath: workspaceProjectRelativePath(deletion.projectId, deletion.branchName),
|
|
2470
|
+
planSourcePath,
|
|
2471
|
+
planWorkspaceRelativePath: workspacePlanRelativePath(deletion.projectId, deletion.branchName),
|
|
2472
|
+
tombstoneId: deletion.tombstoneId
|
|
2473
|
+
});
|
|
2509
2474
|
}
|
|
2510
2475
|
};
|
|
2511
|
-
const
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2476
|
+
const pruneStaleOuterWorkspaceEntries = (entries) => {
|
|
2477
|
+
const remove = (candidate) => {
|
|
2478
|
+
if (candidate) import_node_fs.default.rmSync(candidate, { recursive: true, force: true });
|
|
2479
|
+
};
|
|
2480
|
+
for (const entry of entries) {
|
|
2481
|
+
if (entry.kind === "branch") {
|
|
2482
|
+
projectWorkspaceState = projectWorkspaceStateStore.beginBranchDeletion({
|
|
2483
|
+
projectId: entry.projectId,
|
|
2484
|
+
branchName: entry.branchName
|
|
2485
|
+
});
|
|
2486
|
+
remove(entry.projectTreePath);
|
|
2487
|
+
remove(entry.planTreePath);
|
|
2488
|
+
continue;
|
|
2489
|
+
}
|
|
2490
|
+
remove(entry.projectTreePath);
|
|
2491
|
+
remove(entry.planTreePath);
|
|
2492
|
+
for (const branch of entry.branches) {
|
|
2493
|
+
remove(branch.projectTreePath);
|
|
2494
|
+
remove(branch.planTreePath);
|
|
2495
|
+
}
|
|
2528
2496
|
}
|
|
2529
2497
|
};
|
|
2530
|
-
const
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
if (
|
|
2544
|
-
if (
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2498
|
+
const effectiveWorkerProjects = (projects) => {
|
|
2499
|
+
const incomingById = new Map(projects.map((project) => [project.projectId, project]));
|
|
2500
|
+
const pending = new Set(
|
|
2501
|
+
projectWorkspaceState.locallyPendingCreatedBranches.map(({ projectId, branchName }) => projectBranchKey(projectId, branchName))
|
|
2502
|
+
);
|
|
2503
|
+
return projectWorkspaceState.effectiveDesiredProjects.flatMap((layout) => {
|
|
2504
|
+
const incoming = incomingById.get(layout.projectId);
|
|
2505
|
+
if (!incoming) return [];
|
|
2506
|
+
const existing = projectConfigById.get(layout.projectId);
|
|
2507
|
+
const branches = layout.branches.flatMap(({ branchName }) => {
|
|
2508
|
+
const configured = incoming.branches.find((branch) => branch.branchName === branchName);
|
|
2509
|
+
if (configured) return [configured];
|
|
2510
|
+
const retained = existing?.branches.find((branch) => branch.branchName === branchName);
|
|
2511
|
+
if (retained) return [retained];
|
|
2512
|
+
if (!pending.has(projectBranchKey(layout.projectId, branchName))) return [];
|
|
2513
|
+
const branchPath = configuredProjectBranchPath(
|
|
2514
|
+
projectsRoot,
|
|
2515
|
+
{ ...incoming, checkoutPathSegments: layout.checkoutPathSegments },
|
|
2516
|
+
branchName
|
|
2517
|
+
);
|
|
2518
|
+
if (!hasProjectWorktree(branchPath)) return [];
|
|
2519
|
+
return [
|
|
2520
|
+
{
|
|
2521
|
+
branchName,
|
|
2522
|
+
sourceBranchName: null,
|
|
2523
|
+
baseCommitHash: runGit(["rev-parse", "HEAD"], { cwd: branchPath })
|
|
2556
2524
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2525
|
+
];
|
|
2526
|
+
});
|
|
2527
|
+
return [{ ...incoming, checkoutPathSegments: [...layout.checkoutPathSegments], branches }];
|
|
2528
|
+
});
|
|
2529
|
+
};
|
|
2530
|
+
const buildWorkspaceMounts = () => {
|
|
2531
|
+
const mounts = [];
|
|
2532
|
+
for (const project of [...projectConfigById.values()].sort((left, right) => left.projectId.localeCompare(right.projectId))) {
|
|
2533
|
+
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
2534
|
+
for (const branch of [...project.branches].sort((left, right) => left.branchName.localeCompare(right.branchName))) {
|
|
2535
|
+
const branchPath = configuredProjectBranchPath(projectsRoot, project, branch.branchName);
|
|
2536
|
+
mounts.push({
|
|
2537
|
+
id: projectMountId(project.projectId, branch.branchName),
|
|
2538
|
+
sourcePath: branchPath,
|
|
2539
|
+
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branch.branchName),
|
|
2540
|
+
sourceMode: "git",
|
|
2541
|
+
hydrateDeletionMode: "git",
|
|
2542
|
+
busy: () => hasProjectWorktree(branchPath) && (0, import_project_worktrees.projectWorktreeOperationInProgress)(branchPath)
|
|
2543
|
+
});
|
|
2544
|
+
mounts.push({
|
|
2545
|
+
id: projectPlanMountId(project.projectId, branch.branchName),
|
|
2546
|
+
sourcePath: import_node_path.default.join(planRoot, project.projectId, ...branch.branchName.split("/")),
|
|
2547
|
+
workspaceRelativePath: workspacePlanRelativePath(project.projectId, branch.branchName),
|
|
2548
|
+
sourceMode: "all",
|
|
2549
|
+
hydrateDeletionMode: "all"
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
void projectRoot;
|
|
2553
|
+
}
|
|
2554
|
+
mounts.push({
|
|
2555
|
+
id: "workspace-plans",
|
|
2556
|
+
sourcePath: import_node_path.default.join(planRoot, "workspace"),
|
|
2557
|
+
workspaceRelativePath: "workspace-plans",
|
|
2558
|
+
sourceMode: "all",
|
|
2559
|
+
hydrateDeletionMode: "all"
|
|
2560
|
+
});
|
|
2561
|
+
for (const deletion of pendingMirrorDeletes.values()) {
|
|
2562
|
+
mounts.push({
|
|
2563
|
+
id: deletion.id,
|
|
2564
|
+
sourcePath: deletion.sourcePath,
|
|
2565
|
+
workspaceRelativePath: deletion.workspaceRelativePath,
|
|
2566
|
+
sourceMode: "git",
|
|
2567
|
+
hydrateDeletionMode: "git",
|
|
2568
|
+
deleteWhenSourceMissing: true
|
|
2569
|
+
});
|
|
2570
|
+
mounts.push({
|
|
2571
|
+
id: `${deletion.id}:plan`,
|
|
2572
|
+
sourcePath: deletion.planSourcePath,
|
|
2573
|
+
workspaceRelativePath: deletion.planWorkspaceRelativePath,
|
|
2574
|
+
sourceMode: "all",
|
|
2575
|
+
hydrateDeletionMode: "all",
|
|
2576
|
+
deleteWhenSourceMissing: true
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
return mounts;
|
|
2580
|
+
};
|
|
2581
|
+
const workspaceLocalHead = () => import_node_fs.default.existsSync(import_node_path.default.join(workspaceShadowRoot, ".git")) && tryGit(["rev-parse", "--verify", "HEAD"], { cwd: workspaceShadowRoot }) ? runGit(["rev-parse", "HEAD"], { cwd: workspaceShadowRoot }) : null;
|
|
2582
|
+
const workspaceGitStatus = () => {
|
|
2583
|
+
try {
|
|
2584
|
+
return runGit(["status", "--porcelain"], { cwd: workspaceShadowRoot });
|
|
2585
|
+
} catch {
|
|
2586
|
+
return "";
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
const affectedProjects = (paths) => [
|
|
2590
|
+
...new Set(
|
|
2591
|
+
paths.flatMap((filePath) => {
|
|
2592
|
+
const match = /^(?:projects|plans)\/([^/]+)\//.exec(filePath);
|
|
2593
|
+
return match?.[1] ? [match[1]] : [];
|
|
2594
|
+
})
|
|
2595
|
+
)
|
|
2596
|
+
].sort();
|
|
2597
|
+
const ensureConfiguredProjects = () => {
|
|
2598
|
+
for (const project of projectConfigById.values()) {
|
|
2599
|
+
validateProjectId(project.projectId);
|
|
2600
|
+
for (const branch of project.branches) validateBranchName(branch.branchName);
|
|
2601
|
+
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
2602
|
+
const configFingerprint = (0, import_project_worktrees.projectWorktreeConfigurationFingerprint)({ ...project, gitIdentity: workspaceGitIdentity });
|
|
2603
|
+
const alreadyReady = readyProjectIds.has(project.projectId) && reconciledProjectConfigFingerprints.get(project.projectId) === configFingerprint && project.branches.every(({ branchName }) => hasProjectWorktree(configuredProjectBranchPath(projectsRoot, project, branchName)));
|
|
2604
|
+
if (alreadyReady) {
|
|
2605
|
+
for (const branch of project.branches) pendingCheckouts.delete(projectBranchKey(project.projectId, branch.branchName));
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
try {
|
|
2609
|
+
const connection = projectConnection(project);
|
|
2610
|
+
const states = (0, import_project_worktrees.ensureProjectWorktrees)({
|
|
2611
|
+
projectRoot,
|
|
2612
|
+
primaryBranchName: primaryProjectBranch(project),
|
|
2613
|
+
branches: project.branches,
|
|
2614
|
+
originUrl: connection.originUrl,
|
|
2615
|
+
originAuth: connection.originAuth,
|
|
2616
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2617
|
+
mirrorAuth: connection.mirrorAuth,
|
|
2618
|
+
credentialHelper: connection.credentialHelper,
|
|
2619
|
+
gitIdentity: workspaceGitIdentity
|
|
2620
|
+
});
|
|
2621
|
+
readyProjectIds.add(project.projectId);
|
|
2622
|
+
reconciledProjectConfigFingerprints.set(project.projectId, configFingerprint);
|
|
2623
|
+
for (const state of states) {
|
|
2624
|
+
pendingCheckouts.delete(projectBranchKey(project.projectId, state.branchName));
|
|
2625
|
+
const key = projectBranchKey(project.projectId, state.branchName);
|
|
2626
|
+
if (!lastObservedProjectHeads.has(key)) lastObservedProjectHeads.set(key, state.mirrorHead);
|
|
2627
|
+
if ((state.ahead ?? 0) > 0) {
|
|
2581
2628
|
process.stderr.write(
|
|
2582
|
-
`[r5d-worker]
|
|
2629
|
+
`[r5d-worker] ${project.projectPath}/${state.branchName} is ${state.ahead} commit(s) ahead of origin; switching workers may require publishing or reconciling those commits
|
|
2583
2630
|
`
|
|
2584
2631
|
);
|
|
2585
|
-
armWorkspacePublication(trigger, { delayMs: 1e3, requestId: options2.requestId });
|
|
2586
2632
|
}
|
|
2587
|
-
return;
|
|
2588
2633
|
}
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2634
|
+
} catch (error) {
|
|
2635
|
+
readyProjectIds.delete(project.projectId);
|
|
2636
|
+
reconciledProjectConfigFingerprints.delete(project.projectId);
|
|
2637
|
+
for (const branch of project.branches) {
|
|
2638
|
+
pendingCheckouts.set(projectBranchKey(project.projectId, branch.branchName), {
|
|
2639
|
+
projectId: project.projectId,
|
|
2640
|
+
branchName: branch.branchName
|
|
2641
|
+
});
|
|
2592
2642
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2643
|
+
process.stderr.write(
|
|
2644
|
+
`[r5d-worker] failed to initialize project ${project.projectPath}: ${error instanceof Error ? error.message : String(error)}
|
|
2645
|
+
`
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
};
|
|
2650
|
+
const collectAheadOfOriginBranches = () => {
|
|
2651
|
+
const aheadBranches = [];
|
|
2652
|
+
for (const project of projectConfigById.values()) {
|
|
2653
|
+
if (!readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
|
|
2654
|
+
const connection = projectConnection(project);
|
|
2655
|
+
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
|
|
2656
|
+
if (!tryGit(["fetch", "--no-recurse-submodules", "--prune", "origin", "+refs/heads/*:refs/remotes/origin/*"], {
|
|
2657
|
+
cwd: primaryPath,
|
|
2658
|
+
auth: connection.originAuth
|
|
2659
|
+
})) {
|
|
2660
|
+
process.stderr.write(`[r5d-worker] could not refresh origin divergence for ${project.projectPath}
|
|
2661
|
+
`);
|
|
2662
|
+
}
|
|
2663
|
+
for (const branch of project.branches) {
|
|
2664
|
+
const branchPath = configuredProjectBranchPath(projectsRoot, project, branch.branchName);
|
|
2665
|
+
if (!hasProjectWorktree(branchPath)) continue;
|
|
2604
2666
|
try {
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
);
|
|
2667
|
+
let ahead = 0;
|
|
2668
|
+
const originRef = `refs/remotes/origin/${branch.branchName}`;
|
|
2669
|
+
if (tryGit(["show-ref", "--verify", "--quiet", originRef], { cwd: branchPath })) {
|
|
2670
|
+
const counts = runGit(["rev-list", "--left-right", "--count", `${originRef}...HEAD`], { cwd: branchPath }).split(/\s+/).map(Number);
|
|
2671
|
+
ahead = Number.isSafeInteger(counts[1]) ? counts[1] : 0;
|
|
2672
|
+
} else if (tryGit(["cat-file", "-e", `${branch.baseCommitHash}^{commit}`], { cwd: branchPath })) {
|
|
2673
|
+
const count = Number(runGit(["rev-list", "--count", `${branch.baseCommitHash}..HEAD`], { cwd: branchPath }));
|
|
2674
|
+
ahead = Number.isSafeInteger(count) ? count : 0;
|
|
2675
|
+
}
|
|
2676
|
+
if (ahead > 0) aheadBranches.push({ projectId: project.projectId, branchName: branch.branchName, ahead });
|
|
2615
2677
|
} catch (error) {
|
|
2616
|
-
pendingPublication = void 0;
|
|
2617
|
-
workspaceConvergence.defer();
|
|
2618
2678
|
process.stderr.write(
|
|
2619
|
-
`[r5d-worker]
|
|
2679
|
+
`[r5d-worker] could not measure origin divergence for ${project.projectPath}/${branch.branchName}: ${error instanceof Error ? error.message : String(error)}
|
|
2620
2680
|
`
|
|
2621
2681
|
);
|
|
2622
|
-
armWorkspacePublication(trigger, { delayMs: 1e3, ...options2.requestId ? { requestId: options2.requestId } : {} });
|
|
2623
2682
|
}
|
|
2624
|
-
}
|
|
2625
|
-
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
return aheadBranches.sort(
|
|
2686
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
2626
2687
|
);
|
|
2627
|
-
workspacePublicationTimer.unref();
|
|
2628
2688
|
};
|
|
2629
|
-
const
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2689
|
+
const pushChangedProjectHeads = (activeMountIds, publishedHead) => {
|
|
2690
|
+
const active = new Set(activeMountIds);
|
|
2691
|
+
for (const project of projectConfigById.values()) {
|
|
2692
|
+
if (!readyProjectIds.has(project.projectId)) continue;
|
|
2693
|
+
const changed = /* @__PURE__ */ new Set();
|
|
2694
|
+
for (const branch of project.branches) {
|
|
2695
|
+
if (!active.has(projectMountId(project.projectId, branch.branchName))) continue;
|
|
2696
|
+
const branchPath = configuredProjectBranchPath(projectsRoot, project, branch.branchName);
|
|
2697
|
+
if (!hasProjectWorktree(branchPath)) continue;
|
|
2698
|
+
const head = runGit(["rev-parse", "HEAD"], { cwd: branchPath });
|
|
2699
|
+
if (lastObservedProjectHeads.get(projectBranchKey(project.projectId, branch.branchName)) !== head) {
|
|
2700
|
+
changed.add(branch.branchName);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
if (changed.size === 0) continue;
|
|
2704
|
+
const connection = projectConnection(project);
|
|
2705
|
+
const results = (0, import_project_worktrees.pushProjectMirrorHeads)({
|
|
2706
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
2707
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2708
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2709
|
+
mirrorAuth: connection.mirrorAuth,
|
|
2710
|
+
onlyBranches: changed
|
|
2711
|
+
});
|
|
2712
|
+
for (const result of results) {
|
|
2713
|
+
if (result.pushed) lastObservedProjectHeads.set(projectBranchKey(project.projectId, result.branchName), result.head);
|
|
2714
|
+
}
|
|
2636
2715
|
}
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
);
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
if (
|
|
2652
|
-
|
|
2716
|
+
const publishedTombstoneIds = [
|
|
2717
|
+
...new Set(
|
|
2718
|
+
[...pendingMirrorDeletes.values()].flatMap(
|
|
2719
|
+
(deletion) => active.has(deletion.id) && deletion.tombstoneId ? [deletion.tombstoneId] : []
|
|
2720
|
+
)
|
|
2721
|
+
)
|
|
2722
|
+
];
|
|
2723
|
+
if (publishedTombstoneIds.length > 0) {
|
|
2724
|
+
projectWorkspaceState = projectWorkspaceStateStore.recordTreePublication({
|
|
2725
|
+
tombstoneIds: publishedTombstoneIds,
|
|
2726
|
+
publishedHead
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
for (const [key, deletion] of pendingMirrorDeletes) {
|
|
2730
|
+
if (!active.has(deletion.id)) continue;
|
|
2731
|
+
if (!deletion.projectDeleted) {
|
|
2732
|
+
(0, import_project_worktrees.deleteProjectMirrorBranch)({
|
|
2733
|
+
gitDirectory: deletion.gitDirectory,
|
|
2734
|
+
branchName: deletion.branchName,
|
|
2735
|
+
mirrorUrl: deletion.mirrorUrl,
|
|
2736
|
+
mirrorAuth: deletion.mirrorAuth
|
|
2737
|
+
});
|
|
2653
2738
|
}
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2739
|
+
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
2740
|
+
projectWorkspaceState = projectWorkspaceStateStore.recordMirrorRefDeletion({
|
|
2741
|
+
tombstoneId: deletion.tombstoneId,
|
|
2742
|
+
branchName: deletion.branchName
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
pendingMirrorDeletes.delete(key);
|
|
2746
|
+
lastObservedProjectHeads.delete(key);
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2749
|
+
const refreshProjectHeadsAfterInboundWorkspace = () => {
|
|
2750
|
+
for (const project of projectConfigById.values()) {
|
|
2751
|
+
if (!readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
|
|
2752
|
+
try {
|
|
2753
|
+
const connection = projectConnection(project);
|
|
2754
|
+
const refreshed = (0, import_project_worktrees.fastForwardProjectHeadsFromMirror)({
|
|
2755
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
2756
|
+
primaryBranchName: primaryProjectBranch(project),
|
|
2757
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2758
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2759
|
+
mirrorAuth: connection.mirrorAuth
|
|
2760
|
+
});
|
|
2761
|
+
for (const state of refreshed) {
|
|
2762
|
+
lastObservedProjectHeads.set(
|
|
2763
|
+
projectBranchKey(project.projectId, state.branchName),
|
|
2764
|
+
state.fastForwarded && state.mirrorHead ? state.mirrorHead : state.previousHead
|
|
2765
|
+
);
|
|
2766
|
+
}
|
|
2767
|
+
} catch (error) {
|
|
2768
|
+
process.stderr.write(
|
|
2769
|
+
`[r5d-worker] failed to refresh project heads after workspace update: ${error instanceof Error ? error.message : String(error)}
|
|
2659
2770
|
`
|
|
2660
|
-
|
|
2661
|
-
} finally {
|
|
2662
|
-
workspaceHeadConvergenceInFlight = false;
|
|
2663
|
-
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
2664
|
-
if (pendingWorkspaceHead !== void 0) {
|
|
2665
|
-
setTimeout(() => void convergeAvailableWorkspaceHead(), 1e3).unref();
|
|
2771
|
+
);
|
|
2666
2772
|
}
|
|
2667
2773
|
}
|
|
2668
2774
|
};
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2775
|
+
const reseedProjectHeadsAfterWorkspaceReset = () => {
|
|
2776
|
+
for (const project of projectConfigById.values()) {
|
|
2777
|
+
if (!readyProjectIds.has(project.projectId)) continue;
|
|
2778
|
+
const connection = projectConnection(project);
|
|
2779
|
+
const states = (0, import_project_worktrees.ensureProjectWorktrees)({
|
|
2780
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
2781
|
+
primaryBranchName: primaryProjectBranch(project),
|
|
2782
|
+
branches: project.branches,
|
|
2783
|
+
originUrl: connection.originUrl,
|
|
2784
|
+
originAuth: connection.originAuth,
|
|
2785
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2786
|
+
mirrorAuth: connection.mirrorAuth,
|
|
2787
|
+
credentialHelper: connection.credentialHelper,
|
|
2788
|
+
gitIdentity: workspaceGitIdentity
|
|
2789
|
+
});
|
|
2790
|
+
for (const state of states) {
|
|
2791
|
+
lastObservedProjectHeads.set(projectBranchKey(project.projectId, state.branchName), state.head);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
const mapWorkspaceGitResult = (attemptId, trigger, result) => ({
|
|
2796
|
+
type: "workspace_sync",
|
|
2797
|
+
attemptId,
|
|
2798
|
+
workerLabel: label,
|
|
2799
|
+
trigger,
|
|
2800
|
+
outcome: result.outcome === "pushed" ? "published" : result.outcome,
|
|
2801
|
+
startingHead: result.startingHead,
|
|
2802
|
+
...result.localHead ? { localHead: result.localHead } : {},
|
|
2803
|
+
...result.publishedHead ? { publishedHead: result.publishedHead } : {},
|
|
2804
|
+
rebaseCount: result.rebaseCount,
|
|
2805
|
+
diffSizeBytes: result.diffSizeBytes,
|
|
2806
|
+
gitStatus: workspaceGitStatus(),
|
|
2807
|
+
affectedProjects: affectedProjects(result.affectedPaths),
|
|
2808
|
+
affectedPaths: result.affectedPaths,
|
|
2809
|
+
discardedPaths: [],
|
|
2810
|
+
localChangesDiscarded: false,
|
|
2811
|
+
...result.conflictPaths ? { conflictPaths: result.conflictPaths } : {},
|
|
2812
|
+
...result.conflictSnapshotRefs ? { conflictSnapshotRefs: result.conflictSnapshotRefs } : {},
|
|
2813
|
+
...result.error ? { error: result.error } : {}
|
|
2681
2814
|
});
|
|
2682
|
-
const
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2815
|
+
const failedWorkspaceSyncResult = (attemptId, trigger, error) => ({
|
|
2816
|
+
type: "workspace_sync",
|
|
2817
|
+
attemptId,
|
|
2818
|
+
workerLabel: label,
|
|
2819
|
+
trigger,
|
|
2820
|
+
outcome: "failed",
|
|
2821
|
+
startingHead: workspaceLocalHead(),
|
|
2822
|
+
...workspaceLocalHead() ? { localHead: workspaceLocalHead() } : {},
|
|
2823
|
+
rebaseCount: 0,
|
|
2824
|
+
diffSizeBytes: 0,
|
|
2825
|
+
gitStatus: workspaceGitStatus(),
|
|
2826
|
+
affectedProjects: [],
|
|
2827
|
+
affectedPaths: [],
|
|
2828
|
+
discardedPaths: [],
|
|
2829
|
+
localChangesDiscarded: false,
|
|
2830
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2831
|
+
});
|
|
2832
|
+
const performWorkspaceSync = async (input) => {
|
|
2833
|
+
if (!workspaceRemoteUrl || !workspaceGitIdentity) throw new Error("Worker workspace configuration has not been received");
|
|
2834
|
+
const mounts = buildWorkspaceMounts();
|
|
2835
|
+
const remoteAuth = gitAuthForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2836
|
+
const credentialHelper = credentialHelperForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2837
|
+
if (input.resetToCanonical) {
|
|
2838
|
+
const reset = (0, import_workspace_git_sync.resetWorkspaceGit)({
|
|
2839
|
+
workspacePath: workspaceShadowRoot,
|
|
2840
|
+
remoteUrl: workspaceRemoteUrl,
|
|
2841
|
+
remoteAuth,
|
|
2842
|
+
credentialHelper,
|
|
2843
|
+
gitIdentity: workspaceGitIdentity,
|
|
2844
|
+
mounts
|
|
2845
|
+
});
|
|
2846
|
+
reseedProjectHeadsAfterWorkspaceReset();
|
|
2847
|
+
return {
|
|
2848
|
+
type: "workspace_sync",
|
|
2849
|
+
attemptId: input.attemptId,
|
|
2850
|
+
workerLabel: label,
|
|
2851
|
+
trigger: input.trigger,
|
|
2852
|
+
outcome: "reset",
|
|
2853
|
+
startingHead: reset.startingHead,
|
|
2854
|
+
...reset.localHead ? { localHead: reset.localHead } : {},
|
|
2855
|
+
...reset.remoteHead ? { publishedHead: reset.remoteHead } : {},
|
|
2856
|
+
rebaseCount: 0,
|
|
2857
|
+
diffSizeBytes: 0,
|
|
2858
|
+
gitStatus: workspaceGitStatus(),
|
|
2859
|
+
affectedProjects: affectedProjects(reset.discardedPaths),
|
|
2860
|
+
affectedPaths: reset.discardedPaths,
|
|
2861
|
+
discardedPaths: reset.discardedPaths,
|
|
2862
|
+
localChangesDiscarded: reset.discardedPaths.length > 0 || reset.startingHead !== reset.remoteHead
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
2866
|
+
const result = await (0, import_workspace_git_sync.synchronizeWorkspaceGit)({
|
|
2867
|
+
attemptId: input.attemptId,
|
|
2868
|
+
workerLabel: label,
|
|
2869
|
+
workspacePath: workspaceShadowRoot,
|
|
2870
|
+
remoteUrl: workspaceRemoteUrl,
|
|
2871
|
+
remoteAuth,
|
|
2872
|
+
credentialHelper,
|
|
2873
|
+
gitIdentity: workspaceGitIdentity,
|
|
2874
|
+
mounts,
|
|
2875
|
+
commitDetail: input.confirmationReason ?? input.trigger.detail,
|
|
2876
|
+
allowLargeDiff: input.confirmedLargeDiff,
|
|
2877
|
+
skipMountMirror: outerRemediation,
|
|
2878
|
+
skipMountHydration: outerRemediation,
|
|
2879
|
+
afterWorkspacePublished: outerRemediation ? void 0 : ({ activeMountIds, publishedHead }) => {
|
|
2880
|
+
pushChangedProjectHeads(activeMountIds, publishedHead);
|
|
2690
2881
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2882
|
+
});
|
|
2883
|
+
if (["no_change", "updated", "pushed"].includes(result.outcome) && !outerRemediation) {
|
|
2884
|
+
refreshProjectHeadsAfterInboundWorkspace();
|
|
2885
|
+
}
|
|
2886
|
+
return mapWorkspaceGitResult(input.attemptId, input.trigger, result);
|
|
2887
|
+
};
|
|
2888
|
+
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2889
|
+
try {
|
|
2890
|
+
sendWorkerMessage(ws, {
|
|
2891
|
+
type: "workspace_sync_result",
|
|
2892
|
+
...requestId ? { requestId } : {},
|
|
2893
|
+
result
|
|
2700
2894
|
});
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2895
|
+
} catch (error) {
|
|
2896
|
+
process.stderr.write(
|
|
2897
|
+
`[r5d-worker] failed to send workspace sync result: ${error instanceof Error ? error.message : String(error)}
|
|
2898
|
+
`
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
};
|
|
2902
|
+
const runWorkspaceSync = async (input) => {
|
|
2903
|
+
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
2904
|
+
const requestedAt = Date.now();
|
|
2905
|
+
let queueEnteredAt = requestedAt;
|
|
2906
|
+
let syncStartedAt = requestedAt;
|
|
2907
|
+
workspaceSyncRequestsInFlight += 1;
|
|
2908
|
+
let result;
|
|
2909
|
+
try {
|
|
2910
|
+
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2911
|
+
queueEnteredAt = Date.now();
|
|
2912
|
+
syncStartedAt = Date.now();
|
|
2913
|
+
try {
|
|
2914
|
+
return await performWorkspaceSync({
|
|
2915
|
+
attemptId,
|
|
2916
|
+
trigger: input.trigger,
|
|
2917
|
+
confirmedLargeDiff: input.confirmedLargeDiff,
|
|
2918
|
+
confirmationReason: input.confirmationReason,
|
|
2919
|
+
resetToCanonical: input.resetToCanonical
|
|
2920
|
+
});
|
|
2921
|
+
} catch (error) {
|
|
2922
|
+
return failedWorkspaceSyncResult(attemptId, input.trigger, error);
|
|
2923
|
+
}
|
|
2713
2924
|
});
|
|
2714
|
-
|
|
2925
|
+
} finally {
|
|
2926
|
+
workspaceSyncRequestsInFlight -= 1;
|
|
2715
2927
|
}
|
|
2716
|
-
|
|
2928
|
+
const finishedAt = Date.now();
|
|
2929
|
+
result = {
|
|
2930
|
+
...result,
|
|
2931
|
+
telemetry: {
|
|
2932
|
+
totalMs: finishedAt - requestedAt,
|
|
2933
|
+
queueMs: queueEnteredAt - requestedAt,
|
|
2934
|
+
prepareMs: 0,
|
|
2935
|
+
synchronizeMs: finishedAt - syncStartedAt
|
|
2936
|
+
}
|
|
2937
|
+
};
|
|
2938
|
+
if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result);
|
|
2939
|
+
return result;
|
|
2717
2940
|
};
|
|
2718
|
-
const
|
|
2719
|
-
|
|
2941
|
+
const scheduleAutomaticWorkspaceSync = (trigger, delayMs = WORKSPACE_GIT_QUIET_MS) => {
|
|
2942
|
+
pendingAutomaticTrigger = trigger;
|
|
2943
|
+
if (!workspaceConfigured || activeWorkspaceIncidentId || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight) {
|
|
2720
2944
|
return;
|
|
2721
2945
|
}
|
|
2722
|
-
|
|
2946
|
+
if (workspaceAutomaticTimer) clearTimeout(workspaceAutomaticTimer);
|
|
2947
|
+
workspaceAutomaticTimer = setTimeout(
|
|
2723
2948
|
() => {
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
void workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2733
|
-
if (!workspaceReady || activeWorkspaceIncidentId || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
2734
|
-
return;
|
|
2735
|
-
}
|
|
2736
|
-
const targets = [...pendingManifestCheckouts.values()];
|
|
2737
|
-
ensureVisibleWorktrees(targets, { strictCanonicalRevalidation: true });
|
|
2738
|
-
for (const target of targets) {
|
|
2739
|
-
const manifest = manifestByProjectId.get(target.projectId);
|
|
2740
|
-
if (!manifest || !manifest.branches.includes(target.branchName)) continue;
|
|
2741
|
-
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, target.projectId, manifestByProjectId), target.branchName);
|
|
2742
|
-
if (hasNormalVisibleGitDir(branchPath)) {
|
|
2743
|
-
pendingManifestCheckouts.delete(manifestCheckoutKey(target.projectId, target.branchName));
|
|
2744
|
-
}
|
|
2949
|
+
workspaceAutomaticTimer = void 0;
|
|
2950
|
+
const scheduledTrigger = pendingAutomaticTrigger;
|
|
2951
|
+
pendingAutomaticTrigger = void 0;
|
|
2952
|
+
if (!scheduledTrigger || !workspaceConfigured || activeWorkspaceIncidentId || currentWorkerSocket !== ws) return;
|
|
2953
|
+
automaticSyncInFlight = true;
|
|
2954
|
+
void runWorkspaceSync({ trigger: scheduledTrigger }).then((result) => {
|
|
2955
|
+
if (result.outcome === "failed") {
|
|
2956
|
+
pendingAutomaticTrigger = scheduledTrigger;
|
|
2745
2957
|
}
|
|
2746
|
-
}).then(() => sendWorkspaceReady()).catch((error) => {
|
|
2747
|
-
process.stderr.write(
|
|
2748
|
-
`[r5d-worker] pending manifest checkout hydration failed: ${error instanceof Error ? error.message : String(error)}
|
|
2749
|
-
`
|
|
2750
|
-
);
|
|
2751
2958
|
}).finally(() => {
|
|
2752
|
-
|
|
2959
|
+
automaticSyncInFlight = false;
|
|
2753
2960
|
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
2754
|
-
if (
|
|
2961
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
2962
|
+
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, WORKSPACE_GIT_QUIET_MS);
|
|
2963
|
+
}
|
|
2755
2964
|
});
|
|
2756
2965
|
},
|
|
2757
2966
|
Math.max(0, delayMs)
|
|
2758
2967
|
);
|
|
2759
|
-
|
|
2968
|
+
workspaceAutomaticTimer.unref();
|
|
2760
2969
|
};
|
|
2761
|
-
const
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2970
|
+
const markWorkspaceDirty = (trigger, force = false) => {
|
|
2971
|
+
scheduleAutomaticWorkspaceSync(trigger, force ? 0 : WORKSPACE_GIT_QUIET_MS);
|
|
2972
|
+
};
|
|
2973
|
+
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
2974
|
+
const resolveMessageTarget = (target) => resolveWorkerSessionTarget({
|
|
2975
|
+
target,
|
|
2976
|
+
projectsRoot,
|
|
2977
|
+
workspaceShadowRoot,
|
|
2978
|
+
projectConfigById
|
|
2979
|
+
});
|
|
2980
|
+
const configureWorkerWorkspace = async (message) => {
|
|
2767
2981
|
workspaceSyncRequestsInFlight += 1;
|
|
2768
2982
|
try {
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
);
|
|
2778
|
-
|
|
2779
|
-
|
|
2983
|
+
return await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2984
|
+
workspaceConfigured = false;
|
|
2985
|
+
configureGitHubAuth(message.githubCredential);
|
|
2986
|
+
visibleGitIdentity = message.gitIdentity;
|
|
2987
|
+
workspaceGitIdentity = message.gitIdentity;
|
|
2988
|
+
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2989
|
+
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
2990
|
+
desiredProjects: message.projects
|
|
2991
|
+
});
|
|
2992
|
+
(0, import_project_workspace_state.pruneAuthoritativelyDesiredBranchDeletions)(pendingMirrorDeletes, message.projects);
|
|
2993
|
+
const effectiveProjects = effectiveWorkerProjects(message.projects).map((project) => ({
|
|
2994
|
+
...project,
|
|
2995
|
+
branches: project.branches.filter(({ branchName }) => !pendingMirrorDeletes.has(projectBranchKey(project.projectId, branchName)))
|
|
2996
|
+
}));
|
|
2997
|
+
const nextProjectConfigById = new Map(effectiveProjects.map((project) => [project.projectId, project]));
|
|
2998
|
+
for (const existingProject of projectConfigById.values()) {
|
|
2999
|
+
const nextProject = nextProjectConfigById.get(existingProject.projectId);
|
|
3000
|
+
if (!nextProject) {
|
|
3001
|
+
stageProjectDeletion(existingProject);
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
const nextBranches = new Set(nextProject.branches.map(({ branchName }) => branchName));
|
|
3005
|
+
for (const existingBranch of existingProject.branches) {
|
|
3006
|
+
if (!nextBranches.has(existingBranch.branchName)) {
|
|
3007
|
+
stageProjectBranchDeletion(existingProject, existingBranch.branchName, false);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
projectConfigById.clear();
|
|
3012
|
+
for (const project of effectiveProjects) projectConfigById.set(project.projectId, project);
|
|
3013
|
+
const configuredKeys = new Set(
|
|
3014
|
+
effectiveProjects.flatMap((project) => project.branches.map(({ branchName }) => projectBranchKey(project.projectId, branchName)))
|
|
2780
3015
|
);
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
if (shouldEnsureCheckouts) {
|
|
2784
|
-
checkoutTargets = workspaceSyncCheckoutTargets({
|
|
2785
|
-
projects: syncProjects,
|
|
2786
|
-
pendingTargets: scopedPendingTargets,
|
|
2787
|
-
includeAllManifestCheckouts: trigger.type === "connect",
|
|
2788
|
-
...trigger.canonicalCheckoutOnly && trigger.projectId && trigger.branchName ? { canonicalCheckoutOnly: { projectId: trigger.projectId, branchName: trigger.branchName } } : {}
|
|
2789
|
-
});
|
|
3016
|
+
for (const key of pendingCheckouts.keys()) {
|
|
3017
|
+
if (!configuredKeys.has(key)) pendingCheckouts.delete(key);
|
|
2790
3018
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
strictCanonicalRevalidation: true
|
|
2794
|
-
});
|
|
2795
|
-
const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
|
|
2796
|
-
for (const target of [...overrides.newVisibleCheckouts ?? [], ...createdCheckouts, ...pendingTargets]) {
|
|
2797
|
-
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2798
|
-
if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
|
|
3019
|
+
for (const projectId of [...readyProjectIds]) {
|
|
3020
|
+
if (!projectConfigById.has(projectId)) readyProjectIds.delete(projectId);
|
|
2799
3021
|
}
|
|
2800
|
-
const
|
|
2801
|
-
const
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
3022
|
+
const remoteAuth = gitAuthForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
3023
|
+
const credentialHelper = credentialHelperForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
3024
|
+
(0, import_workspace_git_sync.ensureWorkspaceGitClone)({
|
|
3025
|
+
workspacePath: workspaceShadowRoot,
|
|
3026
|
+
remoteUrl: message.workspaceRemoteUrl,
|
|
3027
|
+
remoteAuth,
|
|
3028
|
+
credentialHelper,
|
|
3029
|
+
gitIdentity: message.gitIdentity
|
|
2805
3030
|
});
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3031
|
+
pruneStaleOuterWorkspaceEntries(projectWorkspaceStateStore.discoverStaleOuterEntries(workspaceShadowRoot));
|
|
3032
|
+
stageDurableWorkspaceDeletions();
|
|
3033
|
+
const mountsBeforeGit = buildWorkspaceMounts();
|
|
3034
|
+
(0, import_workspace_git_sync.hydrateWorkspaceGitMounts)(
|
|
3035
|
+
workspaceShadowRoot,
|
|
3036
|
+
mountsBeforeGit.filter((mount) => !mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath))
|
|
3037
|
+
);
|
|
3038
|
+
ensureConfiguredProjects();
|
|
3039
|
+
let result = await performWorkspaceSync({
|
|
3040
|
+
attemptId: crypto.randomUUID(),
|
|
3041
|
+
trigger: { type: "connect" }
|
|
3042
|
+
});
|
|
3043
|
+
if (["no_change", "published", "updated", "conflict_reset", "reset"].includes(result.outcome)) {
|
|
3044
|
+
const staleAfterInbound = projectWorkspaceStateStore.discoverStaleOuterEntries(workspaceShadowRoot);
|
|
3045
|
+
if (staleAfterInbound.length > 0) {
|
|
3046
|
+
pruneStaleOuterWorkspaceEntries(staleAfterInbound);
|
|
3047
|
+
stageDurableWorkspaceDeletions();
|
|
3048
|
+
result = await performWorkspaceSync({
|
|
3049
|
+
attemptId: crypto.randomUUID(),
|
|
3050
|
+
trigger: { type: "connect" }
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
2812
3053
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
3054
|
+
const publishedHead = result.publishedHead ?? result.localHead ?? result.startingHead;
|
|
3055
|
+
if (publishedHead && ["no_change", "published", "updated", "conflict_reset", "reset"].includes(result.outcome)) {
|
|
3056
|
+
const layoutCleanupIds = projectWorkspaceState.pendingTreeDeletions.flatMap(
|
|
3057
|
+
(deletion) => deletion.kind === "project" && !deletion.projectDeleted ? [deletion.tombstoneId] : []
|
|
3058
|
+
);
|
|
3059
|
+
if (layoutCleanupIds.length > 0) {
|
|
3060
|
+
projectWorkspaceState = projectWorkspaceStateStore.recordTreePublication({
|
|
3061
|
+
tombstoneIds: layoutCleanupIds,
|
|
3062
|
+
publishedHead
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
2823
3065
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
3066
|
+
workspaceConfigured = true;
|
|
3067
|
+
const pending = [...pendingCheckouts.values()].sort(
|
|
3068
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
3069
|
+
);
|
|
3070
|
+
return { result, pending, aheadOfOriginBranches: collectAheadOfOriginBranches() };
|
|
3071
|
+
});
|
|
3072
|
+
} catch (error) {
|
|
3073
|
+
workspaceConfigured = import_node_fs.default.existsSync(import_node_path.default.join(workspaceShadowRoot, ".git"));
|
|
3074
|
+
for (const project of message.projects) {
|
|
3075
|
+
for (const branch of project.branches) {
|
|
3076
|
+
pendingCheckouts.set(projectBranchKey(project.projectId, branch.branchName), {
|
|
3077
|
+
projectId: project.projectId,
|
|
3078
|
+
branchName: branch.branchName
|
|
3079
|
+
});
|
|
2829
3080
|
}
|
|
2830
3081
|
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
const result = {
|
|
2836
|
-
type: "workspace_sync",
|
|
2837
|
-
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2838
|
-
workerLabel: label,
|
|
2839
|
-
trigger,
|
|
2840
|
-
outcome: "failed",
|
|
2841
|
-
startingHead: null,
|
|
2842
|
-
rebaseCount: 0,
|
|
2843
|
-
diffSizeBytes: 0,
|
|
2844
|
-
gitStatus: "",
|
|
2845
|
-
affectedProjects: [],
|
|
2846
|
-
affectedPaths: [],
|
|
2847
|
-
discardedPaths: [],
|
|
2848
|
-
localChangesDiscarded: false,
|
|
2849
|
-
telemetry: {
|
|
2850
|
-
totalMs: completedAt - requestedAt,
|
|
2851
|
-
queueMs: queueEnteredAt - requestedAt,
|
|
2852
|
-
prepareMs: Math.max(0, synchronizeStartedAt - prepareStartedAt),
|
|
2853
|
-
synchronizeMs: Math.max(0, synchronizeFinishedAt - synchronizeStartedAt)
|
|
2854
|
-
},
|
|
2855
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3082
|
+
return {
|
|
3083
|
+
result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
|
|
3084
|
+
pending: [...pendingCheckouts.values()],
|
|
3085
|
+
aheadOfOriginBranches: []
|
|
2856
3086
|
};
|
|
2857
|
-
sendWorkspaceSyncResult(authority, result);
|
|
2858
|
-
return result;
|
|
2859
3087
|
} finally {
|
|
2860
3088
|
workspaceSyncRequestsInFlight -= 1;
|
|
2861
3089
|
}
|
|
@@ -2872,17 +3100,26 @@ async function startWorker(options) {
|
|
|
2872
3100
|
const handleGracefulShutdown = () => {
|
|
2873
3101
|
if (shutdownAfterClose) return;
|
|
2874
3102
|
shutdownAfterClose = true;
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
}
|
|
2879
|
-
|
|
2880
|
-
|
|
3103
|
+
if (workspaceAutomaticTimer) {
|
|
3104
|
+
clearTimeout(workspaceAutomaticTimer);
|
|
3105
|
+
workspaceAutomaticTimer = void 0;
|
|
3106
|
+
}
|
|
3107
|
+
void (async () => {
|
|
3108
|
+
if (workspaceConfigured && !activeWorkspaceIncidentId) {
|
|
3109
|
+
await runWorkspaceSync({
|
|
3110
|
+
trigger: { type: "manual", detail: "graceful worker shutdown" }
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
await workspaceSyncSingleFlight.afterCurrent();
|
|
2881
3114
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
2882
3115
|
ws.close(1e3, "Worker shutting down");
|
|
2883
3116
|
} else {
|
|
2884
3117
|
process.exit(0);
|
|
2885
3118
|
}
|
|
3119
|
+
})().catch((error) => {
|
|
3120
|
+
process.stderr.write(`[r5d-worker] graceful workspace sync failed: ${error instanceof Error ? error.message : String(error)}
|
|
3121
|
+
`);
|
|
3122
|
+
ws.close(1011, "Worker shutdown sync failed");
|
|
2886
3123
|
});
|
|
2887
3124
|
};
|
|
2888
3125
|
process.on("SIGTERM", handleGracefulShutdown);
|
|
@@ -2915,11 +3152,7 @@ async function startWorker(options) {
|
|
|
2915
3152
|
r5dctlVersion: (0, import_cli_update.readInstalledCliVersion)("r5dctl"),
|
|
2916
3153
|
capabilities: {
|
|
2917
3154
|
updateClis: true,
|
|
2918
|
-
canonicalResolverCheckout: true,
|
|
2919
3155
|
browserPortForwarding: true,
|
|
2920
|
-
globalWorkspaceSyncLeaseV1: true,
|
|
2921
|
-
eventualWorkspaceSyncV1: true,
|
|
2922
|
-
workspaceIncidentCandidatePinV1: true,
|
|
2923
3156
|
execStdinV1: true
|
|
2924
3157
|
},
|
|
2925
3158
|
projectRoot: projectsRoot,
|
|
@@ -2946,302 +3179,153 @@ async function startWorker(options) {
|
|
|
2946
3179
|
if (message.type === "connected") {
|
|
2947
3180
|
return;
|
|
2948
3181
|
}
|
|
2949
|
-
if (message.type === "
|
|
2950
|
-
const
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
const completed = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2958
|
-
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2959
|
-
const firstBootstrap = manifestFilesystem.firstBootstrap;
|
|
2960
|
-
configureGitHubAuth(message.githubCredential);
|
|
2961
|
-
visibleGitIdentity = message.gitIdentity;
|
|
2962
|
-
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2963
|
-
manifestByProjectId.clear();
|
|
2964
|
-
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2965
|
-
const currentCheckouts = allManifestCheckouts();
|
|
2966
|
-
const currentCheckoutKeys = new Set(
|
|
2967
|
-
currentCheckouts.map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2968
|
-
);
|
|
2969
|
-
for (const key of pendingManifestCheckouts.keys()) {
|
|
2970
|
-
if (!currentCheckoutKeys.has(key)) pendingManifestCheckouts.delete(key);
|
|
2971
|
-
}
|
|
2972
|
-
for (const checkout of currentCheckouts) {
|
|
2973
|
-
const key = manifestCheckoutKey(checkout.projectId, checkout.branchName);
|
|
2974
|
-
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, checkout.projectId, manifestByProjectId), checkout.branchName);
|
|
2975
|
-
if (!hasNormalVisibleGitDir(branchPath)) pendingManifestCheckouts.set(key, checkout);
|
|
2976
|
-
}
|
|
2977
|
-
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2978
|
-
const created = firstBootstrap ? ensureVisibleWorktrees(currentCheckouts, { strictCanonicalRevalidation: true }) : [];
|
|
2979
|
-
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2980
|
-
const bootstrapped = await (0, import_workspace_sync.convergeWorkspaceHead)(workspaceSyncInput({ type: "connect" }, { newVisibleCheckouts: created }), {
|
|
2981
|
-
// An existing checkout becomes routable after a fetch. Its
|
|
2982
|
-
// visible tree converges asynchronously; first bootstrap alone
|
|
2983
|
-
// must materialize the canonical snapshot before readiness.
|
|
2984
|
-
// Re-check the incident immediately before convergence so a
|
|
2985
|
-
// concurrently delivered fence forces fetch-only behavior.
|
|
2986
|
-
hydrateVisible: firstBootstrap && !activeWorkspaceIncidentId
|
|
2987
|
-
});
|
|
2988
|
-
if (!workspaceManifestRevision.isCurrent(revisionToken)) return false;
|
|
2989
|
-
if ((0, import_workspace_convergence.workspaceBootstrapEstablishesVisibleBaseline)({
|
|
2990
|
-
hydrated: bootstrapped.hydrated,
|
|
2991
|
-
firstBootstrap,
|
|
2992
|
-
incidentActive: Boolean(activeWorkspaceIncidentId),
|
|
2993
|
-
currentLocalHead: workspaceConvergence.snapshot().localHead,
|
|
2994
|
-
currentVisibleBaselineHead: workspaceConvergence.visibleBaselineHead()
|
|
2995
|
-
})) {
|
|
2996
|
-
workspaceConvergence.setLocalHead(bootstrapped.localHead);
|
|
2997
|
-
}
|
|
2998
|
-
workspaceConvergence.observeCanonicalHead(bootstrapped.canonicalHead);
|
|
2999
|
-
for (const checkout of currentCheckouts) {
|
|
3000
|
-
const manifest = manifestByProjectId.get(checkout.projectId);
|
|
3001
|
-
if (!manifest) continue;
|
|
3002
|
-
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, checkout.projectId, manifestByProjectId), checkout.branchName);
|
|
3003
|
-
if (hasNormalVisibleGitDir(branchPath)) {
|
|
3004
|
-
pendingManifestCheckouts.delete(manifestCheckoutKey(checkout.projectId, checkout.branchName));
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
process.stdout.write(`[r5d-worker] workspace manifest: ${message.projects.length} projects
|
|
3008
|
-
`);
|
|
3009
|
-
return true;
|
|
3182
|
+
if (message.type === "workspace_config") {
|
|
3183
|
+
const configured = await configureWorkerWorkspace(message);
|
|
3184
|
+
sendWorkerMessage(ws, {
|
|
3185
|
+
type: "workspace_configured",
|
|
3186
|
+
requestId: message.requestId,
|
|
3187
|
+
result: configured.result,
|
|
3188
|
+
pendingCheckouts: configured.pending,
|
|
3189
|
+
aheadOfOriginBranches: configured.aheadOfOriginBranches
|
|
3010
3190
|
});
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
sendWorkspaceReady();
|
|
3014
|
-
schedulePendingManifestCheckoutHydration();
|
|
3015
|
-
if (!workspaceSafetyScan) {
|
|
3016
|
-
workspaceSafetyScan = setInterval(() => {
|
|
3017
|
-
if (!workspaceReady || !workspaceRemoteUrl || activeWorkspaceIncidentId || pendingPublication || workspaceSafetyScanInFlight) {
|
|
3018
|
-
return;
|
|
3019
|
-
}
|
|
3020
|
-
workspaceSafetyScanInFlight = true;
|
|
3021
|
-
void (async () => {
|
|
3022
|
-
const before = workspaceConvergence.snapshot();
|
|
3023
|
-
const hydrateVisible = before.dirtyGeneration === before.publishedGeneration;
|
|
3024
|
-
const converged = await workspaceSyncSingleFlight.runExclusive(
|
|
3025
|
-
() => (0, import_workspace_sync.convergeWorkspaceHead)(workspaceSyncInput({ type: "periodic", detail: "idle convergence scan" }), {
|
|
3026
|
-
hydrateVisible
|
|
3027
|
-
})
|
|
3028
|
-
);
|
|
3029
|
-
workspaceConvergence.observeCanonicalHead(converged.canonicalHead);
|
|
3030
|
-
if (converged.hydrated) workspaceConvergence.setLocalHead(converged.localHead);
|
|
3031
|
-
if (converged.localChanges && hydrateVisible) {
|
|
3032
|
-
markWorkspaceDirty({ type: "periodic", detail: "idle safety scan" });
|
|
3033
|
-
}
|
|
3034
|
-
sendWorkspaceReady();
|
|
3035
|
-
})().catch((error) => {
|
|
3036
|
-
process.stderr.write(
|
|
3037
|
-
`[r5d-worker] idle workspace safety scan failed: ${error instanceof Error ? error.message : String(error)}
|
|
3191
|
+
process.stdout.write(
|
|
3192
|
+
`[r5d-worker] workspace configured: ${message.projects.length} project(s), ${configured.pending.length} pending checkout(s)
|
|
3038
3193
|
`
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
workspaceSafetyScan.unref();
|
|
3194
|
+
);
|
|
3195
|
+
if (!workspacePeriodicTimer) {
|
|
3196
|
+
workspacePeriodicTimer = setInterval(() => {
|
|
3197
|
+
scheduleAutomaticWorkspaceSync({ type: "periodic", detail: "periodic workspace reconciliation" }, 0);
|
|
3198
|
+
}, WORKSPACE_GIT_PERIODIC_MS);
|
|
3199
|
+
workspacePeriodicTimer.unref();
|
|
3046
3200
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
if (workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
3050
|
-
armWorkspacePublication({ type: "periodic", detail: "resume dirty generation after reconnect" }, { delayMs: 0 });
|
|
3201
|
+
if (pendingAutomaticTrigger && !activeWorkspaceIncidentId) {
|
|
3202
|
+
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, 0);
|
|
3051
3203
|
}
|
|
3052
3204
|
return;
|
|
3053
3205
|
}
|
|
3054
3206
|
if (message.type === "sync_workspace") {
|
|
3055
|
-
await
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
{
|
|
3064
|
-
attemptId: message.attemptId,
|
|
3065
|
-
quarantineRef: message.quarantineRef,
|
|
3066
|
-
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
3067
|
-
confirmationReason: message.confirmationReason,
|
|
3068
|
-
expectedIncidentCandidateHead: message.expectedIncidentCandidateHead,
|
|
3069
|
-
resetToCanonical: message.resetToCanonical,
|
|
3070
|
-
skipVisibleMirror: message.skipVisibleMirror
|
|
3071
|
-
}
|
|
3072
|
-
);
|
|
3073
|
-
return;
|
|
3074
|
-
}
|
|
3075
|
-
if (message.type === "workspace_head_available") {
|
|
3076
|
-
workspaceConvergence.observeCanonicalHead(message.canonicalHead);
|
|
3077
|
-
pendingWorkspaceHead = message.canonicalHead;
|
|
3078
|
-
sendWorkspaceReady();
|
|
3079
|
-
void convergeAvailableWorkspaceHead();
|
|
3080
|
-
return;
|
|
3081
|
-
}
|
|
3082
|
-
if (message.type === "workspace_publication_now") {
|
|
3083
|
-
armWorkspacePublication(message.trigger, { delayMs: 0, requestId: message.requestId });
|
|
3207
|
+
await runWorkspaceSync({
|
|
3208
|
+
requestId: message.requestId,
|
|
3209
|
+
attemptId: message.attemptId,
|
|
3210
|
+
trigger: message.trigger,
|
|
3211
|
+
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
3212
|
+
confirmationReason: message.confirmationReason,
|
|
3213
|
+
resetToCanonical: message.resetToCanonical
|
|
3214
|
+
});
|
|
3084
3215
|
return;
|
|
3085
3216
|
}
|
|
3086
|
-
if (message.type === "
|
|
3087
|
-
const publication = pendingPublication;
|
|
3088
|
-
if (!publication || publication.requestId !== message.requestId) {
|
|
3089
|
-
process.stderr.write(`[r5d-worker] ignoring publication authority for unknown request ${message.requestId}
|
|
3090
|
-
`);
|
|
3091
|
-
return;
|
|
3092
|
-
}
|
|
3093
|
-
if (message.status !== "granted") {
|
|
3094
|
-
pendingPublication = void 0;
|
|
3095
|
-
workspaceConvergence.observeCanonicalHead(message.canonicalHead);
|
|
3096
|
-
const applyBlockedResponse = message.status === "blocked" && workspaceIncidentOrdering.permitsBlockedResponse(message.incidentId);
|
|
3097
|
-
if (applyBlockedResponse) {
|
|
3098
|
-
workspaceConvergence.block();
|
|
3099
|
-
if (message.incidentId) activeWorkspaceIncidentId = message.incidentId;
|
|
3100
|
-
} else {
|
|
3101
|
-
workspaceConvergence.resetTransientPublicationState();
|
|
3102
|
-
workspaceConvergence.observeIncidentState(Boolean(activeWorkspaceIncidentId));
|
|
3103
|
-
if (message.status === "busy") {
|
|
3104
|
-
armWorkspacePublication(publication.trigger, {
|
|
3105
|
-
delayMs: message.retryAfterMs ?? 1e3,
|
|
3106
|
-
requestId: publication.requestId
|
|
3107
|
-
});
|
|
3108
|
-
} else if (!activeWorkspaceIncidentId) {
|
|
3109
|
-
const explicit = explicitWorkspacePublications.next();
|
|
3110
|
-
if (explicit) {
|
|
3111
|
-
armWorkspacePublication(explicit.trigger, { delayMs: 0, requestId: explicit.requestId });
|
|
3112
|
-
} else if (workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
3113
|
-
armWorkspacePublication(publication.trigger, { delayMs: 0 });
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
|
-
sendWorkspaceReady();
|
|
3118
|
-
return;
|
|
3119
|
-
}
|
|
3120
|
-
publication.attemptId = message.attemptId;
|
|
3121
|
-
publication.authorityToken = message.authorityToken;
|
|
3122
|
-
publication.workInFlight = true;
|
|
3123
|
-
pendingPublication = publication;
|
|
3124
|
-
if (currentWorkerSocket !== ws) {
|
|
3125
|
-
publication.workInFlight = false;
|
|
3126
|
-
workspaceConvergence.resetTransientPublicationState();
|
|
3127
|
-
return;
|
|
3128
|
-
}
|
|
3129
|
-
workspaceConvergence.beginPreparing();
|
|
3130
|
-
sendWorkspaceReady();
|
|
3131
|
-
let prepareStartedAt = Date.now();
|
|
3132
|
-
let synchronizeStartedAt = prepareStartedAt;
|
|
3133
|
-
let synchronizeStarted = false;
|
|
3134
|
-
let result;
|
|
3217
|
+
if (message.type === "create_project_branch") {
|
|
3135
3218
|
try {
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3219
|
+
projectWorkspaceState = projectWorkspaceStateStore.recordPendingCreatedBranch({
|
|
3220
|
+
projectId: message.projectId,
|
|
3221
|
+
branchName: message.targetBranch
|
|
3222
|
+
});
|
|
3223
|
+
const created = await workspaceSyncSingleFlight.runMutation(() => {
|
|
3224
|
+
const project = projectConfigById.get(message.projectId);
|
|
3225
|
+
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
3226
|
+
if (!readyProjectIds.has(project.projectId)) {
|
|
3227
|
+
throw new Error(`Project ${project.projectPath} is not initialized on this worker`);
|
|
3228
|
+
}
|
|
3229
|
+
if (!project.branches.some(({ branchName }) => branchName === message.sourceBranch)) {
|
|
3230
|
+
throw new Error(`Source branch ${message.sourceBranch} is missing from the worker workspace configuration`);
|
|
3231
|
+
}
|
|
3232
|
+
if (project.branches.some(({ branchName }) => branchName === message.targetBranch)) {
|
|
3233
|
+
throw new Error(`Project branch ${message.targetBranch} already exists`);
|
|
3234
|
+
}
|
|
3235
|
+
const createdBranch = (0, import_project_worktrees.createLinkedProjectBranch)({
|
|
3236
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
3237
|
+
sourceBranchName: message.sourceBranch,
|
|
3238
|
+
branchName: message.targetBranch
|
|
3239
|
+
});
|
|
3240
|
+
projectConfigById.set(project.projectId, {
|
|
3241
|
+
...project,
|
|
3242
|
+
branches: [
|
|
3243
|
+
...project.branches,
|
|
3244
|
+
{
|
|
3245
|
+
branchName: message.targetBranch,
|
|
3246
|
+
sourceBranchName: message.sourceBranch,
|
|
3247
|
+
baseCommitHash: createdBranch.baseCommitHash
|
|
3248
|
+
}
|
|
3249
|
+
]
|
|
3250
|
+
});
|
|
3251
|
+
lastObservedProjectHeads.set(projectBranchKey(project.projectId, message.targetBranch), null);
|
|
3252
|
+
return createdBranch;
|
|
3253
|
+
});
|
|
3254
|
+
sendWorkerMessage(ws, {
|
|
3255
|
+
type: "operation_result",
|
|
3256
|
+
requestId: message.requestId,
|
|
3257
|
+
result: {
|
|
3258
|
+
type: "create_project_branch",
|
|
3259
|
+
branchName: message.targetBranch,
|
|
3260
|
+
baseCommitHash: created.baseCommitHash
|
|
3153
3261
|
}
|
|
3154
|
-
return prepared.outcome === "updated" ? { ...prepared, outcome: "no_change" } : prepared;
|
|
3155
3262
|
});
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
startingHead: message.expectedHead,
|
|
3165
|
-
expectedHead: message.expectedHead,
|
|
3166
|
-
rebaseCount: 0,
|
|
3167
|
-
diffSizeBytes: 0,
|
|
3168
|
-
gitStatus: "",
|
|
3169
|
-
affectedProjects: [],
|
|
3170
|
-
affectedPaths: [],
|
|
3171
|
-
discardedPaths: [],
|
|
3172
|
-
localChangesDiscarded: false,
|
|
3173
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3174
|
-
};
|
|
3175
|
-
}
|
|
3176
|
-
const finishedAt = Date.now();
|
|
3177
|
-
result = {
|
|
3178
|
-
...result,
|
|
3179
|
-
dirtyGeneration: publication.dirtyGeneration,
|
|
3180
|
-
telemetry: (0, import_workspace_convergence.workspacePublicationTelemetry)({
|
|
3181
|
-
requestedAt: publication.requestedAt,
|
|
3182
|
-
prepareStartedAt,
|
|
3183
|
-
synchronizeStartedAt,
|
|
3184
|
-
finishedAt
|
|
3185
|
-
})
|
|
3186
|
-
};
|
|
3187
|
-
workspaceConvergence.beginSubmitting();
|
|
3188
|
-
sendWorkspaceReady();
|
|
3189
|
-
try {
|
|
3190
|
-
ws.send(
|
|
3191
|
-
JSON.stringify({
|
|
3192
|
-
type: "workspace_publication_result",
|
|
3193
|
-
requestId: publication.requestId,
|
|
3194
|
-
attemptId: message.attemptId,
|
|
3195
|
-
authorityToken: message.authorityToken,
|
|
3196
|
-
dirtyGeneration: publication.dirtyGeneration,
|
|
3197
|
-
result
|
|
3198
|
-
})
|
|
3263
|
+
markWorkspaceDirty(
|
|
3264
|
+
{
|
|
3265
|
+
type: "manual",
|
|
3266
|
+
projectId: message.projectId,
|
|
3267
|
+
branchName: message.targetBranch,
|
|
3268
|
+
detail: `created project branch from ${message.sourceBranch}`
|
|
3269
|
+
},
|
|
3270
|
+
true
|
|
3199
3271
|
);
|
|
3200
3272
|
} catch (error) {
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3273
|
+
sendWorkerMessage(ws, {
|
|
3274
|
+
type: "operation_result",
|
|
3275
|
+
requestId: message.requestId,
|
|
3276
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3277
|
+
});
|
|
3206
3278
|
}
|
|
3207
3279
|
return;
|
|
3208
3280
|
}
|
|
3209
|
-
if (message.type === "
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
}
|
|
3216
|
-
pendingPublication = void 0;
|
|
3217
|
-
workspaceConvergence.observeCanonicalHead(message.canonicalHead);
|
|
3218
|
-
if (message.status === "published" || message.status === "no_change") {
|
|
3219
|
-
workspaceConvergence.complete({
|
|
3220
|
-
generation: Math.min(message.publishedGeneration ?? publication.dirtyGeneration, publication.dirtyGeneration),
|
|
3221
|
-
canonicalHead: message.canonicalHead,
|
|
3222
|
-
published: true
|
|
3281
|
+
if (message.type === "delete_project_branch") {
|
|
3282
|
+
try {
|
|
3283
|
+
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
3284
|
+
projectWorkspaceState = projectWorkspaceStateStore.beginBranchDeletion({
|
|
3285
|
+
projectId: message.projectId,
|
|
3286
|
+
branchName: message.branchName
|
|
3223
3287
|
});
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3288
|
+
await workspaceSyncSingleFlight.runMutation(() => {
|
|
3289
|
+
const project = projectConfigById.get(message.projectId);
|
|
3290
|
+
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
3291
|
+
if (pendingMirrorDeletes.has(deletionKey)) return;
|
|
3292
|
+
stageProjectBranchDeletion(project, message.branchName, true);
|
|
3293
|
+
projectConfigById.set(project.projectId, {
|
|
3294
|
+
...project,
|
|
3295
|
+
branches: project.branches.filter(({ branchName }) => branchName !== message.branchName)
|
|
3296
|
+
});
|
|
3297
|
+
});
|
|
3298
|
+
const result = await runWorkspaceSync({
|
|
3299
|
+
trigger: {
|
|
3300
|
+
type: "manual",
|
|
3301
|
+
projectId: message.projectId,
|
|
3302
|
+
branchName: message.branchName,
|
|
3303
|
+
detail: "deleted project branch"
|
|
3304
|
+
},
|
|
3305
|
+
confirmedLargeDiff: true,
|
|
3306
|
+
confirmationReason: "explicit project branch deletion",
|
|
3307
|
+
sendResult: false
|
|
3308
|
+
});
|
|
3309
|
+
if (result.outcome === "failed" || result.outcome === "conflict_blocked" || result.outcome === "large_diff_blocked") {
|
|
3310
|
+
throw new Error(result.error ?? `Project branch deletion workspace sync ended with ${result.outcome}`);
|
|
3311
|
+
}
|
|
3312
|
+
if (pendingMirrorDeletes.has(deletionKey)) {
|
|
3313
|
+
throw new Error("Project branch deletion did not remove the hidden mirror ref");
|
|
3314
|
+
}
|
|
3315
|
+
sendWorkerMessage(ws, {
|
|
3316
|
+
type: "operation_result",
|
|
3317
|
+
requestId: message.requestId,
|
|
3318
|
+
result: {
|
|
3319
|
+
type: "delete_project_branch",
|
|
3320
|
+
branchName: message.branchName
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
3323
|
+
} catch (error) {
|
|
3324
|
+
sendWorkerMessage(ws, {
|
|
3325
|
+
type: "operation_result",
|
|
3326
|
+
requestId: message.requestId,
|
|
3327
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3238
3328
|
});
|
|
3239
|
-
} else if (message.status === "retry" || message.status === "failed" || workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
3240
|
-
armWorkspacePublication(publication.trigger, { delayMs: message.retryAfterMs ?? 1e3 });
|
|
3241
|
-
}
|
|
3242
|
-
if (message.canonicalHead !== workspaceConvergence.snapshot().localHead) {
|
|
3243
|
-
pendingWorkspaceHead = message.canonicalHead;
|
|
3244
|
-
void convergeAvailableWorkspaceHead();
|
|
3245
3329
|
}
|
|
3246
3330
|
return;
|
|
3247
3331
|
}
|
|
@@ -3272,31 +3356,11 @@ async function startWorker(options) {
|
|
|
3272
3356
|
return;
|
|
3273
3357
|
}
|
|
3274
3358
|
if (message.type === "workspace_incident_updated") {
|
|
3275
|
-
workspaceIncidentOrdering.observe(message);
|
|
3276
3359
|
const previousIncidentId = activeWorkspaceIncidentId;
|
|
3277
|
-
|
|
3278
|
-
if (
|
|
3279
|
-
|
|
3280
|
-
}
|
|
3281
|
-
activeWorkspaceIncidentId = nextIncidentId;
|
|
3282
|
-
const releasedHead = (0, import_workspace_incident_state.releasePendingWorkspaceHead)(
|
|
3283
|
-
previousIncidentId,
|
|
3284
|
-
activeWorkspaceIncidentId,
|
|
3285
|
-
message.status === "resolved" || message.status === "confirmed" || message.status === "reset" ? message.incidentId : null
|
|
3286
|
-
);
|
|
3287
|
-
workspaceConvergence.observeIncidentState(Boolean(activeWorkspaceIncidentId));
|
|
3288
|
-
if (releasedHead.fetchAuthoritativeHead) {
|
|
3289
|
-
pendingWorkspaceHead = workspaceConvergence.snapshot().desiredCanonicalHead;
|
|
3290
|
-
void convergeAvailableWorkspaceHead();
|
|
3360
|
+
activeWorkspaceIncidentId = (0, import_workspace_incident_state.applyWorkspaceIncidentUpdate)(activeWorkspaceIncidentId, message);
|
|
3361
|
+
if (previousIncidentId && !activeWorkspaceIncidentId && (message.status === "resolved" || message.status === "confirmed" || message.status === "reset")) {
|
|
3362
|
+
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger ?? { type: "periodic", detail: "resume after workspace incident" }, 0);
|
|
3291
3363
|
}
|
|
3292
|
-
const explicit = !activeWorkspaceIncidentId ? explicitWorkspacePublications.next() : void 0;
|
|
3293
|
-
if (explicit) {
|
|
3294
|
-
armWorkspacePublication(explicit.trigger, { delayMs: 0, requestId: explicit.requestId });
|
|
3295
|
-
} else if (!activeWorkspaceIncidentId && workspaceConvergence.snapshot().dirtyGeneration > workspaceConvergence.snapshot().publishedGeneration) {
|
|
3296
|
-
armWorkspacePublication({ type: "periodic", detail: "resume after workspace incident" }, { delayMs: 0 });
|
|
3297
|
-
}
|
|
3298
|
-
if (!activeWorkspaceIncidentId) schedulePendingManifestCheckoutHydration();
|
|
3299
|
-
sendWorkspaceReady();
|
|
3300
3364
|
return;
|
|
3301
3365
|
}
|
|
3302
3366
|
if (message.type === "exec_terminal_ack") {
|
|
@@ -3487,7 +3551,7 @@ async function startWorker(options) {
|
|
|
3487
3551
|
const activePty = activePtys.get(message.ptyId);
|
|
3488
3552
|
closePty(message);
|
|
3489
3553
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
3490
|
-
|
|
3554
|
+
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true);
|
|
3491
3555
|
}
|
|
3492
3556
|
return;
|
|
3493
3557
|
}
|
|
@@ -3584,7 +3648,7 @@ async function startWorker(options) {
|
|
|
3584
3648
|
});
|
|
3585
3649
|
return;
|
|
3586
3650
|
}
|
|
3587
|
-
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
|
|
3651
|
+
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes" || message.type === "code_list" || message.type === "code_read") {
|
|
3588
3652
|
try {
|
|
3589
3653
|
const result = await (0, import_workspace_command_sync_policy.runWorkspaceCommand)(message.target, workspaceSyncSingleFlight, async () => {
|
|
3590
3654
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
@@ -3644,20 +3708,13 @@ async function startWorker(options) {
|
|
|
3644
3708
|
process.off("SIGTERM", handleGracefulShutdown);
|
|
3645
3709
|
process.off("SIGINT", handleGracefulShutdown);
|
|
3646
3710
|
stopHeartbeatWatchdog();
|
|
3647
|
-
if (
|
|
3648
|
-
clearTimeout(
|
|
3649
|
-
|
|
3650
|
-
}
|
|
3651
|
-
if (!pendingPublication?.workInFlight) {
|
|
3652
|
-
workspaceConvergence.resetTransientPublicationState();
|
|
3653
|
-
}
|
|
3654
|
-
if (workspaceSafetyScan) {
|
|
3655
|
-
clearInterval(workspaceSafetyScan);
|
|
3656
|
-
workspaceSafetyScan = void 0;
|
|
3711
|
+
if (workspaceAutomaticTimer) {
|
|
3712
|
+
clearTimeout(workspaceAutomaticTimer);
|
|
3713
|
+
workspaceAutomaticTimer = void 0;
|
|
3657
3714
|
}
|
|
3658
|
-
if (
|
|
3659
|
-
|
|
3660
|
-
|
|
3715
|
+
if (workspacePeriodicTimer) {
|
|
3716
|
+
clearInterval(workspacePeriodicTimer);
|
|
3717
|
+
workspacePeriodicTimer = void 0;
|
|
3661
3718
|
}
|
|
3662
3719
|
if (terminalReplayTimer) {
|
|
3663
3720
|
clearInterval(terminalReplayTimer);
|
|
@@ -3751,24 +3808,22 @@ if (isCliEntrypoint()) {
|
|
|
3751
3808
|
0 && (module.exports = {
|
|
3752
3809
|
describeWorkerSessionTarget,
|
|
3753
3810
|
editWorkerTextFile,
|
|
3754
|
-
ensureVisibleGitCheckout,
|
|
3755
3811
|
findWorkerFiles,
|
|
3756
3812
|
githubCliEnv,
|
|
3757
3813
|
grepWorkerFiles,
|
|
3758
3814
|
isArtifactEnvPath,
|
|
3815
|
+
listWorkerCodeDirectory,
|
|
3759
3816
|
listWorkerDirectory,
|
|
3760
3817
|
prepareArtifactEnvForShell,
|
|
3761
3818
|
prepareBuiltInToolPathsForTarget,
|
|
3762
3819
|
preparePlanEnvForShell,
|
|
3763
3820
|
prepareShellEnvForTarget,
|
|
3821
|
+
readWorkerCodeFile,
|
|
3764
3822
|
readWorkerImageFile,
|
|
3765
3823
|
readWorkerTextFile,
|
|
3766
3824
|
resolveHostShell,
|
|
3767
3825
|
resolveWorkerFilePath,
|
|
3768
3826
|
resolveWorkerSessionTarget,
|
|
3769
3827
|
syncSessionArtifacts,
|
|
3770
|
-
visibleCheckoutGitTestHarness,
|
|
3771
|
-
visibleCheckoutRemoteFor,
|
|
3772
|
-
workspaceSyncCheckoutTargets,
|
|
3773
3828
|
writeWorkerTextFile
|
|
3774
3829
|
});
|