@ricsam/r5d-worker 0.0.101 → 0.0.104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/dist/cjs/main.cjs +467 -336
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-mirror-fetch-policy.cjs +48 -7
- package/dist/cjs/project-mirror-refs-token.cjs +36 -2
- package/dist/cjs/project-worktrees.cjs +172 -63
- package/dist/cjs/workspace-git-sync.cjs +45 -6
- package/dist/cjs/workspace-mutation-gate.cjs +35 -0
- package/dist/cjs/workspace-projection-ledger.cjs +81 -0
- package/dist/cjs/workspace-sync-coalescer.cjs +58 -0
- package/dist/mjs/main.mjs +473 -338
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-mirror-fetch-policy.mjs +46 -6
- package/dist/mjs/project-mirror-refs-token.mjs +30 -1
- package/dist/mjs/project-worktrees.mjs +172 -64
- package/dist/mjs/workspace-git-sync.mjs +45 -6
- package/dist/mjs/workspace-mutation-gate.mjs +35 -0
- package/dist/mjs/workspace-projection-ledger.mjs +57 -0
- package/dist/mjs/workspace-sync-coalescer.mjs +34 -0
- package/dist/types/main.d.ts +35 -17
- package/dist/types/project-mirror-fetch-policy.d.ts +70 -15
- package/dist/types/project-mirror-refs-token.d.ts +34 -0
- package/dist/types/project-worktrees.d.ts +73 -8
- package/dist/types/workspace-git-sync.d.ts +24 -2
- package/dist/types/workspace-mutation-gate.d.ts +24 -3
- package/dist/types/workspace-projection-ledger.d.ts +59 -0
- package/dist/types/workspace-sync-coalescer.d.ts +28 -0
- package/package.json +1 -1
package/dist/mjs/main.mjs
CHANGED
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
runWorkspaceCommand
|
|
35
35
|
} from "./workspace-command-sync-policy.mjs";
|
|
36
36
|
import { WorkspaceMutationGate } from "./workspace-mutation-gate.mjs";
|
|
37
|
+
import { WorkspaceProjectionLedger } from "./workspace-projection-ledger.mjs";
|
|
38
|
+
import { WorkspaceSyncCoalescer } from "./workspace-sync-coalescer.mjs";
|
|
37
39
|
import { checkoutPathMovePublicationComplete, preserveProjectCheckoutPathMove } from "./workspace-path-move.mjs";
|
|
38
40
|
import { busyProjectConfigurationChangeIds, deferredProjectConfigurationPendingBranches } from "./workspace-project-config-policy.mjs";
|
|
39
41
|
import { creatorLocalProjectBranchIsAuthorized, workerProjectBranchDisposition } from "./workspace-preserve-only-policy.mjs";
|
|
@@ -58,11 +60,12 @@ import { assertDisjointManagedRoots, managedProjectRoot, validateManagedBranchNa
|
|
|
58
60
|
import {
|
|
59
61
|
applyObservedProjectMirrorHeads,
|
|
60
62
|
assertProjectMirrorHeadsPushed,
|
|
61
|
-
|
|
63
|
+
computeLocalProjectMirrorRefsTokens,
|
|
62
64
|
createOrRetryLinkedProjectBranch,
|
|
63
65
|
deleteLinkedProjectBranch,
|
|
64
66
|
deleteProjectMirrorBranch,
|
|
65
67
|
ensureProjectWorktrees,
|
|
68
|
+
inventoryProjectCheckouts,
|
|
66
69
|
observeProjectMirrorHeads,
|
|
67
70
|
projectBranchMayExistAfterCreateFailure,
|
|
68
71
|
projectOriginBranchName,
|
|
@@ -71,7 +74,10 @@ import {
|
|
|
71
74
|
pushProjectMirrorHeads,
|
|
72
75
|
removeProjectWorktrees
|
|
73
76
|
} from "./project-worktrees.mjs";
|
|
74
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
parseProjectMirrorRefsTokensMessage,
|
|
79
|
+
planProjectMirrorObservationFetch
|
|
80
|
+
} from "./project-mirror-fetch-policy.mjs";
|
|
75
81
|
import {
|
|
76
82
|
PROJECT_SNAPSHOT_RECOVERY_HELPER_COMMAND,
|
|
77
83
|
recoverProjectSnapshotsInChild,
|
|
@@ -182,6 +188,12 @@ const workspaceSyncSingleFlight = {
|
|
|
182
188
|
acquireMutation() {
|
|
183
189
|
return workspaceMutationGate.acquireMutation();
|
|
184
190
|
},
|
|
191
|
+
runBranchMutation(operation) {
|
|
192
|
+
return workspaceMutationGate.runBranchMutation(operation);
|
|
193
|
+
},
|
|
194
|
+
branchMutationQueuePosition() {
|
|
195
|
+
return workspaceMutationGate.branchMutationQueuePosition();
|
|
196
|
+
},
|
|
185
197
|
afterCurrent() {
|
|
186
198
|
return workspaceSyncQueue;
|
|
187
199
|
}
|
|
@@ -312,6 +324,7 @@ const workerPtyTestHarness = {
|
|
|
312
324
|
removeEnvFiles: removePtyEnvFiles,
|
|
313
325
|
resolveEnvFileReferences: resolvePtyEnvFileReferences,
|
|
314
326
|
commandHasWorkspaceEffect: workerCommandHasWorkspaceEffect,
|
|
327
|
+
targetProcessEnv,
|
|
315
328
|
canonicalSyncTerminalHead,
|
|
316
329
|
ptyIsWorkspaceBusy: workerPtyIsWorkspaceBusy,
|
|
317
330
|
parseLinuxForegroundBusy: parseLinuxPtyForegroundBusy
|
|
@@ -319,21 +332,12 @@ const workerPtyTestHarness = {
|
|
|
319
332
|
function defaultConfigPath() {
|
|
320
333
|
return path.join(os.homedir(), ".config", "r5d", "r5dctl", "config.json");
|
|
321
334
|
}
|
|
322
|
-
function
|
|
323
|
-
return path.join(os.homedir(), ".r5d"
|
|
324
|
-
}
|
|
325
|
-
function defaultSyncRoot() {
|
|
326
|
-
return path.join(os.homedir(), ".r5d", "sync");
|
|
327
|
-
}
|
|
328
|
-
function defaultArtifactRoot() {
|
|
329
|
-
return path.join(os.homedir(), ".r5d", "artifacts");
|
|
330
|
-
}
|
|
331
|
-
function defaultPlanRoot() {
|
|
332
|
-
return path.join(os.homedir(), ".r5d", "plans");
|
|
335
|
+
function defaultRootDir() {
|
|
336
|
+
return path.join(os.homedir(), ".r5d");
|
|
333
337
|
}
|
|
334
338
|
function printHelp() {
|
|
335
339
|
process.stdout.write(`Usage:
|
|
336
|
-
r5d-worker start --label <label> [--base-url <url>] [--token <token>] [--api-key <key>]
|
|
340
|
+
r5d-worker start --label <label> [--root <dir>] [--base-url <url>] [--token <token>] [--api-key <key>]
|
|
337
341
|
r5d-worker --version
|
|
338
342
|
|
|
339
343
|
Options:
|
|
@@ -342,10 +346,7 @@ Options:
|
|
|
342
346
|
--token <token> r5d worker token
|
|
343
347
|
--api-key <key> r5d API key
|
|
344
348
|
--config <path> Config path (default ~/.config/r5d/r5dctl/config.json)
|
|
345
|
-
--
|
|
346
|
-
--sync-root <dir> Override r5d internal sync git root (default ~/.r5d/sync)
|
|
347
|
-
--artifact-root <dir> Override session artifact root (default from R5D_ARTIFACTS_ROOT or ~/.r5d/artifacts)
|
|
348
|
-
--plan-root <dir> Override synced plan root (default from R5D_PLANS_ROOT or ~/.r5d/plans)
|
|
349
|
+
--root <dir> Override worker data root (default from R5D_ROOT or ~/.r5d)
|
|
349
350
|
-v, --version Show r5d-worker version
|
|
350
351
|
-h, --help Show this help
|
|
351
352
|
`);
|
|
@@ -460,24 +461,9 @@ function parseArgs(argv) {
|
|
|
460
461
|
options.label = label;
|
|
461
462
|
continue;
|
|
462
463
|
}
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
465
|
-
options.
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
const syncRoot = readOption("--sync-root");
|
|
469
|
-
if (syncRoot !== void 0) {
|
|
470
|
-
options.syncRoot = syncRoot;
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
const artifactRoot = readOption("--artifact-root");
|
|
474
|
-
if (artifactRoot !== void 0) {
|
|
475
|
-
options.artifactRoot = artifactRoot;
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
const planRoot = readOption("--plan-root");
|
|
479
|
-
if (planRoot !== void 0) {
|
|
480
|
-
options.planRoot = planRoot;
|
|
464
|
+
const rootDir = readOption("--root");
|
|
465
|
+
if (rootDir !== void 0) {
|
|
466
|
+
options.rootDir = rootDir;
|
|
481
467
|
continue;
|
|
482
468
|
}
|
|
483
469
|
rest.push(arg);
|
|
@@ -522,11 +508,9 @@ async function readResponseText(response) {
|
|
|
522
508
|
return "";
|
|
523
509
|
}
|
|
524
510
|
}
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
const R5D_PLANS_DIR_ENV = "R5D_PLANS_DIR";
|
|
529
|
-
const BUILT_IN_TOOL_PATH_REFS = [R5D_ARTIFACTS_DIR_REF, R5D_PLANS_DIR_REF, R5D_ACTIVE_PLAN_FILE_REF];
|
|
511
|
+
const R5D_ROOT_REF = "$R5D_ROOT";
|
|
512
|
+
const R5D_ROOT_ENV = "R5D_ROOT";
|
|
513
|
+
const BUILT_IN_TOOL_PATH_REFS = [R5D_ROOT_REF];
|
|
530
514
|
function parseBuiltInToolPath(inputPath) {
|
|
531
515
|
const normalized = inputPath.replace(/\\/g, "/");
|
|
532
516
|
const match = /^\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))(?:\/(.*))?$/.exec(normalized);
|
|
@@ -542,6 +526,26 @@ function parseBuiltInToolPath(inputPath) {
|
|
|
542
526
|
relativePath: match[3] ?? ""
|
|
543
527
|
};
|
|
544
528
|
}
|
|
529
|
+
function expandRootPathSegments(relativePath, builtInPaths) {
|
|
530
|
+
const values = {
|
|
531
|
+
R5D_SESSION_ID: builtInPaths.sessionId,
|
|
532
|
+
R5D_PROJECT_ID: builtInPaths.projectId,
|
|
533
|
+
R5D_BRANCH_NAME: builtInPaths.branchName,
|
|
534
|
+
R5D_ACTIVE_PLAN_ID: builtInPaths.activePlanId
|
|
535
|
+
};
|
|
536
|
+
let expandedPath = relativePath;
|
|
537
|
+
for (const [variableName, value] of Object.entries(values)) {
|
|
538
|
+
const variablePattern = new RegExp(`\\$(?:\\{${variableName}\\}|${variableName})(?=\\/|\\.|$)`, "g");
|
|
539
|
+
if (!variablePattern.test(expandedPath)) continue;
|
|
540
|
+
if (!value) throw new Error(`$${variableName} is unavailable for this worker path`);
|
|
541
|
+
expandedPath = expandedPath.replace(variablePattern, value);
|
|
542
|
+
}
|
|
543
|
+
const unsupportedVariable = /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/.exec(expandedPath);
|
|
544
|
+
if (unsupportedVariable) {
|
|
545
|
+
throw new Error(`Unsupported ${R5D_ROOT_REF} path variable "$${unsupportedVariable[1] ?? unsupportedVariable[2]}"`);
|
|
546
|
+
}
|
|
547
|
+
return expandedPath.split("/").filter(Boolean);
|
|
548
|
+
}
|
|
545
549
|
function validateArtifactSessionId(sessionId) {
|
|
546
550
|
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)) {
|
|
547
551
|
throw new Error(`Invalid artifact session id: ${sessionId}`);
|
|
@@ -557,14 +561,6 @@ function sessionArtifactDir(artifactRoot, sessionId) {
|
|
|
557
561
|
validateArtifactSessionId(sessionId);
|
|
558
562
|
return path.join(artifactRoot, sessionId);
|
|
559
563
|
}
|
|
560
|
-
function artifactEnv(artifactRoot, sessionId) {
|
|
561
|
-
if (!sessionId) {
|
|
562
|
-
return {};
|
|
563
|
-
}
|
|
564
|
-
return {
|
|
565
|
-
R5D_ARTIFACTS_DIR: sessionArtifactDir(artifactRoot, sessionId)
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
564
|
function projectPlanDir(planRoot, projectId, branchName) {
|
|
569
565
|
validateProjectId(projectId);
|
|
570
566
|
validateBranchName(branchName);
|
|
@@ -577,21 +573,18 @@ function targetPlanDir(planRoot, target) {
|
|
|
577
573
|
}
|
|
578
574
|
return path.join(planRoot, "workspace");
|
|
579
575
|
}
|
|
580
|
-
function planEnv(
|
|
581
|
-
const
|
|
582
|
-
const env = {
|
|
583
|
-
[R5D_PLANS_DIR_ENV]: plansDir
|
|
584
|
-
};
|
|
576
|
+
function planEnv(activePlanId) {
|
|
577
|
+
const env = {};
|
|
585
578
|
if (activePlanId) {
|
|
586
579
|
validatePlanId(activePlanId);
|
|
587
580
|
env.R5D_ACTIVE_PLAN_ID = activePlanId;
|
|
588
|
-
env.R5D_ACTIVE_PLAN_FILE = path.join(plansDir, `${activePlanId}.plan.md`);
|
|
589
581
|
}
|
|
590
582
|
return env;
|
|
591
583
|
}
|
|
592
584
|
function isArtifactEnvPath(filePath) {
|
|
593
585
|
try {
|
|
594
|
-
|
|
586
|
+
const parsed = parseBuiltInToolPath(filePath);
|
|
587
|
+
return parsed?.ref === R5D_ROOT_REF && parsed.relativePath.split("/").filter(Boolean)[0] === "artifacts";
|
|
595
588
|
} catch {
|
|
596
589
|
return false;
|
|
597
590
|
}
|
|
@@ -733,48 +726,46 @@ async function prepareBuiltInToolPaths(input) {
|
|
|
733
726
|
if (!parsed) {
|
|
734
727
|
return void 0;
|
|
735
728
|
}
|
|
736
|
-
|
|
729
|
+
const builtInPaths = {
|
|
730
|
+
rootDir: input.rootDir,
|
|
731
|
+
sessionId: input.sessionId,
|
|
732
|
+
activePlanId: input.activePlanId,
|
|
733
|
+
...input.target.type === "project" ? { projectId: input.target.projectId, branchName: input.target.branchName } : {}
|
|
734
|
+
};
|
|
735
|
+
const rootPathSegments = expandRootPathSegments(parsed.relativePath, builtInPaths);
|
|
736
|
+
if (rootPathSegments[0] === "artifacts") {
|
|
737
737
|
if (input.access === "write") {
|
|
738
738
|
throw new Error(
|
|
739
|
-
`${
|
|
739
|
+
`${R5D_ROOT_REF}/artifacts attachments are read-only. Copy them into a project path before editing or creating files.`
|
|
740
740
|
);
|
|
741
741
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
})
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
const plansDir = targetPlanDir(input.planRoot, input.target);
|
|
755
|
-
fs.mkdirSync(plansDir, { recursive: true });
|
|
756
|
-
if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
757
|
-
return { plansDir };
|
|
742
|
+
const referencedSessionId = rootPathSegments[1];
|
|
743
|
+
if (!referencedSessionId) return builtInPaths;
|
|
744
|
+
validateArtifactSessionId(referencedSessionId);
|
|
745
|
+
await syncSessionArtifacts({
|
|
746
|
+
baseUrl: input.baseUrl,
|
|
747
|
+
token: input.token,
|
|
748
|
+
sessionId: referencedSessionId,
|
|
749
|
+
artifactRoot: path.join(input.rootDir, "artifacts")
|
|
750
|
+
});
|
|
758
751
|
}
|
|
759
|
-
if (
|
|
760
|
-
|
|
752
|
+
if (rootPathSegments[0] === "plans") {
|
|
753
|
+
fs.mkdirSync(targetPlanDir(path.join(input.rootDir, "plans"), input.target), { recursive: true });
|
|
761
754
|
}
|
|
762
|
-
|
|
763
|
-
return {
|
|
764
|
-
plansDir,
|
|
765
|
-
activePlanFile: path.join(plansDir, `${input.activePlanId}.plan.md`)
|
|
766
|
-
};
|
|
755
|
+
return builtInPaths;
|
|
767
756
|
}
|
|
768
757
|
async function prepareArtifactEnvForShell(input) {
|
|
758
|
+
const env = { [R5D_ROOT_ENV]: input.rootDir };
|
|
769
759
|
if (!input.sessionId) {
|
|
770
|
-
return
|
|
760
|
+
return env;
|
|
771
761
|
}
|
|
762
|
+
const artifactRoot = path.join(input.rootDir, "artifacts");
|
|
772
763
|
try {
|
|
773
764
|
await syncSessionArtifacts({
|
|
774
765
|
baseUrl: input.baseUrl,
|
|
775
766
|
token: input.token,
|
|
776
767
|
sessionId: input.sessionId,
|
|
777
|
-
artifactRoot
|
|
768
|
+
artifactRoot
|
|
778
769
|
});
|
|
779
770
|
} catch (error) {
|
|
780
771
|
process.stderr.write(
|
|
@@ -782,16 +773,14 @@ async function prepareArtifactEnvForShell(input) {
|
|
|
782
773
|
`
|
|
783
774
|
);
|
|
784
775
|
}
|
|
785
|
-
const artifactsDir = sessionArtifactDir(
|
|
776
|
+
const artifactsDir = sessionArtifactDir(artifactRoot, input.sessionId);
|
|
786
777
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
787
|
-
return
|
|
788
|
-
R5D_ARTIFACTS_DIR: artifactsDir
|
|
789
|
-
};
|
|
778
|
+
return env;
|
|
790
779
|
}
|
|
791
780
|
function preparePlanEnvForShell(input) {
|
|
792
|
-
const plansDir = targetPlanDir(input.
|
|
781
|
+
const plansDir = targetPlanDir(path.join(input.rootDir, "plans"), input.target);
|
|
793
782
|
fs.mkdirSync(plansDir, { recursive: true });
|
|
794
|
-
return planEnv(input.
|
|
783
|
+
return planEnv(input.activePlanId);
|
|
795
784
|
}
|
|
796
785
|
async function verifyR5dctlAuth(baseUrl, token, workerLabel) {
|
|
797
786
|
const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
|
|
@@ -959,30 +948,17 @@ function resolveVirtualWorkerFilePath(inputPath, parsed, builtInPaths) {
|
|
|
959
948
|
if (parsed.relativePath.split("/").includes("..")) {
|
|
960
949
|
throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
|
|
961
950
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
rootPath = builtInPaths?.plansDir;
|
|
968
|
-
} else {
|
|
969
|
-
if (parsed.relativePath) {
|
|
970
|
-
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} must reference the active plan file directly`);
|
|
971
|
-
}
|
|
972
|
-
rootPath = builtInPaths?.plansDir;
|
|
973
|
-
absolutePath = builtInPaths?.activePlanFile;
|
|
974
|
-
}
|
|
975
|
-
if (!rootPath || parsed.ref === R5D_ACTIVE_PLAN_FILE_REF && !absolutePath) {
|
|
976
|
-
const requirement = parsed.ref === R5D_ARTIFACTS_DIR_REF ? "an active chat session" : "an active plan";
|
|
977
|
-
throw new Error(`${parsed.ref} paths require ${requirement}`);
|
|
978
|
-
}
|
|
979
|
-
const normalizedPath = parsed.relativePath ? path.posix.normalize(parsed.relativePath).replace(/^\/+|\/+$/g, "") : "";
|
|
951
|
+
const rootPath = builtInPaths?.rootDir;
|
|
952
|
+
if (!rootPath) throw new Error(`${R5D_ROOT_REF} paths require an available worker root`);
|
|
953
|
+
const expandedPath = expandRootPathSegments(parsed.relativePath, builtInPaths);
|
|
954
|
+
if (expandedPath.includes("..")) throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
|
|
955
|
+
const normalizedPath = expandedPath.length > 0 ? path.posix.normalize(expandedPath.join("/")).replace(/^\/+|\/+$/g, "") : "";
|
|
980
956
|
const normalizedRelativePath = normalizedPath === "." ? "" : normalizedPath;
|
|
981
|
-
absolutePath
|
|
957
|
+
const absolutePath = normalizedRelativePath ? path.resolve(rootPath, ...normalizedRelativePath.split("/")) : path.resolve(rootPath);
|
|
982
958
|
assertPathInsideRealRoot(rootPath, absolutePath, `${parsed.ref} path`);
|
|
983
959
|
return {
|
|
984
960
|
absolutePath,
|
|
985
|
-
displayPath:
|
|
961
|
+
displayPath: parsed.relativePath ? `${parsed.ref}/${parsed.relativePath}` : parsed.ref,
|
|
986
962
|
repoRelativePath: null,
|
|
987
963
|
scope: "virtual",
|
|
988
964
|
virtualRootPath: path.resolve(rootPath)
|
|
@@ -2226,8 +2202,7 @@ async function executeReadFileOperation(input) {
|
|
|
2226
2202
|
token: input.token,
|
|
2227
2203
|
sessionId: input.message.sessionId,
|
|
2228
2204
|
activePlanId: input.message.activePlanId,
|
|
2229
|
-
|
|
2230
|
-
planRoot: input.planRoot,
|
|
2205
|
+
rootDir: input.rootDir,
|
|
2231
2206
|
access: "read"
|
|
2232
2207
|
});
|
|
2233
2208
|
return readWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
|
|
@@ -2240,8 +2215,7 @@ async function executeWriteFileOperation(input) {
|
|
|
2240
2215
|
token: input.token,
|
|
2241
2216
|
sessionId: input.message.sessionId,
|
|
2242
2217
|
activePlanId: input.message.activePlanId,
|
|
2243
|
-
|
|
2244
|
-
planRoot: input.planRoot,
|
|
2218
|
+
rootDir: input.rootDir,
|
|
2245
2219
|
access: "write"
|
|
2246
2220
|
});
|
|
2247
2221
|
input.assertAdmission();
|
|
@@ -2259,8 +2233,7 @@ async function executeEditFileOperation(input) {
|
|
|
2259
2233
|
token: input.token,
|
|
2260
2234
|
sessionId: input.message.sessionId,
|
|
2261
2235
|
activePlanId: input.message.activePlanId,
|
|
2262
|
-
|
|
2263
|
-
planRoot: input.planRoot,
|
|
2236
|
+
rootDir: input.rootDir,
|
|
2264
2237
|
access: "write"
|
|
2265
2238
|
});
|
|
2266
2239
|
input.assertAdmission();
|
|
@@ -2387,8 +2360,7 @@ async function executeGrepOperation(input) {
|
|
|
2387
2360
|
token: input.token,
|
|
2388
2361
|
sessionId: input.message.sessionId,
|
|
2389
2362
|
activePlanId: input.message.activePlanId,
|
|
2390
|
-
|
|
2391
|
-
planRoot: input.planRoot,
|
|
2363
|
+
rootDir: input.rootDir,
|
|
2392
2364
|
access: "read"
|
|
2393
2365
|
});
|
|
2394
2366
|
return grepWorkerFiles(
|
|
@@ -2446,8 +2418,7 @@ async function executeFindOperation(input) {
|
|
|
2446
2418
|
token: input.token,
|
|
2447
2419
|
sessionId: input.message.sessionId,
|
|
2448
2420
|
activePlanId: input.message.activePlanId,
|
|
2449
|
-
|
|
2450
|
-
planRoot: input.planRoot,
|
|
2421
|
+
rootDir: input.rootDir,
|
|
2451
2422
|
access: "read"
|
|
2452
2423
|
});
|
|
2453
2424
|
return findWorkerFiles(
|
|
@@ -2488,8 +2459,7 @@ async function executeLsOperation(input) {
|
|
|
2488
2459
|
token: input.token,
|
|
2489
2460
|
sessionId: input.message.sessionId,
|
|
2490
2461
|
activePlanId: input.message.activePlanId,
|
|
2491
|
-
|
|
2492
|
-
planRoot: input.planRoot,
|
|
2462
|
+
rootDir: input.rootDir,
|
|
2493
2463
|
access: "read"
|
|
2494
2464
|
});
|
|
2495
2465
|
return listWorkerDirectory(input.resolvedTarget.rootPath, input.message.path, input.message.limit, builtInPaths);
|
|
@@ -2595,8 +2565,7 @@ async function executeViewFileBytesOperation(input) {
|
|
|
2595
2565
|
token: input.token,
|
|
2596
2566
|
sessionId: input.message.sessionId,
|
|
2597
2567
|
activePlanId: input.message.activePlanId,
|
|
2598
|
-
|
|
2599
|
-
planRoot: input.planRoot,
|
|
2568
|
+
rootDir: input.rootDir,
|
|
2600
2569
|
access: "read"
|
|
2601
2570
|
});
|
|
2602
2571
|
return readWorkerImageFile(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
@@ -2636,27 +2605,30 @@ function targetIdentityEnv(target) {
|
|
|
2636
2605
|
R5D_BRANCH_NAME: ""
|
|
2637
2606
|
};
|
|
2638
2607
|
}
|
|
2608
|
+
function targetProcessEnv(rootDir, target) {
|
|
2609
|
+
return { [R5D_ROOT_ENV]: rootDir, ...targetIdentityEnv(target) };
|
|
2610
|
+
}
|
|
2639
2611
|
async function prepareShellEnvForTarget(input) {
|
|
2640
2612
|
const artifactProcessEnv = await prepareArtifactEnvForShell({
|
|
2641
2613
|
baseUrl: input.baseUrl,
|
|
2642
2614
|
token: input.token,
|
|
2643
2615
|
sessionId: input.sessionId,
|
|
2644
|
-
|
|
2616
|
+
rootDir: input.rootDir
|
|
2645
2617
|
});
|
|
2646
2618
|
if (input.target.type === "workspace" && input.target.rootProfile === "canonical_sync") {
|
|
2647
2619
|
return {
|
|
2648
2620
|
...artifactProcessEnv,
|
|
2649
|
-
...
|
|
2621
|
+
...targetProcessEnv(input.rootDir, input.target)
|
|
2650
2622
|
};
|
|
2651
2623
|
}
|
|
2652
2624
|
return {
|
|
2653
2625
|
...artifactProcessEnv,
|
|
2654
2626
|
...preparePlanEnvForShell({
|
|
2655
2627
|
target: input.target,
|
|
2656
|
-
|
|
2628
|
+
rootDir: input.rootDir,
|
|
2657
2629
|
activePlanId: input.activePlanId
|
|
2658
2630
|
}),
|
|
2659
|
-
...
|
|
2631
|
+
...targetProcessEnv(input.rootDir, input.target)
|
|
2660
2632
|
};
|
|
2661
2633
|
}
|
|
2662
2634
|
async function executeCommand(input) {
|
|
@@ -2671,13 +2643,12 @@ async function executeCommand(input) {
|
|
|
2671
2643
|
};
|
|
2672
2644
|
input.setPreparationHeartbeatLease(true);
|
|
2673
2645
|
try {
|
|
2674
|
-
const
|
|
2646
|
+
const targetProcessEnv2 = await prepareShellEnvForTarget({
|
|
2675
2647
|
target: input.resolvedTarget.target,
|
|
2676
2648
|
baseUrl: input.baseUrl,
|
|
2677
2649
|
token: input.token,
|
|
2678
2650
|
sessionId: input.message.sessionId,
|
|
2679
|
-
|
|
2680
|
-
planRoot: input.planRoot,
|
|
2651
|
+
rootDir: input.rootDir,
|
|
2681
2652
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2682
2653
|
});
|
|
2683
2654
|
const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
|
|
@@ -2687,7 +2658,7 @@ async function executeCommand(input) {
|
|
|
2687
2658
|
stdout: "pipe",
|
|
2688
2659
|
stderr: "pipe",
|
|
2689
2660
|
detached: true,
|
|
2690
|
-
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {},
|
|
2661
|
+
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv2])
|
|
2691
2662
|
});
|
|
2692
2663
|
spawnedProcess = subprocess;
|
|
2693
2664
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
@@ -2776,13 +2747,12 @@ async function executeStreamingCommand(input) {
|
|
|
2776
2747
|
});
|
|
2777
2748
|
return;
|
|
2778
2749
|
}
|
|
2779
|
-
const
|
|
2750
|
+
const targetProcessEnv2 = await prepareShellEnvForTarget({
|
|
2780
2751
|
target: input.resolvedTarget.target,
|
|
2781
2752
|
baseUrl: input.baseUrl,
|
|
2782
2753
|
token: input.token,
|
|
2783
2754
|
sessionId: input.message.sessionId,
|
|
2784
|
-
|
|
2785
|
-
planRoot: input.planRoot,
|
|
2755
|
+
rootDir: input.rootDir,
|
|
2786
2756
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2787
2757
|
});
|
|
2788
2758
|
const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
|
|
@@ -2796,7 +2766,7 @@ async function executeStreamingCommand(input) {
|
|
|
2796
2766
|
stdout: "pipe",
|
|
2797
2767
|
stderr: "pipe",
|
|
2798
2768
|
detached: true,
|
|
2799
|
-
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {},
|
|
2769
|
+
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv2])
|
|
2800
2770
|
});
|
|
2801
2771
|
spawnedProcess = subprocess;
|
|
2802
2772
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
@@ -3260,10 +3230,10 @@ async function openPty(input) {
|
|
|
3260
3230
|
}
|
|
3261
3231
|
const planProcessEnv = !(input.resolvedTarget.target.type === "workspace" && input.resolvedTarget.target.rootProfile === "canonical_sync") ? preparePlanEnvForShell({
|
|
3262
3232
|
target: input.resolvedTarget.target,
|
|
3263
|
-
|
|
3233
|
+
rootDir: input.rootDir,
|
|
3264
3234
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
3265
3235
|
}) : {};
|
|
3266
|
-
const
|
|
3236
|
+
const targetEnv = targetProcessEnv(input.rootDir, input.resolvedTarget.target);
|
|
3267
3237
|
const shell = resolveHostShell(input.message.command);
|
|
3268
3238
|
const outputCoalescer = createPtyOutputCoalescer({
|
|
3269
3239
|
send: (data) => {
|
|
@@ -3295,7 +3265,7 @@ async function openPty(input) {
|
|
|
3295
3265
|
githubProcessEnv(),
|
|
3296
3266
|
resolvePtyEnvFileReferences(input.message.env ?? {}, stagedEnvFiles),
|
|
3297
3267
|
planProcessEnv,
|
|
3298
|
-
|
|
3268
|
+
targetEnv,
|
|
3299
3269
|
shell.env ?? {}
|
|
3300
3270
|
])
|
|
3301
3271
|
},
|
|
@@ -3468,12 +3438,13 @@ async function startWorker(options) {
|
|
|
3468
3438
|
initializeCredentialStoreDirectoryOnce();
|
|
3469
3439
|
const label = requireValue(options.label, "Missing required --label <label>");
|
|
3470
3440
|
validateLabel(label);
|
|
3471
|
-
const
|
|
3472
|
-
const
|
|
3441
|
+
const rootDir = path.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
|
|
3442
|
+
const projectsRoot = path.join(rootDir, "projects");
|
|
3443
|
+
const syncRoot = path.join(rootDir, "sync");
|
|
3473
3444
|
const workspaceShadowRoot = path.join(syncRoot, "workspace");
|
|
3474
3445
|
const projectWorktreeSnapshotsRoot = path.join(syncRoot, "project-worktree-snapshots");
|
|
3475
|
-
const artifactRoot = path.
|
|
3476
|
-
const planRoot = path.
|
|
3446
|
+
const artifactRoot = path.join(rootDir, "artifacts");
|
|
3447
|
+
const planRoot = path.join(rootDir, "plans");
|
|
3477
3448
|
assertDisjointManagedRoots([
|
|
3478
3449
|
{ label: "projects root", rootPath: projectsRoot },
|
|
3479
3450
|
{ label: "sync root", rootPath: syncRoot },
|
|
@@ -3482,13 +3453,7 @@ async function startWorker(options) {
|
|
|
3482
3453
|
]);
|
|
3483
3454
|
process.stdout.write(`[r5d-worker] label: ${label}
|
|
3484
3455
|
`);
|
|
3485
|
-
process.stdout.write(`[r5d-worker]
|
|
3486
|
-
`);
|
|
3487
|
-
process.stdout.write(`[r5d-worker] sync root: ${syncRoot}
|
|
3488
|
-
`);
|
|
3489
|
-
process.stdout.write(`[r5d-worker] artifact root: ${artifactRoot}
|
|
3490
|
-
`);
|
|
3491
|
-
process.stdout.write(`[r5d-worker] plan root: ${planRoot}
|
|
3456
|
+
process.stdout.write(`[r5d-worker] root: ${rootDir}
|
|
3492
3457
|
`);
|
|
3493
3458
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
3494
3459
|
`);
|
|
@@ -3513,9 +3478,29 @@ async function startWorker(options) {
|
|
|
3513
3478
|
const reconciledProjectConfigFingerprints = /* @__PURE__ */ new Map();
|
|
3514
3479
|
const lastObservedProjectHeads = /* @__PURE__ */ new Map();
|
|
3515
3480
|
const advertisedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
|
|
3481
|
+
const advertisedProjectMirrorMountTokens = /* @__PURE__ */ new Map();
|
|
3482
|
+
let advertisedWorkspaceHead;
|
|
3516
3483
|
const fetchedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
|
|
3517
|
-
const
|
|
3484
|
+
const fetchedProjectMirrorMountTokens = /* @__PURE__ */ new Map();
|
|
3518
3485
|
const projectMirrorFetchSkippingEnabled = process.env.R5D_PROJECT_MIRROR_FETCH_SKIP !== "0";
|
|
3486
|
+
const forgetLocalProjectMirrorRefsTokens = (projectId, branchName) => {
|
|
3487
|
+
fetchedProjectMirrorRefsTokens.delete(projectId);
|
|
3488
|
+
if (branchName === void 0) fetchedProjectMirrorMountTokens.delete(projectId);
|
|
3489
|
+
else fetchedProjectMirrorMountTokens.get(projectId)?.delete(branchName);
|
|
3490
|
+
};
|
|
3491
|
+
const recordLocalProjectMirrorRefsTokens = async (project) => {
|
|
3492
|
+
try {
|
|
3493
|
+
const tokens = await computeLocalProjectMirrorRefsTokens({
|
|
3494
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
3495
|
+
primaryBranchName: primaryProjectBranch(project),
|
|
3496
|
+
mounts: project.branches.map(({ branchId, branchName }) => ({ branchId, branchName }))
|
|
3497
|
+
});
|
|
3498
|
+
fetchedProjectMirrorRefsTokens.set(project.projectId, tokens.project);
|
|
3499
|
+
fetchedProjectMirrorMountTokens.set(project.projectId, tokens.mounts);
|
|
3500
|
+
} catch {
|
|
3501
|
+
forgetLocalProjectMirrorRefsTokens(project.projectId);
|
|
3502
|
+
}
|
|
3503
|
+
};
|
|
3519
3504
|
const pendingMirrorDeletes = /* @__PURE__ */ new Map();
|
|
3520
3505
|
let workspaceRemoteUrl = null;
|
|
3521
3506
|
let workspaceCredentialHelper = null;
|
|
@@ -3530,10 +3515,12 @@ async function startWorker(options) {
|
|
|
3530
3515
|
let workspacePeriodicTimer;
|
|
3531
3516
|
let pendingAutomaticTrigger;
|
|
3532
3517
|
const pendingCreatedBranchPublicationNotBefore = /* @__PURE__ */ new Map();
|
|
3518
|
+
const workspaceProjectionLedger = new WorkspaceProjectionLedger();
|
|
3533
3519
|
let automaticSyncInFlight = false;
|
|
3534
3520
|
let terminalReplayTimer;
|
|
3535
3521
|
let workspaceSyncRequestsInFlight = 0;
|
|
3536
3522
|
let lastWorkspaceSyncMirrorObservationMs = 0;
|
|
3523
|
+
let lastInventoriedMissingCheckouts;
|
|
3537
3524
|
let sessionArtifactSyncRequestsInFlight = 0;
|
|
3538
3525
|
let cliUpdateInProgress = false;
|
|
3539
3526
|
let reloadAfterClose = false;
|
|
@@ -3591,8 +3578,15 @@ async function startWorker(options) {
|
|
|
3591
3578
|
const primaryBranchName = primaryProjectBranch(project);
|
|
3592
3579
|
const connection = projectConnection(project);
|
|
3593
3580
|
const sourcePath = configuredProjectBranchPath(projectsRoot, project, branchName);
|
|
3594
|
-
|
|
3595
|
-
|
|
3581
|
+
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryBranchName);
|
|
3582
|
+
if (requireLocalBranch || fs.existsSync(sourcePath) && hasProjectWorktree(primaryPath)) {
|
|
3583
|
+
const deleted = deleteLinkedProjectBranch({ projectRoot, primaryBranchName, branchName });
|
|
3584
|
+
if (deleted.movedAsidePath) {
|
|
3585
|
+
process.stderr.write(
|
|
3586
|
+
`[r5d-worker] set aside ${sourcePath}, which was not a linked worktree of ${project.projectPath}, at ${deleted.movedAsidePath}
|
|
3587
|
+
`
|
|
3588
|
+
);
|
|
3589
|
+
}
|
|
3596
3590
|
}
|
|
3597
3591
|
const planSourcePath = path.join(planRoot, project.projectId, ...branchName.split("/"));
|
|
3598
3592
|
fs.rmSync(planSourcePath, { recursive: true, force: true });
|
|
@@ -3665,11 +3659,17 @@ async function startWorker(options) {
|
|
|
3665
3659
|
if (branchName === primaryBranchName) {
|
|
3666
3660
|
throw new Error(`Cannot hide preserve-only project branch ${project.projectId}/${branchName} because it is the active anchor`);
|
|
3667
3661
|
}
|
|
3668
|
-
deleteLinkedProjectBranch({
|
|
3662
|
+
const deleted = deleteLinkedProjectBranch({
|
|
3669
3663
|
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
3670
3664
|
primaryBranchName,
|
|
3671
3665
|
branchName
|
|
3672
3666
|
});
|
|
3667
|
+
if (deleted.movedAsidePath) {
|
|
3668
|
+
process.stderr.write(
|
|
3669
|
+
`[r5d-worker] set aside ${branchPath}, which was not a linked worktree of ${project.projectPath}, at ${deleted.movedAsidePath}
|
|
3670
|
+
`
|
|
3671
|
+
);
|
|
3672
|
+
}
|
|
3673
3673
|
fs.rmSync(path.join(planRoot, project.projectId, ...branchName.split("/")), { recursive: true, force: true });
|
|
3674
3674
|
readyProjectIds.delete(project.projectId);
|
|
3675
3675
|
reconciledProjectConfigFingerprints.delete(project.projectId);
|
|
@@ -3838,6 +3838,40 @@ async function startWorker(options) {
|
|
|
3838
3838
|
});
|
|
3839
3839
|
};
|
|
3840
3840
|
const projectBranchMountBusy = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectBranchMountActivityBusy(projectId, branchName, branchPath);
|
|
3841
|
+
const inventoryConfiguredProjectCheckouts = () => {
|
|
3842
|
+
const inventory = { projects: [], missingCheckouts: [] };
|
|
3843
|
+
for (const project of projectConfigById.values()) {
|
|
3844
|
+
if (project.executionDisabled || !readyProjectIds.has(project.projectId) || project.branches.length === 0) {
|
|
3845
|
+
inventory.projects.push(project);
|
|
3846
|
+
continue;
|
|
3847
|
+
}
|
|
3848
|
+
const checkouts = inventoryProjectCheckouts({
|
|
3849
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
3850
|
+
primaryBranchName: primaryProjectBranch(project),
|
|
3851
|
+
branchNames: project.branches.map(({ branchName }) => branchName)
|
|
3852
|
+
});
|
|
3853
|
+
if (checkouts.missingCheckouts.length === 0) {
|
|
3854
|
+
inventory.projects.push(project);
|
|
3855
|
+
continue;
|
|
3856
|
+
}
|
|
3857
|
+
for (const missing of checkouts.missingCheckouts) inventory.missingCheckouts.push({ projectId: project.projectId, ...missing });
|
|
3858
|
+
const linked = new Set(checkouts.linkedBranchNames);
|
|
3859
|
+
inventory.projects.push({ ...project, branches: project.branches.filter(({ branchName }) => linked.has(branchName)) });
|
|
3860
|
+
}
|
|
3861
|
+
inventory.missingCheckouts.sort(
|
|
3862
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
3863
|
+
);
|
|
3864
|
+
return inventory;
|
|
3865
|
+
};
|
|
3866
|
+
const reportedMissingCheckouts = (inventory) => inventory.missingCheckouts.map(({ projectId, branchName, reason }) => ({ projectId, branchName, reason }));
|
|
3867
|
+
const logMissingCheckouts = (inventory) => {
|
|
3868
|
+
if (inventory.missingCheckouts.length === 0) return;
|
|
3869
|
+
const described = inventory.missingCheckouts.map(({ checkoutPath, reason }) => `${checkoutPath} (${reason === "missing" ? "directory missing" : "not a linked worktree of the project"})`).join(", ");
|
|
3870
|
+
process.stderr.write(
|
|
3871
|
+
`[r5d-worker] skipping ${inventory.missingCheckouts.length} configured project checkout(s) this cycle: ${described}; delete the branch through the platform or reconnect this worker to recreate the checkout
|
|
3872
|
+
`
|
|
3873
|
+
);
|
|
3874
|
+
};
|
|
3841
3875
|
const buildWorkspaceMounts = (projects = projectConfigById.values()) => {
|
|
3842
3876
|
const mounts = [];
|
|
3843
3877
|
const creatorLocalIncarnations = new Map(
|
|
@@ -3899,7 +3933,7 @@ async function startWorker(options) {
|
|
|
3899
3933
|
workspaceRelativePath: "workspace-plans",
|
|
3900
3934
|
sourceMode: "all",
|
|
3901
3935
|
hydrateDeletionMode: "all",
|
|
3902
|
-
// Workspace-level agents
|
|
3936
|
+
// Workspace-level agents reach this directory through $R5D_ROOT/plans.
|
|
3903
3937
|
// Keep both projection and hydration away from it while any visible-
|
|
3904
3938
|
// projects process, PTY, or built-in write/edit is active or reserved.
|
|
3905
3939
|
busy: () => {
|
|
@@ -3937,7 +3971,10 @@ async function startWorker(options) {
|
|
|
3937
3971
|
deleteWhenSourceMissing: true
|
|
3938
3972
|
});
|
|
3939
3973
|
}
|
|
3940
|
-
return mounts
|
|
3974
|
+
return mounts.map((mount) => {
|
|
3975
|
+
const lastProjectedMutationToken = workspaceProjectionLedger.lastProjectedToken(mount);
|
|
3976
|
+
return lastProjectedMutationToken === void 0 ? mount : { ...mount, lastProjectedMutationToken };
|
|
3977
|
+
});
|
|
3941
3978
|
};
|
|
3942
3979
|
const workspaceLocalHead = async () => {
|
|
3943
3980
|
if (!fs.existsSync(path.join(workspaceShadowRoot, ".git"))) return null;
|
|
@@ -4113,9 +4150,9 @@ async function startWorker(options) {
|
|
|
4113
4150
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
4114
4151
|
);
|
|
4115
4152
|
};
|
|
4116
|
-
const captureProjectHeadsForWorkspacePublication = async () => {
|
|
4153
|
+
const captureProjectHeadsForWorkspacePublication = async (projects = projectConfigById.values()) => {
|
|
4117
4154
|
const heads = /* @__PURE__ */ new Map();
|
|
4118
|
-
for (const project of
|
|
4155
|
+
for (const project of projects) {
|
|
4119
4156
|
if (!readyProjectIds.has(project.projectId)) continue;
|
|
4120
4157
|
for (const branch of project.branches) {
|
|
4121
4158
|
const branchPath = configuredProjectBranchPath(projectsRoot, project, branch.branchName);
|
|
@@ -4125,9 +4162,9 @@ async function startWorker(options) {
|
|
|
4125
4162
|
}
|
|
4126
4163
|
return heads;
|
|
4127
4164
|
};
|
|
4128
|
-
const pushChangedProjectHeads = async (activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, suppressedMountIds = /* @__PURE__ */ new Set()) => {
|
|
4165
|
+
const pushChangedProjectHeads = async (activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, suppressedMountIds = /* @__PURE__ */ new Set(), projects = projectConfigById.values()) => {
|
|
4129
4166
|
const active = new Set(activeMountIds);
|
|
4130
|
-
for (const project of
|
|
4167
|
+
for (const project of projects) {
|
|
4131
4168
|
if (!readyProjectIds.has(project.projectId) || !repositoryMirrorWritesAllowed(project)) continue;
|
|
4132
4169
|
const changed = /* @__PURE__ */ new Map();
|
|
4133
4170
|
for (const branch of project.branches) {
|
|
@@ -4162,17 +4199,7 @@ async function startWorker(options) {
|
|
|
4162
4199
|
for (const result of results) {
|
|
4163
4200
|
lastObservedProjectHeads.set(projectBranchKey(project.projectId, result.branchName), result.head);
|
|
4164
4201
|
}
|
|
4165
|
-
|
|
4166
|
-
fetchedProjectMirrorRefsTokens.set(
|
|
4167
|
-
project.projectId,
|
|
4168
|
-
await computeLocalProjectMirrorRefsToken({
|
|
4169
|
-
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
4170
|
-
primaryBranchName: primaryProjectBranch(project)
|
|
4171
|
-
})
|
|
4172
|
-
);
|
|
4173
|
-
} catch {
|
|
4174
|
-
fetchedProjectMirrorRefsTokens.delete(project.projectId);
|
|
4175
|
-
}
|
|
4202
|
+
await recordLocalProjectMirrorRefsTokens(project);
|
|
4176
4203
|
}
|
|
4177
4204
|
const publishedTombstoneIds = [
|
|
4178
4205
|
...new Set(
|
|
@@ -4198,7 +4225,7 @@ async function startWorker(options) {
|
|
|
4198
4225
|
credentialHelper: deletion.credentialHelper,
|
|
4199
4226
|
credentialUsername: deletion.credentialUsername
|
|
4200
4227
|
});
|
|
4201
|
-
|
|
4228
|
+
forgetLocalProjectMirrorRefsTokens(deletion.projectId, deletion.branchName);
|
|
4202
4229
|
}
|
|
4203
4230
|
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
4204
4231
|
projectWorkspaceState = projectWorkspaceStateStore.recordMirrorRefDeletion({
|
|
@@ -4210,8 +4237,8 @@ async function startWorker(options) {
|
|
|
4210
4237
|
if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
|
|
4211
4238
|
removeProjectCredentialStore(deletion.projectId);
|
|
4212
4239
|
advertisedProjectMirrorRefsTokens.delete(deletion.projectId);
|
|
4213
|
-
|
|
4214
|
-
|
|
4240
|
+
advertisedProjectMirrorMountTokens.delete(deletion.projectId);
|
|
4241
|
+
forgetLocalProjectMirrorRefsTokens(deletion.projectId);
|
|
4215
4242
|
}
|
|
4216
4243
|
lastObservedProjectHeads.delete(key);
|
|
4217
4244
|
}
|
|
@@ -4220,71 +4247,63 @@ async function startWorker(options) {
|
|
|
4220
4247
|
const connection = projectConnection(project);
|
|
4221
4248
|
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
4222
4249
|
const primaryBranchName = primaryProjectBranch(project);
|
|
4223
|
-
|
|
4224
|
-
const advertisedBeforeFetch = advertisedProjectMirrorRefsTokens.get(project.projectId);
|
|
4225
|
-
const observations = await observeProjectMirrorHeads({
|
|
4226
|
-
projectRoot,
|
|
4227
|
-
primaryBranchName,
|
|
4228
|
-
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4229
|
-
mirrorUrl: connection.mirrorUrl,
|
|
4230
|
-
credentialHelper: connection.credentialHelper,
|
|
4231
|
-
credentialUsername: connection.mirrorCredentialUsername,
|
|
4232
|
-
allowNonFastForward: options2.allowNonFastForward,
|
|
4233
|
-
branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId]))
|
|
4234
|
-
});
|
|
4235
|
-
if (advertisedBeforeFetch === void 0) fetchedProjectMirrorRefsTokens.delete(project.projectId);
|
|
4236
|
-
else fetchedProjectMirrorRefsTokens.set(project.projectId, advertisedBeforeFetch);
|
|
4237
|
-
lastProjectMirrorFetchAtMs.set(project.projectId, Date.now());
|
|
4238
|
-
return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
|
|
4239
|
-
}
|
|
4240
|
-
return {
|
|
4241
|
-
projectId: project.projectId,
|
|
4250
|
+
const observations = await observeProjectMirrorHeads({
|
|
4242
4251
|
projectRoot,
|
|
4243
4252
|
primaryBranchName,
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
};
|
|
4253
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4254
|
+
mirrorUrl: connection.mirrorUrl,
|
|
4255
|
+
credentialHelper: connection.credentialHelper,
|
|
4256
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
4257
|
+
allowNonFastForward: options2.allowNonFastForward,
|
|
4258
|
+
branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId])),
|
|
4259
|
+
refreshRemote: options2.fetch.scope !== "none",
|
|
4260
|
+
...options2.fetch.scope === "mounts" ? { fetchOnlyBranches: new Set(options2.fetch.mounts.map(({ branchName }) => branchName)) } : {}
|
|
4261
|
+
});
|
|
4262
|
+
if (options2.fetch.scope !== "none") await recordLocalProjectMirrorRefsTokens(project);
|
|
4263
|
+
return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
|
|
4256
4264
|
};
|
|
4257
4265
|
const observeProjectHeadsBeforeOuterWorkspace = async (input) => {
|
|
4258
4266
|
const observationStartedAt = Date.now();
|
|
4259
|
-
const eligibleProjects = [...projectConfigById.values()].filter(
|
|
4267
|
+
const eligibleProjects = [...input.projects ?? projectConfigById.values()].filter(
|
|
4260
4268
|
(project) => !project.executionDisabled && readyProjectIds.has(project.projectId) && project.branches.length > 0
|
|
4261
4269
|
);
|
|
4262
4270
|
try {
|
|
4263
4271
|
if (input.failClosed) {
|
|
4264
4272
|
const bundles2 = [];
|
|
4265
4273
|
for (const project of eligibleProjects) {
|
|
4266
|
-
bundles2.push(
|
|
4274
|
+
bundles2.push(
|
|
4275
|
+
await observeSingleProjectMirrorBundle(project, {
|
|
4276
|
+
allowNonFastForward: input.allowNonFastForward,
|
|
4277
|
+
fetch: { scope: "project", reason: "fail_closed" }
|
|
4278
|
+
})
|
|
4279
|
+
);
|
|
4267
4280
|
}
|
|
4268
4281
|
return bundles2;
|
|
4269
4282
|
}
|
|
4270
|
-
let
|
|
4283
|
+
let wholeMirrorFetches = 0;
|
|
4284
|
+
let scopedMirrorFetches = 0;
|
|
4285
|
+
let fetchedMounts = 0;
|
|
4286
|
+
let eligibleMounts = 0;
|
|
4271
4287
|
const bundles = await mapWithConcurrencyLimit(
|
|
4272
4288
|
eligibleProjects,
|
|
4273
4289
|
PROJECT_MIRROR_OBSERVE_CONCURRENCY,
|
|
4274
4290
|
async (project) => {
|
|
4275
4291
|
try {
|
|
4276
|
-
const
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4292
|
+
const fetch2 = planProjectMirrorObservationFetch({
|
|
4293
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4294
|
+
advertisedProjectToken: advertisedProjectMirrorRefsTokens.get(project.projectId),
|
|
4295
|
+
advertisedMountTokens: advertisedProjectMirrorMountTokens.get(project.projectId),
|
|
4296
|
+
fetchedProjectToken: fetchedProjectMirrorRefsTokens.get(project.projectId),
|
|
4297
|
+
fetchedMountTokens: fetchedProjectMirrorMountTokens.get(project.projectId) ?? /* @__PURE__ */ new Map(),
|
|
4281
4298
|
skippingEnabled: projectMirrorFetchSkippingEnabled
|
|
4282
4299
|
});
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4300
|
+
eligibleMounts += project.branches.length;
|
|
4301
|
+
if (fetch2.scope === "project") wholeMirrorFetches += 1;
|
|
4302
|
+
if (fetch2.scope === "mounts") {
|
|
4303
|
+
scopedMirrorFetches += 1;
|
|
4304
|
+
fetchedMounts += fetch2.mounts.length;
|
|
4305
|
+
}
|
|
4306
|
+
return await observeSingleProjectMirrorBundle(project, { allowNonFastForward: input.allowNonFastForward, fetch: fetch2 });
|
|
4288
4307
|
} catch (error) {
|
|
4289
4308
|
process.stderr.write(
|
|
4290
4309
|
`[r5d-worker] failed to observe project mirror heads before workspace update: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -4294,9 +4313,9 @@ async function startWorker(options) {
|
|
|
4294
4313
|
}
|
|
4295
4314
|
}
|
|
4296
4315
|
);
|
|
4297
|
-
if (
|
|
4316
|
+
if (wholeMirrorFetches + scopedMirrorFetches > 0) {
|
|
4298
4317
|
process.stderr.write(
|
|
4299
|
-
`[r5d-worker] project mirror observation fetched ${
|
|
4318
|
+
`[r5d-worker] project mirror observation fetched ${wholeMirrorFetches + scopedMirrorFetches} of ${eligibleProjects.length} mirror(s) (${wholeMirrorFetches} whole, ${scopedMirrorFetches} scoped to ${fetchedMounts} of ${eligibleMounts} mount(s))
|
|
4300
4319
|
`
|
|
4301
4320
|
);
|
|
4302
4321
|
}
|
|
@@ -4355,7 +4374,7 @@ async function startWorker(options) {
|
|
|
4355
4374
|
}
|
|
4356
4375
|
}
|
|
4357
4376
|
};
|
|
4358
|
-
const mapWorkspaceGitResult = async (attemptId, trigger, result) => {
|
|
4377
|
+
const mapWorkspaceGitResult = async (attemptId, trigger, result, missingCheckouts) => {
|
|
4359
4378
|
const activeProjectBranchPublications = activeProjectBranchPublicationEvidence([...projectConfigById.values()], result.activeMountIds);
|
|
4360
4379
|
return {
|
|
4361
4380
|
type: "workspace_sync",
|
|
@@ -4373,6 +4392,7 @@ async function startWorker(options) {
|
|
|
4373
4392
|
affectedPaths: result.affectedPaths,
|
|
4374
4393
|
activeMountIds: result.activeMountIds,
|
|
4375
4394
|
skippedMountIds: result.skippedMountIds,
|
|
4395
|
+
...missingCheckouts ? { missingCheckouts } : {},
|
|
4376
4396
|
activeProjectBranchPublications,
|
|
4377
4397
|
discardedPaths: [],
|
|
4378
4398
|
localChangesDiscarded: false,
|
|
@@ -4400,6 +4420,7 @@ async function startWorker(options) {
|
|
|
4400
4420
|
affectedPaths: [],
|
|
4401
4421
|
discardedPaths: [],
|
|
4402
4422
|
localChangesDiscarded: false,
|
|
4423
|
+
...lastInventoriedMissingCheckouts ? { missingCheckouts: lastInventoriedMissingCheckouts } : {},
|
|
4403
4424
|
error: error instanceof Error ? error.message : String(error)
|
|
4404
4425
|
};
|
|
4405
4426
|
};
|
|
@@ -4408,8 +4429,13 @@ async function startWorker(options) {
|
|
|
4408
4429
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4409
4430
|
throw new Error("Worker workspace configuration has not been received");
|
|
4410
4431
|
}
|
|
4411
|
-
const
|
|
4432
|
+
const checkoutInventory = inventoryConfiguredProjectCheckouts();
|
|
4433
|
+
logMissingCheckouts(checkoutInventory);
|
|
4434
|
+
const missingCheckouts = reportedMissingCheckouts(checkoutInventory);
|
|
4435
|
+
lastInventoriedMissingCheckouts = missingCheckouts;
|
|
4436
|
+
const configuredMounts = buildWorkspaceMounts(checkoutInventory.projects);
|
|
4412
4437
|
if (input.resetToCanonical) {
|
|
4438
|
+
workspaceProjectionLedger.reset();
|
|
4413
4439
|
const resetHasBusyLiveMount = configuredMounts.some(
|
|
4414
4440
|
(mount) => !(mount.deleteWhenSourceMissing && !fs.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
4415
4441
|
);
|
|
@@ -4418,7 +4444,8 @@ async function startWorker(options) {
|
|
|
4418
4444
|
}
|
|
4419
4445
|
const observedProjectHeads2 = await observeProjectHeadsBeforeOuterWorkspace({
|
|
4420
4446
|
allowNonFastForward: true,
|
|
4421
|
-
failClosed: true
|
|
4447
|
+
failClosed: true,
|
|
4448
|
+
projects: checkoutInventory.projects
|
|
4422
4449
|
});
|
|
4423
4450
|
const reset = await resetWorkspaceGit({
|
|
4424
4451
|
workspacePath: workspaceShadowRoot,
|
|
@@ -4446,13 +4473,15 @@ async function startWorker(options) {
|
|
|
4446
4473
|
affectedPaths: reset.discardedPaths,
|
|
4447
4474
|
activeMountIds: reset.activeMountIds,
|
|
4448
4475
|
skippedMountIds: reset.skippedMountIds,
|
|
4476
|
+
missingCheckouts,
|
|
4449
4477
|
discardedPaths: reset.discardedPaths,
|
|
4450
4478
|
localChangesDiscarded: reset.discardedPaths.length > 0 || reset.startingHead !== reset.remoteHead
|
|
4451
4479
|
};
|
|
4452
4480
|
}
|
|
4453
4481
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
4454
4482
|
const mounts = outerRemediation ? workspaceGitMountsForRemediation(configuredMounts) : configuredMounts;
|
|
4455
|
-
|
|
4483
|
+
if (outerRemediation) workspaceProjectionLedger.reset();
|
|
4484
|
+
const observedProjectHeads = outerRemediation ? [] : await observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false, projects: checkoutInventory.projects });
|
|
4456
4485
|
const expectedMirrorHeads = /* @__PURE__ */ new Map();
|
|
4457
4486
|
for (const bundle of observedProjectHeads) {
|
|
4458
4487
|
for (const observation of bundle.observations) {
|
|
@@ -4464,7 +4493,7 @@ async function startWorker(options) {
|
|
|
4464
4493
|
({ projectId, observations }) => observations.filter(({ shouldMove }) => shouldMove).map(({ branchName }) => projectMountId(projectId, branchName))
|
|
4465
4494
|
)
|
|
4466
4495
|
);
|
|
4467
|
-
const publicationHeads = outerRemediation ? /* @__PURE__ */ new Map() : await captureProjectHeadsForWorkspacePublication();
|
|
4496
|
+
const publicationHeads = outerRemediation ? /* @__PURE__ */ new Map() : await captureProjectHeadsForWorkspacePublication(checkoutInventory.projects);
|
|
4468
4497
|
const result = await synchronizeWorkspaceGit({
|
|
4469
4498
|
attemptId: input.attemptId,
|
|
4470
4499
|
workerLabel: label,
|
|
@@ -4478,9 +4507,20 @@ async function startWorker(options) {
|
|
|
4478
4507
|
allowLargeDiff: input.confirmedLargeDiff,
|
|
4479
4508
|
skipMountMirror: outerRemediation,
|
|
4480
4509
|
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4510
|
+
// Same gate as the mirror fetches: while the server's read of the hidden
|
|
4511
|
+
// workspace head equals the head this clone last fetched or pushed, the
|
|
4512
|
+
// cycle's workspace head fetches would transfer nothing.
|
|
4513
|
+
advertisedRemoteHead: projectMirrorFetchSkippingEnabled && !outerRemediation ? advertisedWorkspaceHead : void 0,
|
|
4481
4514
|
assertStillAdmitted: input.assertStillAdmitted,
|
|
4482
4515
|
afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
|
|
4483
|
-
await pushChangedProjectHeads(
|
|
4516
|
+
await pushChangedProjectHeads(
|
|
4517
|
+
activeMountIds,
|
|
4518
|
+
publishedHead,
|
|
4519
|
+
publicationHeads,
|
|
4520
|
+
expectedMirrorHeads,
|
|
4521
|
+
inboundMoveMountIds,
|
|
4522
|
+
checkoutInventory.projects
|
|
4523
|
+
);
|
|
4484
4524
|
},
|
|
4485
4525
|
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4486
4526
|
});
|
|
@@ -4499,7 +4539,11 @@ async function startWorker(options) {
|
|
|
4499
4539
|
result.localHead ?? result.remoteHead
|
|
4500
4540
|
);
|
|
4501
4541
|
}
|
|
4502
|
-
|
|
4542
|
+
const mapped = await mapWorkspaceGitResult(input.attemptId, input.trigger, result, missingCheckouts);
|
|
4543
|
+
if (!outerRemediation) {
|
|
4544
|
+
workspaceProjectionLedger.recordCycle({ result: mapped, mounts, projectedMutationTokens: result.projectedMutationTokens });
|
|
4545
|
+
}
|
|
4546
|
+
return mapped;
|
|
4503
4547
|
};
|
|
4504
4548
|
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
4505
4549
|
try {
|
|
@@ -4537,17 +4581,143 @@ async function startWorker(options) {
|
|
|
4537
4581
|
}, unsafeWorkspaceSyncRecoveryBounceDelayMs(attempt));
|
|
4538
4582
|
timer.unref();
|
|
4539
4583
|
};
|
|
4584
|
+
const captureWorkspaceSyncAdmission = () => ({
|
|
4585
|
+
generation: workerAdmissionGeneration,
|
|
4586
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
4587
|
+
deferredIncidentId: deferredWorkspaceConfiguration?.incidentId ?? null
|
|
4588
|
+
});
|
|
4589
|
+
const workspaceSyncAdmissionSnapshotsMatch = (left, right) => left.generation === right.generation && left.activeIncidentId === right.activeIncidentId && left.deferredIncidentId === right.deferredIncidentId;
|
|
4590
|
+
const assertWorkspaceSyncStillAdmitted = (admitted, trigger) => {
|
|
4591
|
+
if (!workspaceSyncAdmissionSnapshotsMatch(admitted, captureWorkspaceSyncAdmission()) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, trigger)) {
|
|
4592
|
+
throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4593
|
+
}
|
|
4594
|
+
};
|
|
4595
|
+
const executeWorkspaceSyncCycle = async (input) => {
|
|
4596
|
+
const attemptId = input.attemptId;
|
|
4597
|
+
lastWorkspaceSyncMirrorObservationMs = 0;
|
|
4598
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4599
|
+
return await failedWorkspaceSyncResult(
|
|
4600
|
+
attemptId,
|
|
4601
|
+
input.trigger,
|
|
4602
|
+
new Error(
|
|
4603
|
+
`Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
4604
|
+
)
|
|
4605
|
+
);
|
|
4606
|
+
}
|
|
4607
|
+
try {
|
|
4608
|
+
input.assertStillAdmitted();
|
|
4609
|
+
const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
|
|
4610
|
+
return await performWorkspaceSync({
|
|
4611
|
+
attemptId,
|
|
4612
|
+
trigger: input.trigger,
|
|
4613
|
+
confirmedLargeDiff: input.confirmedLargeDiff === true || hasDurableBranchDeletion,
|
|
4614
|
+
confirmationReason: input.confirmationReason ?? (hasDurableBranchDeletion ? "durable project branch deletion" : void 0),
|
|
4615
|
+
resetToCanonical: input.resetToCanonical,
|
|
4616
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4617
|
+
assertStillAdmitted: input.assertStillAdmitted
|
|
4618
|
+
});
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
workspaceProjectionLedger.reset();
|
|
4621
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4622
|
+
const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
|
|
4623
|
+
error,
|
|
4624
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4625
|
+
inspectRecoverableHydration: () => workspaceGitHydrationIsRecoverable(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4626
|
+
});
|
|
4627
|
+
if (hydrationRecoverable) {
|
|
4628
|
+
if (error instanceof WorkspaceSyncAdmissionChangedError) {
|
|
4629
|
+
process.stderr.write(
|
|
4630
|
+
`[r5d-worker] workspace synchronization was cancelled with every mount at a recorded basis; the next cycle converges it: ${errorMessage}
|
|
4631
|
+
`
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4634
|
+
return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
|
|
4635
|
+
}
|
|
4636
|
+
const fenced = fenceUnsafeWorkspaceSyncFailure({
|
|
4637
|
+
hydrationRecoverable,
|
|
4638
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4639
|
+
recoveryAttempts: unsafeWorkspaceSyncRecoveryAttempts,
|
|
4640
|
+
maxRecoveryAttempts: WORKSPACE_UNSAFE_SYNC_MAX_RECOVERY_ATTEMPTS,
|
|
4641
|
+
hasRetainableWork: activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0,
|
|
4642
|
+
bouncePending: unsafeWorkspaceSyncBouncePending,
|
|
4643
|
+
invalidateExecution: () => {
|
|
4644
|
+
workspaceConfigured = false;
|
|
4645
|
+
advanceWorkerAdmissionGeneration();
|
|
4646
|
+
},
|
|
4647
|
+
scheduleRecoveryBounce: () => {
|
|
4648
|
+
unsafeWorkspaceSyncRecoveryAttempts += 1;
|
|
4649
|
+
scheduleUnsafeWorkspaceSyncRecoveryBounce(errorMessage);
|
|
4650
|
+
},
|
|
4651
|
+
fatalExit: (exitCode) => {
|
|
4652
|
+
process.stderr.write(
|
|
4653
|
+
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
|
|
4654
|
+
`
|
|
4655
|
+
);
|
|
4656
|
+
process.exit(exitCode);
|
|
4657
|
+
},
|
|
4658
|
+
exitCode: 1
|
|
4659
|
+
});
|
|
4660
|
+
if (fenced === "recovery_pending") {
|
|
4661
|
+
process.stderr.write(
|
|
4662
|
+
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis while in-process recovery is already pending: ${errorMessage}
|
|
4663
|
+
`
|
|
4664
|
+
);
|
|
4665
|
+
}
|
|
4666
|
+
return {
|
|
4667
|
+
...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
|
|
4668
|
+
failureReason: "unsafe_hydration",
|
|
4669
|
+
recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
};
|
|
4673
|
+
const REMEDIATION_TRIGGER_TYPES = /* @__PURE__ */ new Set(["remediation", "remediation_confirm", "remediation_reset"]);
|
|
4674
|
+
const isOrdinaryWorkspaceSyncRequest = (input) => input.confirmedLargeDiff !== true && input.resetToCanonical !== true && !(input.requiredAncestorHeads && input.requiredAncestorHeads.length > 0) && !REMEDIATION_TRIGGER_TYPES.has(input.trigger.type);
|
|
4675
|
+
const LOCAL_ACTIVITY_TRIGGER_TYPES = /* @__PURE__ */ new Set([
|
|
4676
|
+
"write",
|
|
4677
|
+
"edit",
|
|
4678
|
+
"shell_inline",
|
|
4679
|
+
"process_terminal",
|
|
4680
|
+
"process_cancel"
|
|
4681
|
+
]);
|
|
4682
|
+
const answerOrdinaryWorkspaceSyncFromLastCycle = (requests) => {
|
|
4683
|
+
if (!workspaceConfigured) return null;
|
|
4684
|
+
return workspaceProjectionLedger.answerFromLastCycle({
|
|
4685
|
+
// A workspace-rooted command carries no project target because it may
|
|
4686
|
+
// have touched any checkout.
|
|
4687
|
+
targetMountIds: requests.map(
|
|
4688
|
+
({ trigger }) => LOCAL_ACTIVITY_TRIGGER_TYPES.has(trigger.type) && trigger.projectId && trigger.branchName ? projectMountId(trigger.projectId, trigger.branchName) : null
|
|
4689
|
+
),
|
|
4690
|
+
mounts: buildWorkspaceMounts(),
|
|
4691
|
+
pendingDeletions: pendingMirrorDeletes.size > 0 || projectWorkspaceState.pendingTreeDeletions.length > 0 || projectWorkspaceState.pendingMirrorRefDeletions.length > 0
|
|
4692
|
+
});
|
|
4693
|
+
};
|
|
4694
|
+
const ordinaryWorkspaceSyncCoalescer = new WorkspaceSyncCoalescer({
|
|
4695
|
+
runExclusive: (operation) => workspaceSyncSingleFlight.runExclusive(operation),
|
|
4696
|
+
runBatch: async (requests) => {
|
|
4697
|
+
const startedAtMs = Date.now();
|
|
4698
|
+
const admission = captureWorkspaceSyncAdmission();
|
|
4699
|
+
const staleAttemptIds = /* @__PURE__ */ new Set();
|
|
4700
|
+
const admitted = [];
|
|
4701
|
+
for (const request of requests) {
|
|
4702
|
+
if (workspaceSyncAdmissionSnapshotsMatch(request.admission, admission)) admitted.push(request);
|
|
4703
|
+
else staleAttemptIds.add(request.attemptId);
|
|
4704
|
+
}
|
|
4705
|
+
const leader = admitted[0];
|
|
4706
|
+
if (!leader) return { result: null, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
|
|
4707
|
+
const answered = answerOrdinaryWorkspaceSyncFromLastCycle(admitted);
|
|
4708
|
+
if (answered) return { result: answered, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
|
|
4709
|
+
const result = await executeWorkspaceSyncCycle({
|
|
4710
|
+
attemptId: leader.attemptId,
|
|
4711
|
+
trigger: leader.trigger,
|
|
4712
|
+
assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, leader.trigger)
|
|
4713
|
+
});
|
|
4714
|
+
return { result, ranCycle: true, startedAtMs, finishedAtMs: Date.now(), prepareMs: lastWorkspaceSyncMirrorObservationMs, staleAttemptIds };
|
|
4715
|
+
}
|
|
4716
|
+
});
|
|
4540
4717
|
const runWorkspaceSync = async (input) => {
|
|
4541
4718
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
4542
4719
|
const requestedAt = Date.now();
|
|
4543
|
-
const
|
|
4544
|
-
const admittedActiveIncidentId = activeWorkspaceIncidentId;
|
|
4545
|
-
const admittedDeferredIncidentId = deferredWorkspaceConfiguration?.incidentId ?? null;
|
|
4546
|
-
const assertStillAdmitted = () => {
|
|
4547
|
-
if (admittedGeneration !== workerAdmissionGeneration || admittedActiveIncidentId !== activeWorkspaceIncidentId || admittedDeferredIncidentId !== (deferredWorkspaceConfiguration?.incidentId ?? null) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4548
|
-
throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4549
|
-
}
|
|
4550
|
-
};
|
|
4720
|
+
const admission = captureWorkspaceSyncAdmission();
|
|
4551
4721
|
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4552
4722
|
const result2 = {
|
|
4553
4723
|
...await failedWorkspaceSyncResult(
|
|
@@ -4573,104 +4743,47 @@ async function startWorker(options) {
|
|
|
4573
4743
|
}
|
|
4574
4744
|
ws.send(JSON.stringify({ type: "workspace_sync_started", attemptId }));
|
|
4575
4745
|
}
|
|
4576
|
-
let
|
|
4577
|
-
let
|
|
4746
|
+
let queueMs = 0;
|
|
4747
|
+
let prepareMs = 0;
|
|
4748
|
+
let synchronizeMs = 0;
|
|
4578
4749
|
workspaceSyncRequestsInFlight += 1;
|
|
4579
4750
|
let result;
|
|
4580
4751
|
try {
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
|
|
4597
|
-
return await performWorkspaceSync({
|
|
4752
|
+
if (isOrdinaryWorkspaceSyncRequest(input)) {
|
|
4753
|
+
const settlement = await ordinaryWorkspaceSyncCoalescer.request({ attemptId, trigger: input.trigger, admission });
|
|
4754
|
+
queueMs = settlement.startedAtMs - requestedAt;
|
|
4755
|
+
prepareMs = settlement.prepareMs;
|
|
4756
|
+
synchronizeMs = settlement.finishedAtMs - settlement.startedAtMs;
|
|
4757
|
+
result = settlement.staleAttemptIds.has(attemptId) || settlement.result === null ? await failedWorkspaceSyncResult(
|
|
4758
|
+
attemptId,
|
|
4759
|
+
input.trigger,
|
|
4760
|
+
new Error("Workspace synchronization admission changed while the request was queued")
|
|
4761
|
+
) : { ...settlement.result, attemptId, trigger: input.trigger };
|
|
4762
|
+
} else {
|
|
4763
|
+
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4764
|
+
const startedAt = Date.now();
|
|
4765
|
+
queueMs = startedAt - requestedAt;
|
|
4766
|
+
const cycleResult = await executeWorkspaceSyncCycle({
|
|
4598
4767
|
attemptId,
|
|
4599
4768
|
trigger: input.trigger,
|
|
4600
|
-
confirmedLargeDiff: input.confirmedLargeDiff
|
|
4601
|
-
confirmationReason: input.confirmationReason
|
|
4769
|
+
confirmedLargeDiff: input.confirmedLargeDiff,
|
|
4770
|
+
confirmationReason: input.confirmationReason,
|
|
4602
4771
|
resetToCanonical: input.resetToCanonical,
|
|
4603
4772
|
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4604
|
-
assertStillAdmitted
|
|
4773
|
+
assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, input.trigger)
|
|
4605
4774
|
});
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
inspectRecoverableHydration: () => workspaceGitHydrationIsRecoverable(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4612
|
-
});
|
|
4613
|
-
if (hydrationRecoverable) {
|
|
4614
|
-
if (error instanceof WorkspaceSyncAdmissionChangedError) {
|
|
4615
|
-
process.stderr.write(
|
|
4616
|
-
`[r5d-worker] workspace synchronization was cancelled with every mount at a recorded basis; the next cycle converges it: ${errorMessage}
|
|
4617
|
-
`
|
|
4618
|
-
);
|
|
4619
|
-
}
|
|
4620
|
-
return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
|
|
4621
|
-
}
|
|
4622
|
-
const fenced = fenceUnsafeWorkspaceSyncFailure({
|
|
4623
|
-
hydrationRecoverable,
|
|
4624
|
-
resetToCanonical: input.resetToCanonical === true,
|
|
4625
|
-
recoveryAttempts: unsafeWorkspaceSyncRecoveryAttempts,
|
|
4626
|
-
maxRecoveryAttempts: WORKSPACE_UNSAFE_SYNC_MAX_RECOVERY_ATTEMPTS,
|
|
4627
|
-
hasRetainableWork: activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0,
|
|
4628
|
-
bouncePending: unsafeWorkspaceSyncBouncePending,
|
|
4629
|
-
invalidateExecution: () => {
|
|
4630
|
-
workspaceConfigured = false;
|
|
4631
|
-
advanceWorkerAdmissionGeneration();
|
|
4632
|
-
},
|
|
4633
|
-
scheduleRecoveryBounce: () => {
|
|
4634
|
-
unsafeWorkspaceSyncRecoveryAttempts += 1;
|
|
4635
|
-
scheduleUnsafeWorkspaceSyncRecoveryBounce(errorMessage);
|
|
4636
|
-
},
|
|
4637
|
-
fatalExit: (exitCode) => {
|
|
4638
|
-
process.stderr.write(
|
|
4639
|
-
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
|
|
4640
|
-
`
|
|
4641
|
-
);
|
|
4642
|
-
process.exit(exitCode);
|
|
4643
|
-
},
|
|
4644
|
-
exitCode: 1
|
|
4645
|
-
});
|
|
4646
|
-
if (fenced === "recovery_pending") {
|
|
4647
|
-
process.stderr.write(
|
|
4648
|
-
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis while in-process recovery is already pending: ${errorMessage}
|
|
4649
|
-
`
|
|
4650
|
-
);
|
|
4651
|
-
}
|
|
4652
|
-
return {
|
|
4653
|
-
...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
|
|
4654
|
-
failureReason: "unsafe_hydration",
|
|
4655
|
-
recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
|
|
4656
|
-
};
|
|
4657
|
-
}
|
|
4658
|
-
});
|
|
4775
|
+
prepareMs = lastWorkspaceSyncMirrorObservationMs;
|
|
4776
|
+
synchronizeMs = Date.now() - startedAt;
|
|
4777
|
+
return cycleResult;
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
4659
4780
|
} finally {
|
|
4660
4781
|
workspaceSyncRequestsInFlight -= 1;
|
|
4661
4782
|
}
|
|
4662
4783
|
if (result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
|
|
4663
|
-
const finishedAt = Date.now();
|
|
4664
4784
|
result = {
|
|
4665
4785
|
...result,
|
|
4666
|
-
telemetry: {
|
|
4667
|
-
totalMs: finishedAt - requestedAt,
|
|
4668
|
-
queueMs: queueEnteredAt - requestedAt,
|
|
4669
|
-
// Duration of the pre-outer mirror-head observation phase; near zero
|
|
4670
|
-
// on a cycle whose token-gated observation skipped every fetch.
|
|
4671
|
-
prepareMs: lastWorkspaceSyncMirrorObservationMs,
|
|
4672
|
-
synchronizeMs: finishedAt - syncStartedAt
|
|
4673
|
-
}
|
|
4786
|
+
telemetry: { totalMs: Date.now() - requestedAt, queueMs, prepareMs, synchronizeMs }
|
|
4674
4787
|
};
|
|
4675
4788
|
if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result);
|
|
4676
4789
|
sendLifecycleProgress(
|
|
@@ -4940,6 +5053,7 @@ async function startWorker(options) {
|
|
|
4940
5053
|
);
|
|
4941
5054
|
}
|
|
4942
5055
|
workspaceConfigured = false;
|
|
5056
|
+
workspaceProjectionLedger.reset();
|
|
4943
5057
|
const preserveOnlyBranches = message.projects.flatMap(
|
|
4944
5058
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
4945
5059
|
);
|
|
@@ -4949,6 +5063,16 @@ async function startWorker(options) {
|
|
|
4949
5063
|
preserveOnlyBranches
|
|
4950
5064
|
});
|
|
4951
5065
|
}
|
|
5066
|
+
for (const project of message.projects) {
|
|
5067
|
+
for (const branch of project.branches) {
|
|
5068
|
+
const pending2 = projectWorkspaceState.locallyPendingCreatedBranches.find(
|
|
5069
|
+
(candidate) => candidate.projectId === project.projectId && candidate.branchName === branch.branchName
|
|
5070
|
+
);
|
|
5071
|
+
if (pending2 && pending2.branchId === branch.branchId) {
|
|
5072
|
+
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pending2);
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
4952
5076
|
const stillPendingCreatedBranches = new Map(
|
|
4953
5077
|
projectWorkspaceState.locallyPendingCreatedBranches.map((branch) => [
|
|
4954
5078
|
pendingCreatedBranchKey(branch.projectId, branch.branchName),
|
|
@@ -5390,11 +5514,10 @@ async function startWorker(options) {
|
|
|
5390
5514
|
workspaceConfigResetToCanonicalV1: true,
|
|
5391
5515
|
projectBranchDeletionFastAckV1: true,
|
|
5392
5516
|
projectMirrorLeaseV1: true,
|
|
5393
|
-
projectMirrorRefsTokensV1: true
|
|
5517
|
+
projectMirrorRefsTokensV1: true,
|
|
5518
|
+
projectBranchWorkingTreeModeV1: true
|
|
5394
5519
|
},
|
|
5395
|
-
|
|
5396
|
-
artifactRoot,
|
|
5397
|
-
planRoot
|
|
5520
|
+
root: rootDir
|
|
5398
5521
|
}
|
|
5399
5522
|
};
|
|
5400
5523
|
ws.send(JSON.stringify(hello));
|
|
@@ -5429,14 +5552,12 @@ async function startWorker(options) {
|
|
|
5429
5552
|
return;
|
|
5430
5553
|
}
|
|
5431
5554
|
if (message.type === "project_mirror_refs_tokens") {
|
|
5555
|
+
const advertised = parseProjectMirrorRefsTokensMessage(message);
|
|
5432
5556
|
advertisedProjectMirrorRefsTokens.clear();
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
}
|
|
5438
|
-
}
|
|
5439
|
-
}
|
|
5557
|
+
for (const [projectId, token2] of advertised.projectTokens) advertisedProjectMirrorRefsTokens.set(projectId, token2);
|
|
5558
|
+
advertisedProjectMirrorMountTokens.clear();
|
|
5559
|
+
for (const [projectId, tokens] of advertised.mountTokens) advertisedProjectMirrorMountTokens.set(projectId, tokens);
|
|
5560
|
+
advertisedWorkspaceHead = advertised.workspaceHead;
|
|
5440
5561
|
return;
|
|
5441
5562
|
}
|
|
5442
5563
|
if (message.type === "workspace_config") {
|
|
@@ -5509,13 +5630,21 @@ async function startWorker(options) {
|
|
|
5509
5630
|
});
|
|
5510
5631
|
return;
|
|
5511
5632
|
}
|
|
5633
|
+
sendWorkerMessage(ws, {
|
|
5634
|
+
type: "operation_queued",
|
|
5635
|
+
requestId: message.requestId,
|
|
5636
|
+
queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
|
|
5637
|
+
});
|
|
5512
5638
|
try {
|
|
5639
|
+
if (message.workingTree !== "carry" && message.workingTree !== "clean") {
|
|
5640
|
+
throw new Error('Project branch creation requires workingTree "carry" or "clean"');
|
|
5641
|
+
}
|
|
5513
5642
|
const pendingBranch = {
|
|
5514
5643
|
branchId: message.branchId,
|
|
5515
5644
|
projectId: message.projectId,
|
|
5516
5645
|
branchName: message.targetBranch
|
|
5517
5646
|
};
|
|
5518
|
-
const created = await workspaceSyncSingleFlight.
|
|
5647
|
+
const created = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
5519
5648
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5520
5649
|
throw new Error(
|
|
5521
5650
|
`Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -5556,6 +5685,7 @@ async function startWorker(options) {
|
|
|
5556
5685
|
primaryBranchName: primaryProjectBranch(project),
|
|
5557
5686
|
sourceBranchName: message.sourceBranch,
|
|
5558
5687
|
branchName: message.targetBranch,
|
|
5688
|
+
workingTree: message.workingTree,
|
|
5559
5689
|
pendingRetry: pendingWasRecorded
|
|
5560
5690
|
});
|
|
5561
5691
|
gitBranchCreated = true;
|
|
@@ -5612,9 +5742,14 @@ async function startWorker(options) {
|
|
|
5612
5742
|
});
|
|
5613
5743
|
return;
|
|
5614
5744
|
}
|
|
5745
|
+
sendWorkerMessage(ws, {
|
|
5746
|
+
type: "operation_queued",
|
|
5747
|
+
requestId: message.requestId,
|
|
5748
|
+
queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
|
|
5749
|
+
});
|
|
5615
5750
|
try {
|
|
5616
5751
|
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
5617
|
-
const deletionNeedsSync = await workspaceSyncSingleFlight.
|
|
5752
|
+
const deletionNeedsSync = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
5618
5753
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5619
5754
|
throw new Error(
|
|
5620
5755
|
`Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -5645,6 +5780,9 @@ async function startWorker(options) {
|
|
|
5645
5780
|
projectId: message.projectId,
|
|
5646
5781
|
branchName: message.branchName
|
|
5647
5782
|
});
|
|
5783
|
+
if (pendingLocalBranch) {
|
|
5784
|
+
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pendingLocalBranch);
|
|
5785
|
+
}
|
|
5648
5786
|
pendingCreatedBranchPublicationNotBefore.delete(pendingCreatedBranchKey(message.projectId, message.branchName));
|
|
5649
5787
|
if (pendingMirrorDeletes.has(deletionKey)) return true;
|
|
5650
5788
|
stageProjectBranchDeletion(project, message.branchName, true);
|
|
@@ -5922,7 +6060,7 @@ async function startWorker(options) {
|
|
|
5922
6060
|
ws,
|
|
5923
6061
|
message,
|
|
5924
6062
|
resolvedTarget,
|
|
5925
|
-
|
|
6063
|
+
rootDir,
|
|
5926
6064
|
assertAdmission: assertMessageAdmission,
|
|
5927
6065
|
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
|
|
5928
6066
|
...targetMayMutateVisibleWorkspace(message.target) ? {
|
|
@@ -6020,8 +6158,7 @@ async function startWorker(options) {
|
|
|
6020
6158
|
resolvedTarget,
|
|
6021
6159
|
baseUrl,
|
|
6022
6160
|
token,
|
|
6023
|
-
|
|
6024
|
-
planRoot,
|
|
6161
|
+
rootDir,
|
|
6025
6162
|
setPreparationHeartbeatLease: (active) => {
|
|
6026
6163
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
6027
6164
|
type: "heartbeat_lease",
|
|
@@ -6090,8 +6227,7 @@ async function startWorker(options) {
|
|
|
6090
6227
|
resolvedTarget,
|
|
6091
6228
|
baseUrl,
|
|
6092
6229
|
token,
|
|
6093
|
-
|
|
6094
|
-
planRoot,
|
|
6230
|
+
rootDir,
|
|
6095
6231
|
setPreparationHeartbeatLease: (active) => {
|
|
6096
6232
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
6097
6233
|
type: "heartbeat_lease",
|
|
@@ -6173,8 +6309,7 @@ async function startWorker(options) {
|
|
|
6173
6309
|
resolvedTarget,
|
|
6174
6310
|
baseUrl,
|
|
6175
6311
|
token,
|
|
6176
|
-
|
|
6177
|
-
planRoot,
|
|
6312
|
+
rootDir,
|
|
6178
6313
|
assertAdmission: assertMessageAdmission
|
|
6179
6314
|
});
|
|
6180
6315
|
});
|