@ricsam/r5d-worker 0.0.45 → 0.0.47
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 +542 -153
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync.cjs +790 -90
- package/dist/mjs/main.mjs +541 -154
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-sync.mjs +787 -89
- package/dist/types/main.d.ts +59 -11
- package/dist/types/workspace-sync.d.ts +33 -3
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -43,6 +43,9 @@ __export(main_exports, {
|
|
|
43
43
|
resolveHostShell: () => resolveHostShell,
|
|
44
44
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
45
45
|
syncSessionArtifacts: () => syncSessionArtifacts,
|
|
46
|
+
visibleCheckoutGitTestHarness: () => visibleCheckoutGitTestHarness,
|
|
47
|
+
visibleCheckoutRemoteFor: () => visibleCheckoutRemoteFor,
|
|
48
|
+
workspaceSyncCheckoutTargets: () => workspaceSyncCheckoutTargets,
|
|
46
49
|
writeWorkerTextFile: () => writeWorkerTextFile
|
|
47
50
|
});
|
|
48
51
|
module.exports = __toCommonJS(main_exports);
|
|
@@ -282,7 +285,25 @@ async function readResponseText(response) {
|
|
|
282
285
|
}
|
|
283
286
|
}
|
|
284
287
|
const R5D_ARTIFACTS_DIR_REF = "$R5D_ARTIFACTS_DIR";
|
|
288
|
+
const R5D_PLANS_DIR_REF = "$R5D_PLANS_DIR";
|
|
289
|
+
const R5D_ACTIVE_PLAN_FILE_REF = "$R5D_ACTIVE_PLAN_FILE";
|
|
285
290
|
const R5D_PLANS_DIR_ENV = "R5D_PLANS_DIR";
|
|
291
|
+
const BUILT_IN_TOOL_PATH_REFS = [R5D_ARTIFACTS_DIR_REF, R5D_PLANS_DIR_REF, R5D_ACTIVE_PLAN_FILE_REF];
|
|
292
|
+
function parseBuiltInToolPath(inputPath) {
|
|
293
|
+
const normalized = inputPath.replace(/\\/g, "/");
|
|
294
|
+
const match = /^\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))(?:\/(.*))?$/.exec(normalized);
|
|
295
|
+
if (!match) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const ref = `$${match[1] ?? match[2]}`;
|
|
299
|
+
if (!BUILT_IN_TOOL_PATH_REFS.includes(ref)) {
|
|
300
|
+
throw new Error(`Unsupported tool path variable "${ref}". Supported variables: ${BUILT_IN_TOOL_PATH_REFS.join(", ")}.`);
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
ref,
|
|
304
|
+
relativePath: match[3] ?? ""
|
|
305
|
+
};
|
|
306
|
+
}
|
|
286
307
|
function validateArtifactSessionId(sessionId) {
|
|
287
308
|
if (!/^(migration-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId)) {
|
|
288
309
|
throw new Error(`Invalid artifact session id: ${sessionId}`);
|
|
@@ -291,7 +312,7 @@ function validateArtifactSessionId(sessionId) {
|
|
|
291
312
|
function assertInsideRoot(rootPath, candidatePath, label) {
|
|
292
313
|
const relative = import_node_path.default.relative(rootPath, candidatePath);
|
|
293
314
|
if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
|
|
294
|
-
throw new Error(`${label} escapes
|
|
315
|
+
throw new Error(`${label} escapes its allowed root`);
|
|
295
316
|
}
|
|
296
317
|
}
|
|
297
318
|
function sessionArtifactDir(artifactRoot, sessionId) {
|
|
@@ -324,7 +345,11 @@ function planEnv(planRoot, projectId, branchName, activePlanId) {
|
|
|
324
345
|
return env;
|
|
325
346
|
}
|
|
326
347
|
function isArtifactEnvPath(filePath) {
|
|
327
|
-
|
|
348
|
+
try {
|
|
349
|
+
return parseBuiltInToolPath(filePath)?.ref === R5D_ARTIFACTS_DIR_REF;
|
|
350
|
+
} catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
328
353
|
}
|
|
329
354
|
function artifactApiBasePath(projectId, branchName, sessionId) {
|
|
330
355
|
return `/preview-api/${encodeURIComponent(projectId)}/${encodeURIComponent(branchName)}/artifacts/${encodeURIComponent(sessionId)}`;
|
|
@@ -458,40 +483,45 @@ async function syncSessionArtifacts(input) {
|
|
|
458
483
|
void queued.then(release, release);
|
|
459
484
|
return queued;
|
|
460
485
|
}
|
|
461
|
-
async function
|
|
462
|
-
|
|
463
|
-
|
|
486
|
+
async function prepareBuiltInToolPaths(input) {
|
|
487
|
+
const parsed = parseBuiltInToolPath(input.filePath);
|
|
488
|
+
if (!parsed) {
|
|
489
|
+
return void 0;
|
|
464
490
|
}
|
|
465
|
-
if (
|
|
466
|
-
|
|
491
|
+
if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
|
|
492
|
+
if (input.access === "write") {
|
|
493
|
+
throw new Error(
|
|
494
|
+
`${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
if (!input.sessionId) {
|
|
498
|
+
throw new Error(`${R5D_ARTIFACTS_DIR_REF} paths require an active chat session`);
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
artifactsDir: await syncSessionArtifacts({
|
|
502
|
+
baseUrl: input.baseUrl,
|
|
503
|
+
token: input.token,
|
|
504
|
+
projectId: input.projectId,
|
|
505
|
+
branchName: input.branchName,
|
|
506
|
+
sessionId: input.sessionId,
|
|
507
|
+
artifactRoot: input.artifactRoot
|
|
508
|
+
})
|
|
509
|
+
};
|
|
467
510
|
}
|
|
468
|
-
|
|
469
|
-
|
|
511
|
+
const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
|
|
512
|
+
import_node_fs.default.mkdirSync(plansDir, { recursive: true });
|
|
513
|
+
if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
514
|
+
return { plansDir };
|
|
470
515
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
token: input.token,
|
|
474
|
-
projectId: input.projectId,
|
|
475
|
-
branchName: input.branchName,
|
|
476
|
-
sessionId: input.sessionId,
|
|
477
|
-
artifactRoot: input.artifactRoot
|
|
478
|
-
});
|
|
479
|
-
const relativePath = import_node_path.default.posix.normalize(input.filePath.slice(`${R5D_ARTIFACTS_DIR_REF}/`.length));
|
|
480
|
-
if (!relativePath || relativePath === "." || relativePath.startsWith("../") || relativePath.includes("\0")) {
|
|
481
|
-
throw new Error(`Invalid artifact path: ${input.filePath}`);
|
|
516
|
+
if (!input.activePlanId) {
|
|
517
|
+
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} paths require an active plan`);
|
|
482
518
|
}
|
|
483
|
-
|
|
484
|
-
assertInsideRoot(targetDir, absolutePath, "Artifact path");
|
|
519
|
+
validatePlanId(input.activePlanId);
|
|
485
520
|
return {
|
|
486
|
-
|
|
487
|
-
|
|
521
|
+
plansDir,
|
|
522
|
+
activePlanFile: import_node_path.default.join(plansDir, `${input.activePlanId}.plan.md`)
|
|
488
523
|
};
|
|
489
524
|
}
|
|
490
|
-
function rejectArtifactWritePath(filePath) {
|
|
491
|
-
if (isArtifactEnvPath(filePath)) {
|
|
492
|
-
throw new Error(`${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
525
|
async function prepareArtifactEnvForShell(input) {
|
|
496
526
|
if (!input.sessionId) {
|
|
497
527
|
return {};
|
|
@@ -587,6 +617,14 @@ function validatePlanId(planId) {
|
|
|
587
617
|
throw new Error(`Invalid plan id from server: ${planId}`);
|
|
588
618
|
}
|
|
589
619
|
}
|
|
620
|
+
const NON_RECURSIVE_GIT_CONFIG_ARGS = [
|
|
621
|
+
"-c",
|
|
622
|
+
"submodule.recurse=false",
|
|
623
|
+
"-c",
|
|
624
|
+
"fetch.recurseSubmodules=false",
|
|
625
|
+
"-c",
|
|
626
|
+
"push.recurseSubmodules=false"
|
|
627
|
+
];
|
|
590
628
|
function gitExtraHeaderConfigKey(extraHeaderUrl) {
|
|
591
629
|
return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
|
|
592
630
|
}
|
|
@@ -594,10 +632,23 @@ function gitAuthArgs(auth) {
|
|
|
594
632
|
if (!auth) {
|
|
595
633
|
return [];
|
|
596
634
|
}
|
|
597
|
-
|
|
635
|
+
const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
|
|
636
|
+
return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
|
|
637
|
+
}
|
|
638
|
+
const visibleCheckoutGitTestHarness = {
|
|
639
|
+
gitAuthArgs,
|
|
640
|
+
commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...gitAuthArgs(auth), ...args],
|
|
641
|
+
cloneArgs: (remoteUrl, branchPath) => ["clone", "--no-recurse-submodules", "--origin", "origin", remoteUrl, branchPath],
|
|
642
|
+
fetchArgs: (...args) => ["fetch", "--no-recurse-submodules", ...args]
|
|
643
|
+
};
|
|
644
|
+
function visibleCheckoutCloneArgs(remoteUrl, branchPath) {
|
|
645
|
+
return visibleCheckoutGitTestHarness.cloneArgs(remoteUrl, branchPath);
|
|
646
|
+
}
|
|
647
|
+
function visibleCheckoutFetchArgs(...args) {
|
|
648
|
+
return visibleCheckoutGitTestHarness.fetchArgs(...args);
|
|
598
649
|
}
|
|
599
650
|
function runGit(args, options = {}) {
|
|
600
|
-
const command =
|
|
651
|
+
const command = visibleCheckoutGitTestHarness.commandArgs(args, options.auth);
|
|
601
652
|
const result = Bun.spawnSync(command, {
|
|
602
653
|
cwd: options.cwd,
|
|
603
654
|
stdout: "pipe",
|
|
@@ -642,10 +693,63 @@ function assertAllowedProjectPath(repoRelativePath, inputPath) {
|
|
|
642
693
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
643
694
|
}
|
|
644
695
|
}
|
|
645
|
-
function
|
|
696
|
+
function assertPathInsideRealRoot(rootPath, candidatePath, label) {
|
|
697
|
+
const resolvedRoot = import_node_path.default.resolve(rootPath);
|
|
698
|
+
const resolvedCandidate = import_node_path.default.resolve(candidatePath);
|
|
699
|
+
assertInsideRoot(resolvedRoot, resolvedCandidate, label);
|
|
700
|
+
let existingPath = resolvedCandidate;
|
|
701
|
+
while (!import_node_fs.default.existsSync(existingPath)) {
|
|
702
|
+
const parentPath = import_node_path.default.dirname(existingPath);
|
|
703
|
+
if (parentPath === existingPath) {
|
|
704
|
+
throw new Error(`${label} root does not exist: ${rootPath}`);
|
|
705
|
+
}
|
|
706
|
+
existingPath = parentPath;
|
|
707
|
+
}
|
|
708
|
+
const realRoot = import_node_fs.default.realpathSync(resolvedRoot);
|
|
709
|
+
const realExistingPath = import_node_fs.default.realpathSync(existingPath);
|
|
710
|
+
assertInsideRoot(realRoot, realExistingPath, label);
|
|
711
|
+
}
|
|
712
|
+
function resolveVirtualWorkerFilePath(inputPath, parsed, builtInPaths) {
|
|
713
|
+
if (parsed.relativePath.split("/").includes("..")) {
|
|
714
|
+
throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
|
|
715
|
+
}
|
|
716
|
+
let rootPath;
|
|
717
|
+
let absolutePath;
|
|
718
|
+
if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
|
|
719
|
+
rootPath = builtInPaths?.artifactsDir;
|
|
720
|
+
} else if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
721
|
+
rootPath = builtInPaths?.plansDir;
|
|
722
|
+
} else {
|
|
723
|
+
if (parsed.relativePath) {
|
|
724
|
+
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} must reference the active plan file directly`);
|
|
725
|
+
}
|
|
726
|
+
rootPath = builtInPaths?.plansDir;
|
|
727
|
+
absolutePath = builtInPaths?.activePlanFile;
|
|
728
|
+
}
|
|
729
|
+
if (!rootPath || parsed.ref === R5D_ACTIVE_PLAN_FILE_REF && !absolutePath) {
|
|
730
|
+
const requirement = parsed.ref === R5D_ARTIFACTS_DIR_REF ? "an active chat session" : "an active plan";
|
|
731
|
+
throw new Error(`${parsed.ref} paths require ${requirement}`);
|
|
732
|
+
}
|
|
733
|
+
const normalizedPath = parsed.relativePath ? import_node_path.default.posix.normalize(parsed.relativePath).replace(/^\/+|\/+$/g, "") : "";
|
|
734
|
+
const normalizedRelativePath = normalizedPath === "." ? "" : normalizedPath;
|
|
735
|
+
absolutePath ??= normalizedRelativePath ? import_node_path.default.resolve(rootPath, ...normalizedRelativePath.split("/")) : import_node_path.default.resolve(rootPath);
|
|
736
|
+
assertPathInsideRealRoot(rootPath, absolutePath, `${parsed.ref} path`);
|
|
737
|
+
return {
|
|
738
|
+
absolutePath,
|
|
739
|
+
displayPath: normalizedRelativePath ? `${parsed.ref}/${normalizedRelativePath}` : parsed.ref,
|
|
740
|
+
repoRelativePath: null,
|
|
741
|
+
scope: "virtual",
|
|
742
|
+
virtualRootPath: import_node_path.default.resolve(rootPath)
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
|
|
646
746
|
if (typeof inputPath !== "string" || inputPath.length === 0 || inputPath.includes("\0")) {
|
|
647
747
|
throw new Error(`File path must be a non-empty string. Got: ${JSON.stringify(inputPath)}`);
|
|
648
748
|
}
|
|
749
|
+
const parsedBuiltInPath = parseBuiltInToolPath(inputPath);
|
|
750
|
+
if (parsedBuiltInPath) {
|
|
751
|
+
return resolveVirtualWorkerFilePath(inputPath, parsedBuiltInPath, builtInPaths);
|
|
752
|
+
}
|
|
649
753
|
const resolvedBranchPath = import_node_path.default.resolve(branchPath);
|
|
650
754
|
if (import_node_path.default.isAbsolute(inputPath)) {
|
|
651
755
|
const absolutePath2 = import_node_path.default.resolve(inputPath);
|
|
@@ -688,8 +792,28 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
688
792
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
689
793
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
690
794
|
}
|
|
691
|
-
function
|
|
692
|
-
|
|
795
|
+
function visibleCheckoutRemoteFor(input) {
|
|
796
|
+
const canonicalCheckout = input.manifest?.canonicalCheckouts?.find((checkout) => checkout.branchName === input.branchName);
|
|
797
|
+
const isCanonicalManifestBranch = Boolean(canonicalCheckout);
|
|
798
|
+
const usesCanonicalRemote = isCanonicalManifestBranch || !input.manifest?.repoHttpUrl;
|
|
799
|
+
if (usesCanonicalRemote) {
|
|
800
|
+
return {
|
|
801
|
+
remoteUrl: fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
|
|
802
|
+
authHeader: `Authorization: Bearer ${input.token}`,
|
|
803
|
+
persistAuth: false,
|
|
804
|
+
reconcileExistingOrigin: isCanonicalManifestBranch,
|
|
805
|
+
requireRemoteBranch: isCanonicalManifestBranch,
|
|
806
|
+
requiredBaseCommit: canonicalCheckout?.scaffoldCommitHash ?? null
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
remoteUrl: input.manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
|
|
811
|
+
authHeader: input.manifest?.repoAuthHeader ?? null,
|
|
812
|
+
persistAuth: true,
|
|
813
|
+
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl),
|
|
814
|
+
requireRemoteBranch: false,
|
|
815
|
+
requiredBaseCommit: null
|
|
816
|
+
};
|
|
693
817
|
}
|
|
694
818
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
695
819
|
try {
|
|
@@ -718,6 +842,10 @@ function ensureOriginRemote(branchPath, remoteUrl) {
|
|
|
718
842
|
runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
719
843
|
}
|
|
720
844
|
}
|
|
845
|
+
function originRemoteUrl(branchPath) {
|
|
846
|
+
if (!tryGit(["remote", "get-url", "origin"], { cwd: branchPath })) return null;
|
|
847
|
+
return runGit(["remote", "get-url", "origin"], { cwd: branchPath });
|
|
848
|
+
}
|
|
721
849
|
function shellQuote(value) {
|
|
722
850
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
723
851
|
}
|
|
@@ -786,6 +914,18 @@ function configureVisibleGitHubAuth(branchPath, remoteUrl, authHeader) {
|
|
|
786
914
|
}
|
|
787
915
|
runGit(["config", `${gitExtraHeaderConfigKey(gitExtraHeaderUrlForRemote(remoteUrl))}`, authHeader], { cwd: branchPath });
|
|
788
916
|
}
|
|
917
|
+
function clearVisibleGitAuthentication(branchPath) {
|
|
918
|
+
let extraHeaderKeys = [];
|
|
919
|
+
try {
|
|
920
|
+
extraHeaderKeys = runGit(["config", "--local", "--name-only", "--get-regexp", "^http\\..*extraheader$"], { cwd: branchPath }).split(/\r?\n/).filter(Boolean);
|
|
921
|
+
} catch {
|
|
922
|
+
}
|
|
923
|
+
for (const key of extraHeaderKeys) {
|
|
924
|
+
tryGit(["config", "--local", "--unset-all", key], { cwd: branchPath });
|
|
925
|
+
}
|
|
926
|
+
runGit(["config", "--local", "--replace-all", "credential.helper", ""], { cwd: branchPath });
|
|
927
|
+
tryGit(["config", "--local", "--unset-all", "credential.useHttpPath"], { cwd: branchPath });
|
|
928
|
+
}
|
|
789
929
|
function dockerConfigPath() {
|
|
790
930
|
return import_node_path.default.join(import_node_os.default.homedir(), ".docker", "config.json");
|
|
791
931
|
}
|
|
@@ -874,18 +1014,58 @@ function checkoutVisibleBranch(input) {
|
|
|
874
1014
|
});
|
|
875
1015
|
const clean = !hasWorktreeChanges(input.branchPath);
|
|
876
1016
|
if (remoteBranchExists && clean) {
|
|
877
|
-
runGit(["checkout", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1017
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1018
|
+
cwd: input.branchPath
|
|
1019
|
+
});
|
|
878
1020
|
return;
|
|
879
1021
|
}
|
|
880
1022
|
if (branchExists) {
|
|
881
|
-
runGit(["checkout", input.branchName], { cwd: input.branchPath });
|
|
1023
|
+
runGit(["checkout", "--no-recurse-submodules", input.branchName], { cwd: input.branchPath });
|
|
882
1024
|
return;
|
|
883
1025
|
}
|
|
884
1026
|
if (defaultRemoteExists) {
|
|
885
|
-
runGit(["checkout", "-B", input.branchName, `origin/${input.defaultBranch}`], {
|
|
1027
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.defaultBranch}`], {
|
|
1028
|
+
cwd: input.branchPath
|
|
1029
|
+
});
|
|
886
1030
|
return;
|
|
887
1031
|
}
|
|
888
|
-
runGit(["checkout", "-B", input.branchName], { cwd: input.branchPath });
|
|
1032
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName], { cwd: input.branchPath });
|
|
1033
|
+
}
|
|
1034
|
+
function migrateExistingCheckoutToRequiredRemote(input) {
|
|
1035
|
+
if (originRemoteUrl(input.branchPath) === input.remoteUrl) return;
|
|
1036
|
+
if (hasWorktreeChanges(input.branchPath)) {
|
|
1037
|
+
throw new Error(`Cannot migrate dirty checkout for ${input.branchName} to its canonical remote`);
|
|
1038
|
+
}
|
|
1039
|
+
runGit(
|
|
1040
|
+
visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
|
|
1041
|
+
{
|
|
1042
|
+
cwd: input.branchPath,
|
|
1043
|
+
auth: input.remoteAuth
|
|
1044
|
+
}
|
|
1045
|
+
);
|
|
1046
|
+
const canonicalRemoteRef = `refs/remotes/origin/${input.branchName}`;
|
|
1047
|
+
if (!tryGit(["show-ref", "--verify", "--quiet", canonicalRemoteRef], { cwd: input.branchPath })) {
|
|
1048
|
+
throw new Error(`Canonical branch ${input.branchName} is unavailable during checkout migration`);
|
|
1049
|
+
}
|
|
1050
|
+
if (input.requiredBaseCommit && (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, canonicalRemoteRef], { cwd: input.branchPath }))) {
|
|
1051
|
+
throw new Error(`Canonical branch ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
|
|
1052
|
+
}
|
|
1053
|
+
if (tryGit(["rev-parse", "--verify", "HEAD"], { cwd: input.branchPath })) {
|
|
1054
|
+
const previousHead = runGit(["rev-parse", "HEAD"], { cwd: input.branchPath });
|
|
1055
|
+
runGit(["update-ref", `refs/r5d/pre-canonical-checkout/${previousHead}`, previousHead], { cwd: input.branchPath });
|
|
1056
|
+
}
|
|
1057
|
+
ensureOriginRemote(input.branchPath, input.remoteUrl);
|
|
1058
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1059
|
+
cwd: input.branchPath
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
function assertCheckoutDerivedFromRequiredBase(input) {
|
|
1063
|
+
const remoteRevision = `refs/remotes/origin/${input.branchName}`;
|
|
1064
|
+
for (const revision of [remoteRevision, "HEAD"]) {
|
|
1065
|
+
if (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, revision], { cwd: input.branchPath })) {
|
|
1066
|
+
throw new Error(`Canonical checkout ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
889
1069
|
}
|
|
890
1070
|
function ensureVisibleGitCheckout(input) {
|
|
891
1071
|
validateBranchName(input.branchName);
|
|
@@ -895,40 +1075,114 @@ function ensureVisibleGitCheckout(input) {
|
|
|
895
1075
|
if (createdCheckout) {
|
|
896
1076
|
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
897
1077
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(branchPath), { recursive: true });
|
|
898
|
-
|
|
1078
|
+
const cloned = tryGit(visibleCheckoutCloneArgs(input.remoteUrl, branchPath), {
|
|
1079
|
+
auth: input.remoteAuth
|
|
1080
|
+
});
|
|
1081
|
+
if (!cloned && input.requireRemoteBranch) {
|
|
1082
|
+
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1083
|
+
throw new Error(`Failed to clone canonical checkout for branch ${input.branchName}`);
|
|
1084
|
+
}
|
|
1085
|
+
if (!cloned) {
|
|
899
1086
|
import_node_fs.default.mkdirSync(branchPath, { recursive: true });
|
|
900
1087
|
runGit(["init"], { cwd: branchPath });
|
|
901
1088
|
}
|
|
902
|
-
ensureOriginRemote(branchPath, input.
|
|
903
|
-
|
|
1089
|
+
ensureOriginRemote(branchPath, input.remoteUrl);
|
|
1090
|
+
if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
|
|
1091
|
+
else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
|
|
904
1092
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
905
|
-
tryGit(
|
|
1093
|
+
tryGit(visibleCheckoutFetchArgs("origin", "--prune"), { cwd: branchPath, auth: input.remoteAuth });
|
|
1094
|
+
if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
|
|
1095
|
+
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1096
|
+
throw new Error(`Canonical branch ${input.branchName} is unavailable after clone`);
|
|
1097
|
+
}
|
|
906
1098
|
checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
|
|
1099
|
+
if (input.requiredBaseCommit) {
|
|
1100
|
+
try {
|
|
1101
|
+
assertCheckoutDerivedFromRequiredBase({
|
|
1102
|
+
branchPath,
|
|
1103
|
+
branchName: input.branchName,
|
|
1104
|
+
requiredBaseCommit: input.requiredBaseCommit
|
|
1105
|
+
});
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1108
|
+
throw error;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
907
1111
|
return branchPath;
|
|
908
1112
|
}
|
|
1113
|
+
if (input.requireRemoteBranch) {
|
|
1114
|
+
if (input.allowRequiredRemoteMigration === false && originRemoteUrl(branchPath) !== input.remoteUrl) {
|
|
1115
|
+
throw new Error(`Canonical checkout ${input.branchName} no longer uses its required remote`);
|
|
1116
|
+
}
|
|
1117
|
+
migrateExistingCheckoutToRequiredRemote({
|
|
1118
|
+
branchPath,
|
|
1119
|
+
branchName: input.branchName,
|
|
1120
|
+
remoteUrl: input.remoteUrl,
|
|
1121
|
+
remoteAuth: input.remoteAuth,
|
|
1122
|
+
requiredBaseCommit: input.requiredBaseCommit
|
|
1123
|
+
});
|
|
1124
|
+
runGit(
|
|
1125
|
+
visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
|
|
1126
|
+
{
|
|
1127
|
+
cwd: branchPath,
|
|
1128
|
+
auth: input.remoteAuth
|
|
1129
|
+
}
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
909
1132
|
if (input.reconcileExistingOrigin) {
|
|
910
|
-
ensureOriginRemote(branchPath, input.
|
|
1133
|
+
ensureOriginRemote(branchPath, input.remoteUrl);
|
|
1134
|
+
}
|
|
1135
|
+
if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
|
|
1136
|
+
throw new Error(`Existing checkout for ${input.branchName} does not contain its canonical remote branch`);
|
|
911
1137
|
}
|
|
912
|
-
|
|
1138
|
+
if (input.requiredBaseCommit) {
|
|
1139
|
+
assertCheckoutDerivedFromRequiredBase({
|
|
1140
|
+
branchPath,
|
|
1141
|
+
branchName: input.branchName,
|
|
1142
|
+
requiredBaseCommit: input.requiredBaseCommit
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
|
|
1146
|
+
else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
|
|
913
1147
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
914
1148
|
return branchPath;
|
|
915
1149
|
}
|
|
916
1150
|
function ensureBranchWorkspace(input) {
|
|
917
1151
|
const defaultBranch = input.manifest?.defaultBranch || "main";
|
|
918
|
-
const
|
|
919
|
-
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
|
|
1152
|
+
const remote = visibleCheckoutRemoteFor(input);
|
|
920
1153
|
return {
|
|
921
1154
|
branchPath: ensureVisibleGitCheckout({
|
|
922
1155
|
projectRoot: input.projectRoot,
|
|
923
1156
|
branchName: input.branchName,
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1157
|
+
remoteUrl: remote.remoteUrl,
|
|
1158
|
+
remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
|
|
1159
|
+
persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
|
|
927
1160
|
defaultBranch,
|
|
928
|
-
reconcileExistingOrigin:
|
|
1161
|
+
reconcileExistingOrigin: remote.reconcileExistingOrigin,
|
|
1162
|
+
requireRemoteBranch: remote.requireRemoteBranch,
|
|
1163
|
+
requiredBaseCommit: remote.requiredBaseCommit,
|
|
1164
|
+
clearPersistentAuth: !remote.persistAuth
|
|
929
1165
|
})
|
|
930
1166
|
};
|
|
931
1167
|
}
|
|
1168
|
+
function workspaceSyncCheckoutTargets(input) {
|
|
1169
|
+
const selected = /* @__PURE__ */ new Map();
|
|
1170
|
+
const add = (target) => {
|
|
1171
|
+
selected.set(`${target.projectId}\0${target.branchName}`, target);
|
|
1172
|
+
};
|
|
1173
|
+
if (input.canonicalCheckoutOnly) {
|
|
1174
|
+
add(input.canonicalCheckoutOnly);
|
|
1175
|
+
} else if (input.includeAllManifestCheckouts) {
|
|
1176
|
+
for (const project of input.projects) {
|
|
1177
|
+
for (const branchName of project.branches) add({ projectId: project.projectId, branchName });
|
|
1178
|
+
}
|
|
1179
|
+
} else {
|
|
1180
|
+
for (const target of input.pendingTargets) add(target);
|
|
1181
|
+
}
|
|
1182
|
+
return [...selected.values()].sort(
|
|
1183
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
932
1186
|
function resolveCommandCwd(branchPath, cwd) {
|
|
933
1187
|
if (!cwd) {
|
|
934
1188
|
return branchPath;
|
|
@@ -1102,13 +1356,13 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1102
1356
|
}
|
|
1103
1357
|
}
|
|
1104
1358
|
function mutationQueueKey(projectId, branchName, resolved) {
|
|
1105
|
-
if (resolved.scope
|
|
1106
|
-
return
|
|
1359
|
+
if (resolved.scope !== "project") {
|
|
1360
|
+
return `${resolved.scope}:${import_node_path.default.normalize(resolved.absolutePath)}`;
|
|
1107
1361
|
}
|
|
1108
1362
|
return `project:${projectId}:${branchName}:${import_node_path.default.posix.normalize(resolved.repoRelativePath)}`;
|
|
1109
1363
|
}
|
|
1110
|
-
function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
1111
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1364
|
+
function readWorkerTextFile(branchPath, filePath, offset, limit, builtInPaths) {
|
|
1365
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1112
1366
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1113
1367
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1114
1368
|
}
|
|
@@ -1122,8 +1376,8 @@ function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
|
1122
1376
|
...formatted
|
|
1123
1377
|
};
|
|
1124
1378
|
}
|
|
1125
|
-
function writeWorkerTextFile(branchPath, filePath, content) {
|
|
1126
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1379
|
+
function writeWorkerTextFile(branchPath, filePath, content, builtInPaths) {
|
|
1380
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1127
1381
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(resolved.absolutePath), { recursive: true });
|
|
1128
1382
|
import_node_fs.default.writeFileSync(resolved.absolutePath, content, "utf8");
|
|
1129
1383
|
return {
|
|
@@ -1132,8 +1386,8 @@ function writeWorkerTextFile(branchPath, filePath, content) {
|
|
|
1132
1386
|
gitBlobHash: getGitBlobHashForContent(content)
|
|
1133
1387
|
};
|
|
1134
1388
|
}
|
|
1135
|
-
function editWorkerTextFile(branchPath, filePath, edits) {
|
|
1136
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1389
|
+
function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
|
|
1390
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1137
1391
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
1138
1392
|
throw new Error("edit requires at least one replacement");
|
|
1139
1393
|
}
|
|
@@ -1180,46 +1434,57 @@ function editWorkerTextFile(branchPath, filePath, edits) {
|
|
|
1180
1434
|
};
|
|
1181
1435
|
}
|
|
1182
1436
|
async function executeReadFileOperation(input) {
|
|
1183
|
-
const
|
|
1437
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1184
1438
|
filePath: input.message.filePath,
|
|
1185
1439
|
baseUrl: input.baseUrl,
|
|
1186
1440
|
token: input.token,
|
|
1187
1441
|
projectId: input.projectId,
|
|
1188
1442
|
branchName: input.message.branchName,
|
|
1189
1443
|
sessionId: input.message.sessionId,
|
|
1190
|
-
|
|
1444
|
+
activePlanId: input.message.activePlanId,
|
|
1445
|
+
artifactRoot: input.artifactRoot,
|
|
1446
|
+
planRoot: input.planRoot,
|
|
1447
|
+
access: "read"
|
|
1191
1448
|
});
|
|
1192
|
-
if (artifact) {
|
|
1193
|
-
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1194
|
-
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1195
|
-
}
|
|
1196
|
-
const buffer = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1197
|
-
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1198
|
-
return {
|
|
1199
|
-
type: "read",
|
|
1200
|
-
kind: "text",
|
|
1201
|
-
file: artifact.virtualPath,
|
|
1202
|
-
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1203
|
-
...formatted
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
1449
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1207
|
-
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit);
|
|
1450
|
+
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
|
|
1208
1451
|
}
|
|
1209
1452
|
async function executeWriteFileOperation(input) {
|
|
1210
|
-
|
|
1453
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1454
|
+
filePath: input.message.filePath,
|
|
1455
|
+
baseUrl: input.baseUrl,
|
|
1456
|
+
token: input.token,
|
|
1457
|
+
projectId: input.projectId,
|
|
1458
|
+
branchName: input.message.branchName,
|
|
1459
|
+
sessionId: input.message.sessionId,
|
|
1460
|
+
activePlanId: input.message.activePlanId,
|
|
1461
|
+
artifactRoot: input.artifactRoot,
|
|
1462
|
+
planRoot: input.planRoot,
|
|
1463
|
+
access: "write"
|
|
1464
|
+
});
|
|
1211
1465
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1212
|
-
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1466
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1213
1467
|
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1214
|
-
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content);
|
|
1468
|
+
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content, builtInPaths);
|
|
1215
1469
|
});
|
|
1216
1470
|
}
|
|
1217
1471
|
async function executeEditFileOperation(input) {
|
|
1218
|
-
|
|
1472
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1473
|
+
filePath: input.message.filePath,
|
|
1474
|
+
baseUrl: input.baseUrl,
|
|
1475
|
+
token: input.token,
|
|
1476
|
+
projectId: input.projectId,
|
|
1477
|
+
branchName: input.message.branchName,
|
|
1478
|
+
sessionId: input.message.sessionId,
|
|
1479
|
+
activePlanId: input.message.activePlanId,
|
|
1480
|
+
artifactRoot: input.artifactRoot,
|
|
1481
|
+
planRoot: input.planRoot,
|
|
1482
|
+
access: "write"
|
|
1483
|
+
});
|
|
1219
1484
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1220
|
-
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1485
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1221
1486
|
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1222
|
-
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits);
|
|
1487
|
+
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits, builtInPaths);
|
|
1223
1488
|
});
|
|
1224
1489
|
}
|
|
1225
1490
|
const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
|
|
@@ -1240,16 +1505,20 @@ function normalizeFindPattern(pattern) {
|
|
|
1240
1505
|
function walkWorkerEntries(branchPath, start) {
|
|
1241
1506
|
const entries = [];
|
|
1242
1507
|
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
1508
|
+
const realVirtualRoot = start.scope === "virtual" ? import_node_fs.default.realpathSync(start.virtualRootPath) : null;
|
|
1243
1509
|
const visit = (absolutePath, isRoot) => {
|
|
1244
1510
|
let stat;
|
|
1245
1511
|
try {
|
|
1246
1512
|
stat = import_node_fs.default.statSync(absolutePath);
|
|
1513
|
+
if (realVirtualRoot) {
|
|
1514
|
+
assertInsideRoot(realVirtualRoot, import_node_fs.default.realpathSync(absolutePath), `${start.displayPath} path`);
|
|
1515
|
+
}
|
|
1247
1516
|
} catch (error) {
|
|
1248
1517
|
if (isRoot) throw error;
|
|
1249
1518
|
return;
|
|
1250
1519
|
}
|
|
1251
|
-
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : import_node_path.default.resolve(absolutePath);
|
|
1252
1520
|
const hostRelativePath = import_node_path.default.relative(start.absolutePath, absolutePath).split(import_node_path.default.sep).join(import_node_path.default.posix.sep);
|
|
1521
|
+
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : start.scope === "virtual" ? hostRelativePath ? `${start.displayPath}/${hostRelativePath}` : start.displayPath : import_node_path.default.resolve(absolutePath);
|
|
1253
1522
|
const matchPath = start.scope === "project" ? displayPath : hostRelativePath || import_node_path.default.basename(start.absolutePath);
|
|
1254
1523
|
entries.push({ absolutePath, displayPath, matchPath, stat });
|
|
1255
1524
|
if (!stat.isDirectory()) return;
|
|
@@ -1280,8 +1549,8 @@ function walkWorkerEntries(branchPath, start) {
|
|
|
1280
1549
|
function isProbablyText(bytes) {
|
|
1281
1550
|
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
1282
1551
|
}
|
|
1283
|
-
function grepWorkerFiles(branchPath, input) {
|
|
1284
|
-
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1552
|
+
function grepWorkerFiles(branchPath, input, builtInPaths) {
|
|
1553
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
1285
1554
|
if (!import_node_fs.default.existsSync(start.absolutePath)) {
|
|
1286
1555
|
throw new Error(`Path not found: ${start.displayPath}`);
|
|
1287
1556
|
}
|
|
@@ -1327,17 +1596,34 @@ function grepWorkerFiles(branchPath, input) {
|
|
|
1327
1596
|
};
|
|
1328
1597
|
}
|
|
1329
1598
|
async function executeGrepOperation(input) {
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1599
|
+
const toolPath = input.message.path ?? ".";
|
|
1600
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1601
|
+
filePath: toolPath,
|
|
1602
|
+
baseUrl: input.baseUrl,
|
|
1603
|
+
token: input.token,
|
|
1604
|
+
projectId: input.projectId,
|
|
1605
|
+
branchName: input.message.branchName,
|
|
1606
|
+
sessionId: input.message.sessionId,
|
|
1607
|
+
activePlanId: input.message.activePlanId,
|
|
1608
|
+
artifactRoot: input.artifactRoot,
|
|
1609
|
+
planRoot: input.planRoot,
|
|
1610
|
+
access: "read"
|
|
1337
1611
|
});
|
|
1612
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1613
|
+
return grepWorkerFiles(
|
|
1614
|
+
workspace.branchPath,
|
|
1615
|
+
{
|
|
1616
|
+
pattern: input.message.pattern,
|
|
1617
|
+
path: input.message.path,
|
|
1618
|
+
glob: input.message.glob,
|
|
1619
|
+
caseSensitive: input.message.caseSensitive,
|
|
1620
|
+
limit: input.message.limit
|
|
1621
|
+
},
|
|
1622
|
+
builtInPaths
|
|
1623
|
+
);
|
|
1338
1624
|
}
|
|
1339
|
-
function findWorkerFiles(branchPath, input) {
|
|
1340
|
-
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1625
|
+
function findWorkerFiles(branchPath, input, builtInPaths) {
|
|
1626
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
1341
1627
|
if (!import_node_fs.default.existsSync(start.absolutePath) || !import_node_fs.default.statSync(start.absolutePath).isDirectory()) {
|
|
1342
1628
|
throw new Error(`Directory not found: ${start.displayPath}`);
|
|
1343
1629
|
}
|
|
@@ -1371,16 +1657,33 @@ function findWorkerFiles(branchPath, input) {
|
|
|
1371
1657
|
};
|
|
1372
1658
|
}
|
|
1373
1659
|
async function executeFindOperation(input) {
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1660
|
+
const toolPath = input.message.path ?? ".";
|
|
1661
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1662
|
+
filePath: toolPath,
|
|
1663
|
+
baseUrl: input.baseUrl,
|
|
1664
|
+
token: input.token,
|
|
1665
|
+
projectId: input.projectId,
|
|
1666
|
+
branchName: input.message.branchName,
|
|
1667
|
+
sessionId: input.message.sessionId,
|
|
1668
|
+
activePlanId: input.message.activePlanId,
|
|
1669
|
+
artifactRoot: input.artifactRoot,
|
|
1670
|
+
planRoot: input.planRoot,
|
|
1671
|
+
access: "read"
|
|
1380
1672
|
});
|
|
1673
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1674
|
+
return findWorkerFiles(
|
|
1675
|
+
workspace.branchPath,
|
|
1676
|
+
{
|
|
1677
|
+
pattern: input.message.pattern,
|
|
1678
|
+
path: input.message.path,
|
|
1679
|
+
entryType: input.message.entryType,
|
|
1680
|
+
limit: input.message.limit
|
|
1681
|
+
},
|
|
1682
|
+
builtInPaths
|
|
1683
|
+
);
|
|
1381
1684
|
}
|
|
1382
|
-
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
1383
|
-
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
1685
|
+
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit, builtInPaths) {
|
|
1686
|
+
const resolved = resolveWorkerFilePath(branchPath, inputPath, builtInPaths);
|
|
1384
1687
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isDirectory()) {
|
|
1385
1688
|
throw new Error(`Directory not found: ${resolved.displayPath}`);
|
|
1386
1689
|
}
|
|
@@ -1398,11 +1701,24 @@ function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
|
1398
1701
|
};
|
|
1399
1702
|
}
|
|
1400
1703
|
async function executeLsOperation(input) {
|
|
1704
|
+
const toolPath = input.message.path ?? ".";
|
|
1705
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1706
|
+
filePath: toolPath,
|
|
1707
|
+
baseUrl: input.baseUrl,
|
|
1708
|
+
token: input.token,
|
|
1709
|
+
projectId: input.projectId,
|
|
1710
|
+
branchName: input.message.branchName,
|
|
1711
|
+
sessionId: input.message.sessionId,
|
|
1712
|
+
activePlanId: input.message.activePlanId,
|
|
1713
|
+
artifactRoot: input.artifactRoot,
|
|
1714
|
+
planRoot: input.planRoot,
|
|
1715
|
+
access: "read"
|
|
1716
|
+
});
|
|
1401
1717
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1402
|
-
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit);
|
|
1718
|
+
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit, builtInPaths);
|
|
1403
1719
|
}
|
|
1404
|
-
function readWorkerImageFile(branchPath, filePath) {
|
|
1405
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1720
|
+
function readWorkerImageFile(branchPath, filePath, builtInPaths) {
|
|
1721
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1406
1722
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1407
1723
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1408
1724
|
}
|
|
@@ -1424,38 +1740,20 @@ function readWorkerImageFile(branchPath, filePath) {
|
|
|
1424
1740
|
};
|
|
1425
1741
|
}
|
|
1426
1742
|
async function executeViewFileBytesOperation(input) {
|
|
1427
|
-
const
|
|
1743
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1428
1744
|
filePath: input.message.filePath,
|
|
1429
1745
|
baseUrl: input.baseUrl,
|
|
1430
1746
|
token: input.token,
|
|
1431
1747
|
projectId: input.projectId,
|
|
1432
1748
|
branchName: input.message.branchName,
|
|
1433
1749
|
sessionId: input.message.sessionId,
|
|
1434
|
-
|
|
1750
|
+
activePlanId: input.message.activePlanId,
|
|
1751
|
+
artifactRoot: input.artifactRoot,
|
|
1752
|
+
planRoot: input.planRoot,
|
|
1753
|
+
access: "read"
|
|
1435
1754
|
});
|
|
1436
|
-
if (artifact) {
|
|
1437
|
-
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1438
|
-
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1439
|
-
}
|
|
1440
|
-
const extension = import_node_path.default.extname(artifact.absolutePath).toLowerCase();
|
|
1441
|
-
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1442
|
-
if (!mediaType) {
|
|
1443
|
-
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1444
|
-
}
|
|
1445
|
-
const bytes = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1446
|
-
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1447
|
-
return {
|
|
1448
|
-
type: "view_file_bytes",
|
|
1449
|
-
filePath: artifact.virtualPath,
|
|
1450
|
-
mediaType,
|
|
1451
|
-
base64: bytes.toString("base64"),
|
|
1452
|
-
fileSizeBytes: bytes.length,
|
|
1453
|
-
width: dimensions.width,
|
|
1454
|
-
height: dimensions.height
|
|
1455
|
-
};
|
|
1456
|
-
}
|
|
1457
1755
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1458
|
-
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1756
|
+
return readWorkerImageFile(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1459
1757
|
}
|
|
1460
1758
|
async function executeOperation(input) {
|
|
1461
1759
|
switch (input.message.type) {
|
|
@@ -2094,6 +2392,7 @@ async function startWorker(options) {
|
|
|
2094
2392
|
import_node_fs.default.mkdirSync(artifactRoot, { recursive: true });
|
|
2095
2393
|
import_node_fs.default.mkdirSync(planRoot, { recursive: true });
|
|
2096
2394
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
2395
|
+
const pendingManifestCheckouts = /* @__PURE__ */ new Map();
|
|
2097
2396
|
const workspaceSyncSingleFlight = new import_workspace_sync.WorkspaceSyncSingleFlight();
|
|
2098
2397
|
let workspaceRemoteUrl = null;
|
|
2099
2398
|
let activeWorkspaceIncidentId = null;
|
|
@@ -2107,6 +2406,7 @@ async function startWorker(options) {
|
|
|
2107
2406
|
let reloadAfterClose = false;
|
|
2108
2407
|
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2109
2408
|
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2409
|
+
const projects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
2110
2410
|
return {
|
|
2111
2411
|
workerLabel: label,
|
|
2112
2412
|
remoteUrl: workspaceRemoteUrl,
|
|
@@ -2114,7 +2414,7 @@ async function startWorker(options) {
|
|
|
2114
2414
|
projectsRoot,
|
|
2115
2415
|
plansRoot: planRoot,
|
|
2116
2416
|
shadowRoot: workspaceShadowRoot,
|
|
2117
|
-
projects
|
|
2417
|
+
projects,
|
|
2118
2418
|
trigger,
|
|
2119
2419
|
...overrides
|
|
2120
2420
|
};
|
|
@@ -2122,37 +2422,89 @@ async function startWorker(options) {
|
|
|
2122
2422
|
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2123
2423
|
sendWorkerMessage(ws, { type: "workspace_sync_result", requestId, result });
|
|
2124
2424
|
};
|
|
2125
|
-
const
|
|
2425
|
+
const manifestCheckoutKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
2426
|
+
const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
|
|
2427
|
+
(manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
|
|
2428
|
+
);
|
|
2429
|
+
const ensureVisibleWorkspaceCheckouts = (targets, options2 = {}) => {
|
|
2126
2430
|
const created = [];
|
|
2127
|
-
for (const
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
projectRoot: projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId),
|
|
2134
|
-
branchName,
|
|
2135
|
-
githubRemoteUrl,
|
|
2136
|
-
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2137
|
-
githubAuthHeader: manifest.repoAuthHeader,
|
|
2138
|
-
defaultBranch: manifest.defaultBranch || "main",
|
|
2139
|
-
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2140
|
-
});
|
|
2141
|
-
if (!existed) created.push({ projectId: manifest.projectId, branchName });
|
|
2431
|
+
for (const target of targets) {
|
|
2432
|
+
const manifest = manifestByProjectId.get(target.projectId);
|
|
2433
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2434
|
+
if (!manifest || !manifest.branches.includes(target.branchName)) {
|
|
2435
|
+
pendingManifestCheckouts.delete(key);
|
|
2436
|
+
continue;
|
|
2142
2437
|
}
|
|
2438
|
+
const projectRoot = projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId);
|
|
2439
|
+
const branchPath = import_node_path.default.join(projectRoot, target.branchName);
|
|
2440
|
+
const existed = hasNormalVisibleGitDir(branchPath);
|
|
2441
|
+
const remote = visibleCheckoutRemoteFor({
|
|
2442
|
+
baseUrl,
|
|
2443
|
+
token,
|
|
2444
|
+
projectId: manifest.projectId,
|
|
2445
|
+
branchName: target.branchName,
|
|
2446
|
+
manifest
|
|
2447
|
+
});
|
|
2448
|
+
ensureVisibleGitCheckout({
|
|
2449
|
+
projectRoot,
|
|
2450
|
+
branchName: target.branchName,
|
|
2451
|
+
remoteUrl: remote.remoteUrl,
|
|
2452
|
+
remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
|
|
2453
|
+
persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
|
|
2454
|
+
defaultBranch: manifest.defaultBranch || "main",
|
|
2455
|
+
reconcileExistingOrigin: remote.reconcileExistingOrigin,
|
|
2456
|
+
requireRemoteBranch: remote.requireRemoteBranch,
|
|
2457
|
+
requiredBaseCommit: remote.requiredBaseCommit,
|
|
2458
|
+
allowRequiredRemoteMigration: !(options2.strictCanonicalRevalidation && remote.requireRemoteBranch),
|
|
2459
|
+
clearPersistentAuth: !remote.persistAuth
|
|
2460
|
+
});
|
|
2461
|
+
if (!existed) created.push(target);
|
|
2143
2462
|
}
|
|
2144
2463
|
return created;
|
|
2145
2464
|
};
|
|
2146
2465
|
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2147
2466
|
workspaceSyncRequestsInFlight += 1;
|
|
2148
2467
|
try {
|
|
2149
|
-
|
|
2150
|
-
const result = await workspaceSyncSingleFlight.
|
|
2151
|
-
|
|
2468
|
+
let pendingTargets = [];
|
|
2469
|
+
const result = await workspaceSyncSingleFlight.runPrepared(() => {
|
|
2470
|
+
const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
|
|
2471
|
+
const syncProjects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
2472
|
+
const allowedCheckoutKeys = new Set(
|
|
2473
|
+
syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
|
|
2474
|
+
);
|
|
2475
|
+
const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
|
|
2476
|
+
(target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2477
|
+
);
|
|
2478
|
+
let checkoutTargets = [];
|
|
2479
|
+
if (shouldEnsureCheckouts) {
|
|
2480
|
+
checkoutTargets = workspaceSyncCheckoutTargets({
|
|
2481
|
+
projects: syncProjects,
|
|
2482
|
+
pendingTargets: scopedPendingTargets,
|
|
2483
|
+
includeAllManifestCheckouts: trigger.type === "connect",
|
|
2484
|
+
...trigger.canonicalCheckoutOnly && trigger.projectId && trigger.branchName ? { canonicalCheckoutOnly: { projectId: trigger.projectId, branchName: trigger.branchName } } : {}
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
pendingTargets = shouldEnsureCheckouts ? scopedPendingTargets : [];
|
|
2488
|
+
const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets, {
|
|
2489
|
+
strictCanonicalRevalidation: true
|
|
2490
|
+
});
|
|
2491
|
+
const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
|
|
2492
|
+
for (const target of [...overrides.newVisibleCheckouts ?? [], ...createdCheckouts, ...pendingTargets]) {
|
|
2493
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2494
|
+
if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
|
|
2495
|
+
}
|
|
2496
|
+
const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
|
|
2497
|
+
return workspaceSyncInput(trigger, {
|
|
2152
2498
|
...overrides,
|
|
2153
2499
|
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2154
|
-
})
|
|
2155
|
-
);
|
|
2500
|
+
});
|
|
2501
|
+
});
|
|
2502
|
+
if (result.outcome === "no_change" || result.outcome === "published" || result.outcome === "updated" || result.outcome === "conflict_reset") {
|
|
2503
|
+
for (const target of pendingTargets) {
|
|
2504
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2505
|
+
if (pendingManifestCheckouts.get(key) === target) pendingManifestCheckouts.delete(key);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2156
2508
|
previousPeriodicFingerprint = null;
|
|
2157
2509
|
sendWorkspaceSyncResult(requestId, result);
|
|
2158
2510
|
return result;
|
|
@@ -2215,7 +2567,8 @@ async function startWorker(options) {
|
|
|
2215
2567
|
version: getWorkerVersion(),
|
|
2216
2568
|
r5dctlVersion: (0, import_cli_update.readInstalledCliVersion)("r5dctl"),
|
|
2217
2569
|
capabilities: {
|
|
2218
|
-
updateClis: true
|
|
2570
|
+
updateClis: true,
|
|
2571
|
+
canonicalResolverCheckout: true
|
|
2219
2572
|
},
|
|
2220
2573
|
projectRoot: projectsRoot,
|
|
2221
2574
|
artifactRoot,
|
|
@@ -2245,9 +2598,26 @@ async function startWorker(options) {
|
|
|
2245
2598
|
configureGitHubAuth(message.githubCredential);
|
|
2246
2599
|
visibleGitIdentity = message.gitIdentity;
|
|
2247
2600
|
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2601
|
+
const previousCheckoutKeys = new Set(
|
|
2602
|
+
allManifestCheckouts().map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2603
|
+
);
|
|
2248
2604
|
const migratedProjectIds = (0, import_managed_paths.migrateLegacyProjectRoots)(projectsRoot, message.projects);
|
|
2249
2605
|
manifestByProjectId.clear();
|
|
2250
2606
|
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2607
|
+
const currentCheckouts = allManifestCheckouts();
|
|
2608
|
+
const currentCheckoutKeys = new Set(
|
|
2609
|
+
currentCheckouts.map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2610
|
+
);
|
|
2611
|
+
for (const key of pendingManifestCheckouts.keys()) {
|
|
2612
|
+
if (!currentCheckoutKeys.has(key)) pendingManifestCheckouts.delete(key);
|
|
2613
|
+
}
|
|
2614
|
+
for (const checkout of currentCheckouts) {
|
|
2615
|
+
const key = manifestCheckoutKey(checkout.projectId, checkout.branchName);
|
|
2616
|
+
if (previousCheckoutKeys.has(key)) continue;
|
|
2617
|
+
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, checkout.projectId, manifestByProjectId), checkout.branchName);
|
|
2618
|
+
if (!hasNormalVisibleGitDir(branchPath)) pendingManifestCheckouts.set(key, checkout);
|
|
2619
|
+
}
|
|
2620
|
+
previousPeriodicFingerprint = null;
|
|
2251
2621
|
process.stdout.write(
|
|
2252
2622
|
`[r5d-worker] workspace manifest: ${message.projects.length} projects${migratedProjectIds.length > 0 ? `; migrated ${migratedProjectIds.length} project checkout root(s)` : ""}
|
|
2253
2623
|
`
|
|
@@ -2258,8 +2628,23 @@ async function startWorker(options) {
|
|
|
2258
2628
|
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2259
2629
|
periodicWorkspaceScanInFlight = true;
|
|
2260
2630
|
void (async () => {
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2631
|
+
const genericCheckoutKeys = new Set(
|
|
2632
|
+
(0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], { type: "periodic" }).flatMap(
|
|
2633
|
+
(project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName))
|
|
2634
|
+
)
|
|
2635
|
+
);
|
|
2636
|
+
const hasPendingGenericCheckout = [...pendingManifestCheckouts.values()].some(
|
|
2637
|
+
(target) => genericCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2638
|
+
);
|
|
2639
|
+
if (hasPendingGenericCheckout) {
|
|
2640
|
+
previousPeriodicFingerprint = null;
|
|
2641
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), {
|
|
2642
|
+
type: "periodic",
|
|
2643
|
+
detail: "workspace manifest additions"
|
|
2644
|
+
});
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
const fingerprint = await workspaceSyncSingleFlight.fingerprintPrepared(() => workspaceSyncInput({ type: "periodic" }));
|
|
2263
2648
|
if (fingerprint === emptyFingerprint) {
|
|
2264
2649
|
previousPeriodicFingerprint = null;
|
|
2265
2650
|
return;
|
|
@@ -2526,6 +2911,7 @@ async function startWorker(options) {
|
|
|
2526
2911
|
projectRoot,
|
|
2527
2912
|
syncRoot,
|
|
2528
2913
|
artifactRoot,
|
|
2914
|
+
planRoot,
|
|
2529
2915
|
manifest
|
|
2530
2916
|
});
|
|
2531
2917
|
ws.send(
|
|
@@ -2668,5 +3054,8 @@ if (isCliEntrypoint()) {
|
|
|
2668
3054
|
resolveHostShell,
|
|
2669
3055
|
resolveWorkerFilePath,
|
|
2670
3056
|
syncSessionArtifacts,
|
|
3057
|
+
visibleCheckoutGitTestHarness,
|
|
3058
|
+
visibleCheckoutRemoteFor,
|
|
3059
|
+
workspaceSyncCheckoutTargets,
|
|
2671
3060
|
writeWorkerTextFile
|
|
2672
3061
|
});
|