@ricsam/r5d-worker 0.0.34 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/main.cjs +172 -14
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +170 -14
- package/dist/mjs/package.json +1 -1
- package/dist/types/main.d.ts +37 -6
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(main_exports, {
|
|
|
34
34
|
findWorkerFiles: () => findWorkerFiles,
|
|
35
35
|
githubCliEnv: () => githubCliEnv,
|
|
36
36
|
grepWorkerFiles: () => grepWorkerFiles,
|
|
37
|
+
inspectManagedWorkspace: () => inspectManagedWorkspace,
|
|
37
38
|
isArtifactEnvPath: () => isArtifactEnvPath,
|
|
38
39
|
listWorkerDirectory: () => listWorkerDirectory,
|
|
39
40
|
prepareArtifactEnvForShell: () => prepareArtifactEnvForShell,
|
|
@@ -41,6 +42,7 @@ __export(main_exports, {
|
|
|
41
42
|
readWorkerImageFile: () => readWorkerImageFile,
|
|
42
43
|
readWorkerTextFile: () => readWorkerTextFile,
|
|
43
44
|
resolveHostShell: () => resolveHostShell,
|
|
45
|
+
resolveManagedCheckoutPath: () => resolveManagedCheckoutPath,
|
|
44
46
|
resolveProjectFilePath: () => resolveProjectFilePath,
|
|
45
47
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
46
48
|
selectManifestSyncTargets: () => selectManifestSyncTargets,
|
|
@@ -744,6 +746,11 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
744
746
|
const displayPath = toProjectDisplayPath(resolvedBranchPath, absolutePath2);
|
|
745
747
|
const repoRelativePath2 = displayPath === "." ? "" : displayPath;
|
|
746
748
|
assertAllowedProjectPath(repoRelativePath2, inputPath);
|
|
749
|
+
const realBranchPath2 = import_node_fs.default.existsSync(resolvedBranchPath) ? import_node_fs.default.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
750
|
+
const realTargetPath2 = resolveThroughExistingAncestor(absolutePath2);
|
|
751
|
+
if (!isInsideBranchPath(realBranchPath2, realTargetPath2)) {
|
|
752
|
+
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
753
|
+
}
|
|
747
754
|
return {
|
|
748
755
|
absolutePath: absolutePath2,
|
|
749
756
|
displayPath,
|
|
@@ -762,6 +769,11 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
762
769
|
if (!isInsideBranchPath(resolvedBranchPath, absolutePath)) {
|
|
763
770
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
764
771
|
}
|
|
772
|
+
const realBranchPath = import_node_fs.default.existsSync(resolvedBranchPath) ? import_node_fs.default.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
773
|
+
const realTargetPath = resolveThroughExistingAncestor(absolutePath);
|
|
774
|
+
if (!isInsideBranchPath(realBranchPath, realTargetPath)) {
|
|
775
|
+
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
776
|
+
}
|
|
765
777
|
return {
|
|
766
778
|
absolutePath,
|
|
767
779
|
displayPath: repoRelativePath || ".",
|
|
@@ -773,11 +785,60 @@ function resolveProjectFilePath(branchPath, inputPath) {
|
|
|
773
785
|
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
774
786
|
if (resolved.scope === "host") {
|
|
775
787
|
throw new Error(
|
|
776
|
-
`Project file writes must
|
|
788
|
+
`Project file writes must resolve inside the selected managed checkout: ${inputPath}`
|
|
777
789
|
);
|
|
778
790
|
}
|
|
779
791
|
return resolved;
|
|
780
792
|
}
|
|
793
|
+
function resolveThroughExistingAncestor(inputPath) {
|
|
794
|
+
const suffix = [];
|
|
795
|
+
let cursor = import_node_path.default.resolve(inputPath);
|
|
796
|
+
while (!import_node_fs.default.existsSync(cursor)) {
|
|
797
|
+
const parent = import_node_path.default.dirname(cursor);
|
|
798
|
+
if (parent === cursor) return import_node_path.default.resolve(inputPath);
|
|
799
|
+
suffix.unshift(import_node_path.default.basename(cursor));
|
|
800
|
+
cursor = parent;
|
|
801
|
+
}
|
|
802
|
+
return import_node_path.default.resolve(import_node_fs.default.realpathSync(cursor), ...suffix);
|
|
803
|
+
}
|
|
804
|
+
function resolveManagedCheckoutPath(input) {
|
|
805
|
+
const originManifest = input.manifests.find((manifest) => manifest.projectId === input.originProjectId);
|
|
806
|
+
const candidate = import_node_path.default.isAbsolute(input.inputPath) ? import_node_path.default.resolve(input.inputPath) : import_node_path.default.resolve(
|
|
807
|
+
input.projectsRoot,
|
|
808
|
+
originManifest?.repoSlug ?? input.originProjectId,
|
|
809
|
+
input.originBranchName,
|
|
810
|
+
input.inputPath
|
|
811
|
+
);
|
|
812
|
+
const resolvedCandidate = resolveThroughExistingAncestor(candidate);
|
|
813
|
+
for (const manifest of input.manifests) {
|
|
814
|
+
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
815
|
+
for (const branchName of branches) {
|
|
816
|
+
const checkoutPath = import_node_path.default.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName);
|
|
817
|
+
if (!hasNormalVisibleGitDir(checkoutPath)) continue;
|
|
818
|
+
const resolvedCheckoutPath = import_node_fs.default.existsSync(checkoutPath) ? import_node_fs.default.realpathSync(checkoutPath) : checkoutPath;
|
|
819
|
+
const matchesLexically = isInsideBranchPath(checkoutPath, candidate);
|
|
820
|
+
if (!matchesLexically) continue;
|
|
821
|
+
if (!isInsideBranchPath(resolvedCheckoutPath, resolvedCandidate)) {
|
|
822
|
+
throw new Error(`Managed path escapes its checkout through a symbolic link: ${input.inputPath}`);
|
|
823
|
+
}
|
|
824
|
+
const repoRelativePath = toProjectDisplayPath(resolvedCheckoutPath, resolvedCandidate);
|
|
825
|
+
assertAllowedProjectPath(repoRelativePath === "." ? "" : repoRelativePath, input.inputPath);
|
|
826
|
+
return {
|
|
827
|
+
type: "resolve_managed_path",
|
|
828
|
+
resolved: {
|
|
829
|
+
projectId: manifest.projectId,
|
|
830
|
+
projectPath: manifest.projectPath,
|
|
831
|
+
branchName,
|
|
832
|
+
checkoutPath,
|
|
833
|
+
inputPath: input.inputPath,
|
|
834
|
+
absolutePath: candidate,
|
|
835
|
+
repoRelativePath: repoRelativePath === "." ? "" : repoRelativePath
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return { type: "resolve_managed_path", resolved: null };
|
|
841
|
+
}
|
|
781
842
|
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
782
843
|
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
783
844
|
return remoteUrl;
|
|
@@ -1146,6 +1207,9 @@ async function forceSyncManifestBranchFromInternal(input) {
|
|
|
1146
1207
|
if (!target) {
|
|
1147
1208
|
throw new Error(`Internal remote has neither ${input.branchName} nor main`);
|
|
1148
1209
|
}
|
|
1210
|
+
if (hasInternalWorktreeChanges(context)) {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1149
1213
|
await runInternalGitAsync(context, ["reset", "--hard", target]);
|
|
1150
1214
|
await runInternalGitAsync(context, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1151
1215
|
return getInternalCommitHash(context);
|
|
@@ -1153,10 +1217,6 @@ async function forceSyncManifestBranchFromInternal(input) {
|
|
|
1153
1217
|
function findRepositorySyncBlockers(input) {
|
|
1154
1218
|
const targetByKey = new Map(input.targets.map((target) => [`${target.projectId}:${target.branchName}`, target]));
|
|
1155
1219
|
const blockedBy = [];
|
|
1156
|
-
for (const process2 of input.processes) {
|
|
1157
|
-
const target = targetByKey.get(`${process2.projectId}:${process2.branchName}`);
|
|
1158
|
-
if (target) blockedBy.push({ ...target, kind: "process", id: process2.id });
|
|
1159
|
-
}
|
|
1160
1220
|
for (const shell of input.shells) {
|
|
1161
1221
|
const target = targetByKey.get(`${shell.projectId}:${shell.branchName}`);
|
|
1162
1222
|
if (target) blockedBy.push({ ...target, kind: "shell", id: shell.id });
|
|
@@ -1192,11 +1252,6 @@ async function syncManifestProjectsFromInternal(input) {
|
|
|
1192
1252
|
projectPath: manifest.projectPath,
|
|
1193
1253
|
branchName
|
|
1194
1254
|
})),
|
|
1195
|
-
processes: [...activeProcesses].map(([id, active]) => ({
|
|
1196
|
-
id,
|
|
1197
|
-
projectId: active.projectId,
|
|
1198
|
-
branchName: active.branchName
|
|
1199
|
-
})),
|
|
1200
1255
|
shells: [...activePtys].map(([id, active]) => ({
|
|
1201
1256
|
id,
|
|
1202
1257
|
projectId: active.projectId,
|
|
@@ -1219,6 +1274,16 @@ async function syncManifestProjectsFromInternal(input) {
|
|
|
1219
1274
|
branchName,
|
|
1220
1275
|
manifest
|
|
1221
1276
|
});
|
|
1277
|
+
if (commitHash === null) {
|
|
1278
|
+
blockedBy.push({
|
|
1279
|
+
projectId: manifest.projectId,
|
|
1280
|
+
projectPath: manifest.projectPath,
|
|
1281
|
+
branchName,
|
|
1282
|
+
kind: "dirty_checkout",
|
|
1283
|
+
id: `${manifest.projectId}:${branchName}`
|
|
1284
|
+
});
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1222
1287
|
results.push({
|
|
1223
1288
|
projectId: manifest.projectId,
|
|
1224
1289
|
projectPath: manifest.projectPath,
|
|
@@ -1291,6 +1356,60 @@ async function streamCommandOutput(stream, onData) {
|
|
|
1291
1356
|
function ensureOperationBranch(input) {
|
|
1292
1357
|
return ensureBranchWorkspace(input);
|
|
1293
1358
|
}
|
|
1359
|
+
async function inspectManagedWorkspace(input) {
|
|
1360
|
+
const candidates = input.manifests.flatMap((manifest) => {
|
|
1361
|
+
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
1362
|
+
return branches.map((branchName) => ({
|
|
1363
|
+
manifest,
|
|
1364
|
+
branchName,
|
|
1365
|
+
checkoutPath: import_node_path.default.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName)
|
|
1366
|
+
}));
|
|
1367
|
+
});
|
|
1368
|
+
const inspected = await Promise.all(
|
|
1369
|
+
candidates.map(async ({ manifest, branchName, checkoutPath }) => {
|
|
1370
|
+
const gitDir = internalGitDirFor(input.syncRoot, manifest.projectId, branchName);
|
|
1371
|
+
if (!hasNormalVisibleGitDir(checkoutPath) || !import_node_fs.default.existsSync(gitDir)) return null;
|
|
1372
|
+
const context = {
|
|
1373
|
+
gitDir,
|
|
1374
|
+
workTree: checkoutPath,
|
|
1375
|
+
auth: internalGitAuth(input.baseUrl, input.token)
|
|
1376
|
+
};
|
|
1377
|
+
try {
|
|
1378
|
+
const status = await runInternalGitAsync(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
|
|
1379
|
+
let commitHash = null;
|
|
1380
|
+
for (const revision of [
|
|
1381
|
+
"HEAD",
|
|
1382
|
+
`refs/remotes/${R5D_REMOTE_NAME}/${branchName}`,
|
|
1383
|
+
`refs/remotes/${R5D_REMOTE_NAME}/main`
|
|
1384
|
+
]) {
|
|
1385
|
+
try {
|
|
1386
|
+
commitHash = await runInternalGitAsync(context, ["rev-parse", "--verify", revision]);
|
|
1387
|
+
break;
|
|
1388
|
+
} catch {
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (!commitHash) return null;
|
|
1392
|
+
return {
|
|
1393
|
+
projectId: manifest.projectId,
|
|
1394
|
+
projectPath: manifest.projectPath,
|
|
1395
|
+
branchName,
|
|
1396
|
+
checkoutPath,
|
|
1397
|
+
commitHash,
|
|
1398
|
+
status,
|
|
1399
|
+
dirty: status.trim().length > 0
|
|
1400
|
+
};
|
|
1401
|
+
} catch {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
})
|
|
1405
|
+
);
|
|
1406
|
+
return {
|
|
1407
|
+
type: "workspace_status",
|
|
1408
|
+
checkouts: inspected.filter((checkout) => checkout !== null).sort(
|
|
1409
|
+
(left, right) => left.projectPath.localeCompare(right.projectPath) || left.branchName.localeCompare(right.branchName)
|
|
1410
|
+
)
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1294
1413
|
function internalGitProcessEnv(workspace, env) {
|
|
1295
1414
|
if (env?.R5D_USE_INTERNAL_GIT !== "1") {
|
|
1296
1415
|
return {};
|
|
@@ -1857,6 +1976,22 @@ function pullBranch(input) {
|
|
|
1857
1976
|
}
|
|
1858
1977
|
async function executeOperation(input) {
|
|
1859
1978
|
switch (input.message.type) {
|
|
1979
|
+
case "resolve_managed_path":
|
|
1980
|
+
return resolveManagedCheckoutPath({
|
|
1981
|
+
projectsRoot: input.projectsRoot,
|
|
1982
|
+
manifests: input.manifests,
|
|
1983
|
+
originProjectId: input.message.projectId,
|
|
1984
|
+
originBranchName: input.message.branchName,
|
|
1985
|
+
inputPath: input.message.inputPath
|
|
1986
|
+
});
|
|
1987
|
+
case "workspace_status":
|
|
1988
|
+
return await inspectManagedWorkspace({
|
|
1989
|
+
baseUrl: input.baseUrl,
|
|
1990
|
+
token: input.token,
|
|
1991
|
+
projectsRoot: input.projectsRoot,
|
|
1992
|
+
syncRoot: input.syncRoot,
|
|
1993
|
+
manifests: input.manifests
|
|
1994
|
+
});
|
|
1860
1995
|
case "read":
|
|
1861
1996
|
return executeReadFileOperation({ ...input, message: input.message });
|
|
1862
1997
|
case "write":
|
|
@@ -1959,9 +2094,11 @@ async function executeCommand(input) {
|
|
|
1959
2094
|
projectId: input.projectId,
|
|
1960
2095
|
sessionId: input.message.sessionId ?? "",
|
|
1961
2096
|
branchName: input.message.branchName,
|
|
2097
|
+
mode: "foreground",
|
|
2098
|
+
startCommitHash: "",
|
|
1962
2099
|
argv: input.message.argv,
|
|
1963
2100
|
command: input.message.argv.join(" "),
|
|
1964
|
-
cwd
|
|
2101
|
+
cwd,
|
|
1965
2102
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1966
2103
|
});
|
|
1967
2104
|
if (input.message.timeoutMs) {
|
|
@@ -2064,17 +2201,28 @@ async function executeStreamingCommand(input) {
|
|
|
2064
2201
|
projectId: input.projectId,
|
|
2065
2202
|
sessionId: input.message.sessionId,
|
|
2066
2203
|
branchName: input.message.branchName,
|
|
2204
|
+
mode: input.message.mode,
|
|
2205
|
+
startCommitHash: input.message.startCommitHash,
|
|
2206
|
+
launchWorkspaceActionId: input.message.launchWorkspaceActionId,
|
|
2067
2207
|
credentialId: input.message.credentialId,
|
|
2208
|
+
pid: subprocess.pid,
|
|
2209
|
+
processGroupId: subprocess.pid,
|
|
2068
2210
|
argv: input.message.argv,
|
|
2069
2211
|
command: input.message.command,
|
|
2070
|
-
cwd
|
|
2212
|
+
// Persist the resolved absolute cwd so reconnect reports and process
|
|
2213
|
+
// history retain the actual execution location, including cross-project
|
|
2214
|
+
// managed paths.
|
|
2215
|
+
cwd,
|
|
2071
2216
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2072
2217
|
});
|
|
2073
2218
|
started = true;
|
|
2074
2219
|
sendWorkerMessage(input.ws, {
|
|
2075
2220
|
type: "exec_started",
|
|
2076
2221
|
requestId: input.message.requestId,
|
|
2077
|
-
runId: input.message.runId
|
|
2222
|
+
runId: input.message.runId,
|
|
2223
|
+
cwd,
|
|
2224
|
+
pid: subprocess.pid,
|
|
2225
|
+
processGroupId: subprocess.pid
|
|
2078
2226
|
});
|
|
2079
2227
|
if (input.message.timeoutMs) {
|
|
2080
2228
|
timeout = setTimeout(() => {
|
|
@@ -2153,7 +2301,12 @@ function buildActiveProcessReports() {
|
|
|
2153
2301
|
projectId: active.projectId,
|
|
2154
2302
|
sessionId: active.sessionId,
|
|
2155
2303
|
branchName: active.branchName,
|
|
2304
|
+
mode: active.mode,
|
|
2305
|
+
startCommitHash: active.startCommitHash,
|
|
2306
|
+
...active.launchWorkspaceActionId ? { launchWorkspaceActionId: active.launchWorkspaceActionId } : {},
|
|
2156
2307
|
...active.credentialId ? { credentialId: active.credentialId } : {},
|
|
2308
|
+
...active.pid !== void 0 ? { pid: active.pid } : {},
|
|
2309
|
+
...active.processGroupId !== void 0 ? { processGroupId: active.processGroupId } : {},
|
|
2157
2310
|
argv: active.argv,
|
|
2158
2311
|
command: active.command,
|
|
2159
2312
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
@@ -2650,6 +2803,7 @@ async function startWorker(options) {
|
|
|
2650
2803
|
requestId: message.requestId,
|
|
2651
2804
|
result: syncResult
|
|
2652
2805
|
});
|
|
2806
|
+
sendActiveProcessReport(ws);
|
|
2653
2807
|
return;
|
|
2654
2808
|
}
|
|
2655
2809
|
if (message.type === "update_clis") {
|
|
@@ -2831,7 +2985,7 @@ async function startWorker(options) {
|
|
|
2831
2985
|
}
|
|
2832
2986
|
return;
|
|
2833
2987
|
}
|
|
2834
|
-
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 === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
2988
|
+
if (message.type === "resolve_managed_path" || message.type === "workspace_status" || 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 === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
2835
2989
|
try {
|
|
2836
2990
|
await repositorySyncQueue;
|
|
2837
2991
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
@@ -2844,6 +2998,8 @@ async function startWorker(options) {
|
|
|
2844
2998
|
projectRoot,
|
|
2845
2999
|
syncRoot,
|
|
2846
3000
|
artifactRoot,
|
|
3001
|
+
projectsRoot,
|
|
3002
|
+
manifests: [...manifestByProjectId.values()],
|
|
2847
3003
|
manifest
|
|
2848
3004
|
});
|
|
2849
3005
|
ws.send(
|
|
@@ -2959,6 +3115,7 @@ if (isCliEntrypoint()) {
|
|
|
2959
3115
|
findWorkerFiles,
|
|
2960
3116
|
githubCliEnv,
|
|
2961
3117
|
grepWorkerFiles,
|
|
3118
|
+
inspectManagedWorkspace,
|
|
2962
3119
|
isArtifactEnvPath,
|
|
2963
3120
|
listWorkerDirectory,
|
|
2964
3121
|
prepareArtifactEnvForShell,
|
|
@@ -2966,6 +3123,7 @@ if (isCliEntrypoint()) {
|
|
|
2966
3123
|
readWorkerImageFile,
|
|
2967
3124
|
readWorkerTextFile,
|
|
2968
3125
|
resolveHostShell,
|
|
3126
|
+
resolveManagedCheckoutPath,
|
|
2969
3127
|
resolveProjectFilePath,
|
|
2970
3128
|
resolveWorkerFilePath,
|
|
2971
3129
|
selectManifestSyncTargets,
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/main.mjs
CHANGED
|
@@ -693,6 +693,11 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
693
693
|
const displayPath = toProjectDisplayPath(resolvedBranchPath, absolutePath2);
|
|
694
694
|
const repoRelativePath2 = displayPath === "." ? "" : displayPath;
|
|
695
695
|
assertAllowedProjectPath(repoRelativePath2, inputPath);
|
|
696
|
+
const realBranchPath2 = fs.existsSync(resolvedBranchPath) ? fs.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
697
|
+
const realTargetPath2 = resolveThroughExistingAncestor(absolutePath2);
|
|
698
|
+
if (!isInsideBranchPath(realBranchPath2, realTargetPath2)) {
|
|
699
|
+
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
700
|
+
}
|
|
696
701
|
return {
|
|
697
702
|
absolutePath: absolutePath2,
|
|
698
703
|
displayPath,
|
|
@@ -711,6 +716,11 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
711
716
|
if (!isInsideBranchPath(resolvedBranchPath, absolutePath)) {
|
|
712
717
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
713
718
|
}
|
|
719
|
+
const realBranchPath = fs.existsSync(resolvedBranchPath) ? fs.realpathSync(resolvedBranchPath) : resolvedBranchPath;
|
|
720
|
+
const realTargetPath = resolveThroughExistingAncestor(absolutePath);
|
|
721
|
+
if (!isInsideBranchPath(realBranchPath, realTargetPath)) {
|
|
722
|
+
throw new Error(`Project file path escapes its checkout through a symbolic link: ${inputPath}`);
|
|
723
|
+
}
|
|
714
724
|
return {
|
|
715
725
|
absolutePath,
|
|
716
726
|
displayPath: repoRelativePath || ".",
|
|
@@ -722,11 +732,60 @@ function resolveProjectFilePath(branchPath, inputPath) {
|
|
|
722
732
|
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
723
733
|
if (resolved.scope === "host") {
|
|
724
734
|
throw new Error(
|
|
725
|
-
`Project file writes must
|
|
735
|
+
`Project file writes must resolve inside the selected managed checkout: ${inputPath}`
|
|
726
736
|
);
|
|
727
737
|
}
|
|
728
738
|
return resolved;
|
|
729
739
|
}
|
|
740
|
+
function resolveThroughExistingAncestor(inputPath) {
|
|
741
|
+
const suffix = [];
|
|
742
|
+
let cursor = path.resolve(inputPath);
|
|
743
|
+
while (!fs.existsSync(cursor)) {
|
|
744
|
+
const parent = path.dirname(cursor);
|
|
745
|
+
if (parent === cursor) return path.resolve(inputPath);
|
|
746
|
+
suffix.unshift(path.basename(cursor));
|
|
747
|
+
cursor = parent;
|
|
748
|
+
}
|
|
749
|
+
return path.resolve(fs.realpathSync(cursor), ...suffix);
|
|
750
|
+
}
|
|
751
|
+
function resolveManagedCheckoutPath(input) {
|
|
752
|
+
const originManifest = input.manifests.find((manifest) => manifest.projectId === input.originProjectId);
|
|
753
|
+
const candidate = path.isAbsolute(input.inputPath) ? path.resolve(input.inputPath) : path.resolve(
|
|
754
|
+
input.projectsRoot,
|
|
755
|
+
originManifest?.repoSlug ?? input.originProjectId,
|
|
756
|
+
input.originBranchName,
|
|
757
|
+
input.inputPath
|
|
758
|
+
);
|
|
759
|
+
const resolvedCandidate = resolveThroughExistingAncestor(candidate);
|
|
760
|
+
for (const manifest of input.manifests) {
|
|
761
|
+
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
762
|
+
for (const branchName of branches) {
|
|
763
|
+
const checkoutPath = path.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName);
|
|
764
|
+
if (!hasNormalVisibleGitDir(checkoutPath)) continue;
|
|
765
|
+
const resolvedCheckoutPath = fs.existsSync(checkoutPath) ? fs.realpathSync(checkoutPath) : checkoutPath;
|
|
766
|
+
const matchesLexically = isInsideBranchPath(checkoutPath, candidate);
|
|
767
|
+
if (!matchesLexically) continue;
|
|
768
|
+
if (!isInsideBranchPath(resolvedCheckoutPath, resolvedCandidate)) {
|
|
769
|
+
throw new Error(`Managed path escapes its checkout through a symbolic link: ${input.inputPath}`);
|
|
770
|
+
}
|
|
771
|
+
const repoRelativePath = toProjectDisplayPath(resolvedCheckoutPath, resolvedCandidate);
|
|
772
|
+
assertAllowedProjectPath(repoRelativePath === "." ? "" : repoRelativePath, input.inputPath);
|
|
773
|
+
return {
|
|
774
|
+
type: "resolve_managed_path",
|
|
775
|
+
resolved: {
|
|
776
|
+
projectId: manifest.projectId,
|
|
777
|
+
projectPath: manifest.projectPath,
|
|
778
|
+
branchName,
|
|
779
|
+
checkoutPath,
|
|
780
|
+
inputPath: input.inputPath,
|
|
781
|
+
absolutePath: candidate,
|
|
782
|
+
repoRelativePath: repoRelativePath === "." ? "" : repoRelativePath
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return { type: "resolve_managed_path", resolved: null };
|
|
788
|
+
}
|
|
730
789
|
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
731
790
|
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
732
791
|
return remoteUrl;
|
|
@@ -1095,6 +1154,9 @@ async function forceSyncManifestBranchFromInternal(input) {
|
|
|
1095
1154
|
if (!target) {
|
|
1096
1155
|
throw new Error(`Internal remote has neither ${input.branchName} nor main`);
|
|
1097
1156
|
}
|
|
1157
|
+
if (hasInternalWorktreeChanges(context)) {
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1098
1160
|
await runInternalGitAsync(context, ["reset", "--hard", target]);
|
|
1099
1161
|
await runInternalGitAsync(context, ["clean", "-fd", "--", ".", ":(exclude).git"]);
|
|
1100
1162
|
return getInternalCommitHash(context);
|
|
@@ -1102,10 +1164,6 @@ async function forceSyncManifestBranchFromInternal(input) {
|
|
|
1102
1164
|
function findRepositorySyncBlockers(input) {
|
|
1103
1165
|
const targetByKey = new Map(input.targets.map((target) => [`${target.projectId}:${target.branchName}`, target]));
|
|
1104
1166
|
const blockedBy = [];
|
|
1105
|
-
for (const process2 of input.processes) {
|
|
1106
|
-
const target = targetByKey.get(`${process2.projectId}:${process2.branchName}`);
|
|
1107
|
-
if (target) blockedBy.push({ ...target, kind: "process", id: process2.id });
|
|
1108
|
-
}
|
|
1109
1167
|
for (const shell of input.shells) {
|
|
1110
1168
|
const target = targetByKey.get(`${shell.projectId}:${shell.branchName}`);
|
|
1111
1169
|
if (target) blockedBy.push({ ...target, kind: "shell", id: shell.id });
|
|
@@ -1141,11 +1199,6 @@ async function syncManifestProjectsFromInternal(input) {
|
|
|
1141
1199
|
projectPath: manifest.projectPath,
|
|
1142
1200
|
branchName
|
|
1143
1201
|
})),
|
|
1144
|
-
processes: [...activeProcesses].map(([id, active]) => ({
|
|
1145
|
-
id,
|
|
1146
|
-
projectId: active.projectId,
|
|
1147
|
-
branchName: active.branchName
|
|
1148
|
-
})),
|
|
1149
1202
|
shells: [...activePtys].map(([id, active]) => ({
|
|
1150
1203
|
id,
|
|
1151
1204
|
projectId: active.projectId,
|
|
@@ -1168,6 +1221,16 @@ async function syncManifestProjectsFromInternal(input) {
|
|
|
1168
1221
|
branchName,
|
|
1169
1222
|
manifest
|
|
1170
1223
|
});
|
|
1224
|
+
if (commitHash === null) {
|
|
1225
|
+
blockedBy.push({
|
|
1226
|
+
projectId: manifest.projectId,
|
|
1227
|
+
projectPath: manifest.projectPath,
|
|
1228
|
+
branchName,
|
|
1229
|
+
kind: "dirty_checkout",
|
|
1230
|
+
id: `${manifest.projectId}:${branchName}`
|
|
1231
|
+
});
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1171
1234
|
results.push({
|
|
1172
1235
|
projectId: manifest.projectId,
|
|
1173
1236
|
projectPath: manifest.projectPath,
|
|
@@ -1240,6 +1303,60 @@ async function streamCommandOutput(stream, onData) {
|
|
|
1240
1303
|
function ensureOperationBranch(input) {
|
|
1241
1304
|
return ensureBranchWorkspace(input);
|
|
1242
1305
|
}
|
|
1306
|
+
async function inspectManagedWorkspace(input) {
|
|
1307
|
+
const candidates = input.manifests.flatMap((manifest) => {
|
|
1308
|
+
const branches = manifest.branches.length > 0 ? manifest.branches : [manifest.defaultBranch || "main"];
|
|
1309
|
+
return branches.map((branchName) => ({
|
|
1310
|
+
manifest,
|
|
1311
|
+
branchName,
|
|
1312
|
+
checkoutPath: path.resolve(input.projectsRoot, manifest.repoSlug || manifest.projectId, branchName)
|
|
1313
|
+
}));
|
|
1314
|
+
});
|
|
1315
|
+
const inspected = await Promise.all(
|
|
1316
|
+
candidates.map(async ({ manifest, branchName, checkoutPath }) => {
|
|
1317
|
+
const gitDir = internalGitDirFor(input.syncRoot, manifest.projectId, branchName);
|
|
1318
|
+
if (!hasNormalVisibleGitDir(checkoutPath) || !fs.existsSync(gitDir)) return null;
|
|
1319
|
+
const context = {
|
|
1320
|
+
gitDir,
|
|
1321
|
+
workTree: checkoutPath,
|
|
1322
|
+
auth: internalGitAuth(input.baseUrl, input.token)
|
|
1323
|
+
};
|
|
1324
|
+
try {
|
|
1325
|
+
const status = await runInternalGitAsync(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
|
|
1326
|
+
let commitHash = null;
|
|
1327
|
+
for (const revision of [
|
|
1328
|
+
"HEAD",
|
|
1329
|
+
`refs/remotes/${R5D_REMOTE_NAME}/${branchName}`,
|
|
1330
|
+
`refs/remotes/${R5D_REMOTE_NAME}/main`
|
|
1331
|
+
]) {
|
|
1332
|
+
try {
|
|
1333
|
+
commitHash = await runInternalGitAsync(context, ["rev-parse", "--verify", revision]);
|
|
1334
|
+
break;
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
if (!commitHash) return null;
|
|
1339
|
+
return {
|
|
1340
|
+
projectId: manifest.projectId,
|
|
1341
|
+
projectPath: manifest.projectPath,
|
|
1342
|
+
branchName,
|
|
1343
|
+
checkoutPath,
|
|
1344
|
+
commitHash,
|
|
1345
|
+
status,
|
|
1346
|
+
dirty: status.trim().length > 0
|
|
1347
|
+
};
|
|
1348
|
+
} catch {
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
})
|
|
1352
|
+
);
|
|
1353
|
+
return {
|
|
1354
|
+
type: "workspace_status",
|
|
1355
|
+
checkouts: inspected.filter((checkout) => checkout !== null).sort(
|
|
1356
|
+
(left, right) => left.projectPath.localeCompare(right.projectPath) || left.branchName.localeCompare(right.branchName)
|
|
1357
|
+
)
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1243
1360
|
function internalGitProcessEnv(workspace, env) {
|
|
1244
1361
|
if (env?.R5D_USE_INTERNAL_GIT !== "1") {
|
|
1245
1362
|
return {};
|
|
@@ -1806,6 +1923,22 @@ function pullBranch(input) {
|
|
|
1806
1923
|
}
|
|
1807
1924
|
async function executeOperation(input) {
|
|
1808
1925
|
switch (input.message.type) {
|
|
1926
|
+
case "resolve_managed_path":
|
|
1927
|
+
return resolveManagedCheckoutPath({
|
|
1928
|
+
projectsRoot: input.projectsRoot,
|
|
1929
|
+
manifests: input.manifests,
|
|
1930
|
+
originProjectId: input.message.projectId,
|
|
1931
|
+
originBranchName: input.message.branchName,
|
|
1932
|
+
inputPath: input.message.inputPath
|
|
1933
|
+
});
|
|
1934
|
+
case "workspace_status":
|
|
1935
|
+
return await inspectManagedWorkspace({
|
|
1936
|
+
baseUrl: input.baseUrl,
|
|
1937
|
+
token: input.token,
|
|
1938
|
+
projectsRoot: input.projectsRoot,
|
|
1939
|
+
syncRoot: input.syncRoot,
|
|
1940
|
+
manifests: input.manifests
|
|
1941
|
+
});
|
|
1809
1942
|
case "read":
|
|
1810
1943
|
return executeReadFileOperation({ ...input, message: input.message });
|
|
1811
1944
|
case "write":
|
|
@@ -1908,9 +2041,11 @@ async function executeCommand(input) {
|
|
|
1908
2041
|
projectId: input.projectId,
|
|
1909
2042
|
sessionId: input.message.sessionId ?? "",
|
|
1910
2043
|
branchName: input.message.branchName,
|
|
2044
|
+
mode: "foreground",
|
|
2045
|
+
startCommitHash: "",
|
|
1911
2046
|
argv: input.message.argv,
|
|
1912
2047
|
command: input.message.argv.join(" "),
|
|
1913
|
-
cwd
|
|
2048
|
+
cwd,
|
|
1914
2049
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1915
2050
|
});
|
|
1916
2051
|
if (input.message.timeoutMs) {
|
|
@@ -2013,17 +2148,28 @@ async function executeStreamingCommand(input) {
|
|
|
2013
2148
|
projectId: input.projectId,
|
|
2014
2149
|
sessionId: input.message.sessionId,
|
|
2015
2150
|
branchName: input.message.branchName,
|
|
2151
|
+
mode: input.message.mode,
|
|
2152
|
+
startCommitHash: input.message.startCommitHash,
|
|
2153
|
+
launchWorkspaceActionId: input.message.launchWorkspaceActionId,
|
|
2016
2154
|
credentialId: input.message.credentialId,
|
|
2155
|
+
pid: subprocess.pid,
|
|
2156
|
+
processGroupId: subprocess.pid,
|
|
2017
2157
|
argv: input.message.argv,
|
|
2018
2158
|
command: input.message.command,
|
|
2019
|
-
cwd
|
|
2159
|
+
// Persist the resolved absolute cwd so reconnect reports and process
|
|
2160
|
+
// history retain the actual execution location, including cross-project
|
|
2161
|
+
// managed paths.
|
|
2162
|
+
cwd,
|
|
2020
2163
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2021
2164
|
});
|
|
2022
2165
|
started = true;
|
|
2023
2166
|
sendWorkerMessage(input.ws, {
|
|
2024
2167
|
type: "exec_started",
|
|
2025
2168
|
requestId: input.message.requestId,
|
|
2026
|
-
runId: input.message.runId
|
|
2169
|
+
runId: input.message.runId,
|
|
2170
|
+
cwd,
|
|
2171
|
+
pid: subprocess.pid,
|
|
2172
|
+
processGroupId: subprocess.pid
|
|
2027
2173
|
});
|
|
2028
2174
|
if (input.message.timeoutMs) {
|
|
2029
2175
|
timeout = setTimeout(() => {
|
|
@@ -2102,7 +2248,12 @@ function buildActiveProcessReports() {
|
|
|
2102
2248
|
projectId: active.projectId,
|
|
2103
2249
|
sessionId: active.sessionId,
|
|
2104
2250
|
branchName: active.branchName,
|
|
2251
|
+
mode: active.mode,
|
|
2252
|
+
startCommitHash: active.startCommitHash,
|
|
2253
|
+
...active.launchWorkspaceActionId ? { launchWorkspaceActionId: active.launchWorkspaceActionId } : {},
|
|
2105
2254
|
...active.credentialId ? { credentialId: active.credentialId } : {},
|
|
2255
|
+
...active.pid !== void 0 ? { pid: active.pid } : {},
|
|
2256
|
+
...active.processGroupId !== void 0 ? { processGroupId: active.processGroupId } : {},
|
|
2106
2257
|
argv: active.argv,
|
|
2107
2258
|
command: active.command,
|
|
2108
2259
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
@@ -2599,6 +2750,7 @@ async function startWorker(options) {
|
|
|
2599
2750
|
requestId: message.requestId,
|
|
2600
2751
|
result: syncResult
|
|
2601
2752
|
});
|
|
2753
|
+
sendActiveProcessReport(ws);
|
|
2602
2754
|
return;
|
|
2603
2755
|
}
|
|
2604
2756
|
if (message.type === "update_clis") {
|
|
@@ -2780,7 +2932,7 @@ async function startWorker(options) {
|
|
|
2780
2932
|
}
|
|
2781
2933
|
return;
|
|
2782
2934
|
}
|
|
2783
|
-
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 === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
2935
|
+
if (message.type === "resolve_managed_path" || message.type === "workspace_status" || 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 === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
2784
2936
|
try {
|
|
2785
2937
|
await repositorySyncQueue;
|
|
2786
2938
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
@@ -2793,6 +2945,8 @@ async function startWorker(options) {
|
|
|
2793
2945
|
projectRoot,
|
|
2794
2946
|
syncRoot,
|
|
2795
2947
|
artifactRoot,
|
|
2948
|
+
projectsRoot,
|
|
2949
|
+
manifests: [...manifestByProjectId.values()],
|
|
2796
2950
|
manifest
|
|
2797
2951
|
});
|
|
2798
2952
|
ws.send(
|
|
@@ -2907,6 +3061,7 @@ export {
|
|
|
2907
3061
|
findWorkerFiles,
|
|
2908
3062
|
githubCliEnv,
|
|
2909
3063
|
grepWorkerFiles,
|
|
3064
|
+
inspectManagedWorkspace,
|
|
2910
3065
|
isArtifactEnvPath,
|
|
2911
3066
|
listWorkerDirectory,
|
|
2912
3067
|
prepareArtifactEnvForShell,
|
|
@@ -2914,6 +3069,7 @@ export {
|
|
|
2914
3069
|
readWorkerImageFile,
|
|
2915
3070
|
readWorkerTextFile,
|
|
2916
3071
|
resolveHostShell,
|
|
3072
|
+
resolveManagedCheckoutPath,
|
|
2917
3073
|
resolveProjectFilePath,
|
|
2918
3074
|
resolveWorkerFilePath,
|
|
2919
3075
|
selectManifestSyncTargets,
|
package/dist/mjs/package.json
CHANGED
package/dist/types/main.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ type WorkerRepositorySyncResult = {
|
|
|
40
40
|
error: string;
|
|
41
41
|
})>;
|
|
42
42
|
blockedBy: Array<WorkerRepositorySyncTarget & {
|
|
43
|
-
kind: "
|
|
43
|
+
kind: "dirty_checkout" | "shell";
|
|
44
44
|
id: string;
|
|
45
45
|
}>;
|
|
46
46
|
};
|
|
@@ -72,6 +72,28 @@ type WorkerViewFileBytesResult = {
|
|
|
72
72
|
width: number;
|
|
73
73
|
height: number;
|
|
74
74
|
};
|
|
75
|
+
type WorkerManagedCheckout = {
|
|
76
|
+
projectId: string;
|
|
77
|
+
projectPath: string;
|
|
78
|
+
branchName: string;
|
|
79
|
+
checkoutPath: string;
|
|
80
|
+
};
|
|
81
|
+
type WorkerResolveManagedPathResult = {
|
|
82
|
+
type: "resolve_managed_path";
|
|
83
|
+
resolved: (WorkerManagedCheckout & {
|
|
84
|
+
inputPath: string;
|
|
85
|
+
absolutePath: string;
|
|
86
|
+
repoRelativePath: string;
|
|
87
|
+
}) | null;
|
|
88
|
+
};
|
|
89
|
+
type WorkerWorkspaceStatusResult = {
|
|
90
|
+
type: "workspace_status";
|
|
91
|
+
checkouts: Array<WorkerManagedCheckout & {
|
|
92
|
+
commitHash: string;
|
|
93
|
+
status: string;
|
|
94
|
+
dirty: boolean;
|
|
95
|
+
}>;
|
|
96
|
+
};
|
|
75
97
|
export declare function isArtifactEnvPath(filePath: string): boolean;
|
|
76
98
|
export declare function syncSessionArtifacts(input: {
|
|
77
99
|
baseUrl: string;
|
|
@@ -123,6 +145,13 @@ export declare function resolveWorkerFilePath(branchPath: string, inputPath: str
|
|
|
123
145
|
export declare function resolveProjectFilePath(branchPath: string, inputPath: string): Extract<ResolvedWorkerFilePath, {
|
|
124
146
|
scope: "project";
|
|
125
147
|
}>;
|
|
148
|
+
export declare function resolveManagedCheckoutPath(input: {
|
|
149
|
+
projectsRoot: string;
|
|
150
|
+
manifests: WorkerProjectManifestEntry[];
|
|
151
|
+
originProjectId: string;
|
|
152
|
+
originBranchName: string;
|
|
153
|
+
inputPath: string;
|
|
154
|
+
}): WorkerResolveManagedPathResult;
|
|
126
155
|
export declare function githubCliEnv(token: string | null | undefined): Record<string, string>;
|
|
127
156
|
export declare function ensureVisibleGitCheckout(input: {
|
|
128
157
|
projectRoot: string;
|
|
@@ -135,11 +164,6 @@ export declare function ensureVisibleGitCheckout(input: {
|
|
|
135
164
|
}): string;
|
|
136
165
|
export declare function findRepositorySyncBlockers(input: {
|
|
137
166
|
targets: WorkerRepositorySyncTarget[];
|
|
138
|
-
processes: Array<{
|
|
139
|
-
id: string;
|
|
140
|
-
projectId: string;
|
|
141
|
-
branchName: string;
|
|
142
|
-
}>;
|
|
143
167
|
shells: Array<{
|
|
144
168
|
id: string;
|
|
145
169
|
projectId: string;
|
|
@@ -161,6 +185,13 @@ export declare function syncManifestProjectsFromInternal(input: {
|
|
|
161
185
|
manifests: WorkerProjectManifestEntry[];
|
|
162
186
|
requestedTargets?: WorkerRepositorySyncSelector[];
|
|
163
187
|
}): Promise<WorkerRepositorySyncResult>;
|
|
188
|
+
export declare function inspectManagedWorkspace(input: {
|
|
189
|
+
baseUrl: string;
|
|
190
|
+
token: string;
|
|
191
|
+
projectsRoot: string;
|
|
192
|
+
syncRoot: string;
|
|
193
|
+
manifests: WorkerProjectManifestEntry[];
|
|
194
|
+
}): Promise<WorkerWorkspaceStatusResult>;
|
|
164
195
|
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number): WorkerReadFileResult;
|
|
165
196
|
export declare function grepWorkerFiles(branchPath: string, input: {
|
|
166
197
|
pattern: string;
|