@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/cjs/main.cjs
CHANGED
|
@@ -72,6 +72,8 @@ var import_workspace_incident_state = require("./workspace-incident-state.cjs");
|
|
|
72
72
|
var import_workspace_automatic_sync_policy = require("./workspace-automatic-sync-policy.cjs");
|
|
73
73
|
var import_workspace_command_sync_policy = require("./workspace-command-sync-policy.cjs");
|
|
74
74
|
var import_workspace_mutation_gate = require("./workspace-mutation-gate.cjs");
|
|
75
|
+
var import_workspace_projection_ledger = require("./workspace-projection-ledger.cjs");
|
|
76
|
+
var import_workspace_sync_coalescer = require("./workspace-sync-coalescer.cjs");
|
|
75
77
|
var import_workspace_path_move = require("./workspace-path-move.cjs");
|
|
76
78
|
var import_workspace_project_config_policy = require("./workspace-project-config-policy.cjs");
|
|
77
79
|
var import_workspace_preserve_only_policy = require("./workspace-preserve-only-policy.cjs");
|
|
@@ -175,6 +177,12 @@ const workspaceSyncSingleFlight = {
|
|
|
175
177
|
acquireMutation() {
|
|
176
178
|
return workspaceMutationGate.acquireMutation();
|
|
177
179
|
},
|
|
180
|
+
runBranchMutation(operation) {
|
|
181
|
+
return workspaceMutationGate.runBranchMutation(operation);
|
|
182
|
+
},
|
|
183
|
+
branchMutationQueuePosition() {
|
|
184
|
+
return workspaceMutationGate.branchMutationQueuePosition();
|
|
185
|
+
},
|
|
178
186
|
afterCurrent() {
|
|
179
187
|
return workspaceSyncQueue;
|
|
180
188
|
}
|
|
@@ -305,6 +313,7 @@ const workerPtyTestHarness = {
|
|
|
305
313
|
removeEnvFiles: removePtyEnvFiles,
|
|
306
314
|
resolveEnvFileReferences: resolvePtyEnvFileReferences,
|
|
307
315
|
commandHasWorkspaceEffect: workerCommandHasWorkspaceEffect,
|
|
316
|
+
targetProcessEnv,
|
|
308
317
|
canonicalSyncTerminalHead,
|
|
309
318
|
ptyIsWorkspaceBusy: workerPtyIsWorkspaceBusy,
|
|
310
319
|
parseLinuxForegroundBusy: parseLinuxPtyForegroundBusy
|
|
@@ -312,21 +321,12 @@ const workerPtyTestHarness = {
|
|
|
312
321
|
function defaultConfigPath() {
|
|
313
322
|
return import_node_path.default.join(import_node_os.default.homedir(), ".config", "r5d", "r5dctl", "config.json");
|
|
314
323
|
}
|
|
315
|
-
function
|
|
316
|
-
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d"
|
|
317
|
-
}
|
|
318
|
-
function defaultSyncRoot() {
|
|
319
|
-
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "sync");
|
|
320
|
-
}
|
|
321
|
-
function defaultArtifactRoot() {
|
|
322
|
-
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "artifacts");
|
|
323
|
-
}
|
|
324
|
-
function defaultPlanRoot() {
|
|
325
|
-
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "plans");
|
|
324
|
+
function defaultRootDir() {
|
|
325
|
+
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d");
|
|
326
326
|
}
|
|
327
327
|
function printHelp() {
|
|
328
328
|
process.stdout.write(`Usage:
|
|
329
|
-
r5d-worker start --label <label> [--base-url <url>] [--token <token>] [--api-key <key>]
|
|
329
|
+
r5d-worker start --label <label> [--root <dir>] [--base-url <url>] [--token <token>] [--api-key <key>]
|
|
330
330
|
r5d-worker --version
|
|
331
331
|
|
|
332
332
|
Options:
|
|
@@ -335,10 +335,7 @@ Options:
|
|
|
335
335
|
--token <token> r5d worker token
|
|
336
336
|
--api-key <key> r5d API key
|
|
337
337
|
--config <path> Config path (default ~/.config/r5d/r5dctl/config.json)
|
|
338
|
-
--
|
|
339
|
-
--sync-root <dir> Override r5d internal sync git root (default ~/.r5d/sync)
|
|
340
|
-
--artifact-root <dir> Override session artifact root (default from R5D_ARTIFACTS_ROOT or ~/.r5d/artifacts)
|
|
341
|
-
--plan-root <dir> Override synced plan root (default from R5D_PLANS_ROOT or ~/.r5d/plans)
|
|
338
|
+
--root <dir> Override worker data root (default from R5D_ROOT or ~/.r5d)
|
|
342
339
|
-v, --version Show r5d-worker version
|
|
343
340
|
-h, --help Show this help
|
|
344
341
|
`);
|
|
@@ -453,24 +450,9 @@ function parseArgs(argv) {
|
|
|
453
450
|
options.label = label;
|
|
454
451
|
continue;
|
|
455
452
|
}
|
|
456
|
-
const
|
|
457
|
-
if (
|
|
458
|
-
options.
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
const syncRoot = readOption("--sync-root");
|
|
462
|
-
if (syncRoot !== void 0) {
|
|
463
|
-
options.syncRoot = syncRoot;
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
466
|
-
const artifactRoot = readOption("--artifact-root");
|
|
467
|
-
if (artifactRoot !== void 0) {
|
|
468
|
-
options.artifactRoot = artifactRoot;
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
const planRoot = readOption("--plan-root");
|
|
472
|
-
if (planRoot !== void 0) {
|
|
473
|
-
options.planRoot = planRoot;
|
|
453
|
+
const rootDir = readOption("--root");
|
|
454
|
+
if (rootDir !== void 0) {
|
|
455
|
+
options.rootDir = rootDir;
|
|
474
456
|
continue;
|
|
475
457
|
}
|
|
476
458
|
rest.push(arg);
|
|
@@ -515,11 +497,9 @@ async function readResponseText(response) {
|
|
|
515
497
|
return "";
|
|
516
498
|
}
|
|
517
499
|
}
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
const R5D_PLANS_DIR_ENV = "R5D_PLANS_DIR";
|
|
522
|
-
const BUILT_IN_TOOL_PATH_REFS = [R5D_ARTIFACTS_DIR_REF, R5D_PLANS_DIR_REF, R5D_ACTIVE_PLAN_FILE_REF];
|
|
500
|
+
const R5D_ROOT_REF = "$R5D_ROOT";
|
|
501
|
+
const R5D_ROOT_ENV = "R5D_ROOT";
|
|
502
|
+
const BUILT_IN_TOOL_PATH_REFS = [R5D_ROOT_REF];
|
|
523
503
|
function parseBuiltInToolPath(inputPath) {
|
|
524
504
|
const normalized = inputPath.replace(/\\/g, "/");
|
|
525
505
|
const match = /^\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))(?:\/(.*))?$/.exec(normalized);
|
|
@@ -535,6 +515,26 @@ function parseBuiltInToolPath(inputPath) {
|
|
|
535
515
|
relativePath: match[3] ?? ""
|
|
536
516
|
};
|
|
537
517
|
}
|
|
518
|
+
function expandRootPathSegments(relativePath, builtInPaths) {
|
|
519
|
+
const values = {
|
|
520
|
+
R5D_SESSION_ID: builtInPaths.sessionId,
|
|
521
|
+
R5D_PROJECT_ID: builtInPaths.projectId,
|
|
522
|
+
R5D_BRANCH_NAME: builtInPaths.branchName,
|
|
523
|
+
R5D_ACTIVE_PLAN_ID: builtInPaths.activePlanId
|
|
524
|
+
};
|
|
525
|
+
let expandedPath = relativePath;
|
|
526
|
+
for (const [variableName, value] of Object.entries(values)) {
|
|
527
|
+
const variablePattern = new RegExp(`\\$(?:\\{${variableName}\\}|${variableName})(?=\\/|\\.|$)`, "g");
|
|
528
|
+
if (!variablePattern.test(expandedPath)) continue;
|
|
529
|
+
if (!value) throw new Error(`$${variableName} is unavailable for this worker path`);
|
|
530
|
+
expandedPath = expandedPath.replace(variablePattern, value);
|
|
531
|
+
}
|
|
532
|
+
const unsupportedVariable = /\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/.exec(expandedPath);
|
|
533
|
+
if (unsupportedVariable) {
|
|
534
|
+
throw new Error(`Unsupported ${R5D_ROOT_REF} path variable "$${unsupportedVariable[1] ?? unsupportedVariable[2]}"`);
|
|
535
|
+
}
|
|
536
|
+
return expandedPath.split("/").filter(Boolean);
|
|
537
|
+
}
|
|
538
538
|
function validateArtifactSessionId(sessionId) {
|
|
539
539
|
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)) {
|
|
540
540
|
throw new Error(`Invalid artifact session id: ${sessionId}`);
|
|
@@ -550,14 +550,6 @@ function sessionArtifactDir(artifactRoot, sessionId) {
|
|
|
550
550
|
validateArtifactSessionId(sessionId);
|
|
551
551
|
return import_node_path.default.join(artifactRoot, sessionId);
|
|
552
552
|
}
|
|
553
|
-
function artifactEnv(artifactRoot, sessionId) {
|
|
554
|
-
if (!sessionId) {
|
|
555
|
-
return {};
|
|
556
|
-
}
|
|
557
|
-
return {
|
|
558
|
-
R5D_ARTIFACTS_DIR: sessionArtifactDir(artifactRoot, sessionId)
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
553
|
function projectPlanDir(planRoot, projectId, branchName) {
|
|
562
554
|
validateProjectId(projectId);
|
|
563
555
|
validateBranchName(branchName);
|
|
@@ -570,21 +562,18 @@ function targetPlanDir(planRoot, target) {
|
|
|
570
562
|
}
|
|
571
563
|
return import_node_path.default.join(planRoot, "workspace");
|
|
572
564
|
}
|
|
573
|
-
function planEnv(
|
|
574
|
-
const
|
|
575
|
-
const env = {
|
|
576
|
-
[R5D_PLANS_DIR_ENV]: plansDir
|
|
577
|
-
};
|
|
565
|
+
function planEnv(activePlanId) {
|
|
566
|
+
const env = {};
|
|
578
567
|
if (activePlanId) {
|
|
579
568
|
validatePlanId(activePlanId);
|
|
580
569
|
env.R5D_ACTIVE_PLAN_ID = activePlanId;
|
|
581
|
-
env.R5D_ACTIVE_PLAN_FILE = import_node_path.default.join(plansDir, `${activePlanId}.plan.md`);
|
|
582
570
|
}
|
|
583
571
|
return env;
|
|
584
572
|
}
|
|
585
573
|
function isArtifactEnvPath(filePath) {
|
|
586
574
|
try {
|
|
587
|
-
|
|
575
|
+
const parsed = parseBuiltInToolPath(filePath);
|
|
576
|
+
return parsed?.ref === R5D_ROOT_REF && parsed.relativePath.split("/").filter(Boolean)[0] === "artifacts";
|
|
588
577
|
} catch {
|
|
589
578
|
return false;
|
|
590
579
|
}
|
|
@@ -726,48 +715,46 @@ async function prepareBuiltInToolPaths(input) {
|
|
|
726
715
|
if (!parsed) {
|
|
727
716
|
return void 0;
|
|
728
717
|
}
|
|
729
|
-
|
|
718
|
+
const builtInPaths = {
|
|
719
|
+
rootDir: input.rootDir,
|
|
720
|
+
sessionId: input.sessionId,
|
|
721
|
+
activePlanId: input.activePlanId,
|
|
722
|
+
...input.target.type === "project" ? { projectId: input.target.projectId, branchName: input.target.branchName } : {}
|
|
723
|
+
};
|
|
724
|
+
const rootPathSegments = expandRootPathSegments(parsed.relativePath, builtInPaths);
|
|
725
|
+
if (rootPathSegments[0] === "artifacts") {
|
|
730
726
|
if (input.access === "write") {
|
|
731
727
|
throw new Error(
|
|
732
|
-
`${
|
|
728
|
+
`${R5D_ROOT_REF}/artifacts attachments are read-only. Copy them into a project path before editing or creating files.`
|
|
733
729
|
);
|
|
734
730
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
})
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
const plansDir = targetPlanDir(input.planRoot, input.target);
|
|
748
|
-
import_node_fs.default.mkdirSync(plansDir, { recursive: true });
|
|
749
|
-
if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
750
|
-
return { plansDir };
|
|
731
|
+
const referencedSessionId = rootPathSegments[1];
|
|
732
|
+
if (!referencedSessionId) return builtInPaths;
|
|
733
|
+
validateArtifactSessionId(referencedSessionId);
|
|
734
|
+
await syncSessionArtifacts({
|
|
735
|
+
baseUrl: input.baseUrl,
|
|
736
|
+
token: input.token,
|
|
737
|
+
sessionId: referencedSessionId,
|
|
738
|
+
artifactRoot: import_node_path.default.join(input.rootDir, "artifacts")
|
|
739
|
+
});
|
|
751
740
|
}
|
|
752
|
-
if (
|
|
753
|
-
|
|
741
|
+
if (rootPathSegments[0] === "plans") {
|
|
742
|
+
import_node_fs.default.mkdirSync(targetPlanDir(import_node_path.default.join(input.rootDir, "plans"), input.target), { recursive: true });
|
|
754
743
|
}
|
|
755
|
-
|
|
756
|
-
return {
|
|
757
|
-
plansDir,
|
|
758
|
-
activePlanFile: import_node_path.default.join(plansDir, `${input.activePlanId}.plan.md`)
|
|
759
|
-
};
|
|
744
|
+
return builtInPaths;
|
|
760
745
|
}
|
|
761
746
|
async function prepareArtifactEnvForShell(input) {
|
|
747
|
+
const env = { [R5D_ROOT_ENV]: input.rootDir };
|
|
762
748
|
if (!input.sessionId) {
|
|
763
|
-
return
|
|
749
|
+
return env;
|
|
764
750
|
}
|
|
751
|
+
const artifactRoot = import_node_path.default.join(input.rootDir, "artifacts");
|
|
765
752
|
try {
|
|
766
753
|
await syncSessionArtifacts({
|
|
767
754
|
baseUrl: input.baseUrl,
|
|
768
755
|
token: input.token,
|
|
769
756
|
sessionId: input.sessionId,
|
|
770
|
-
artifactRoot
|
|
757
|
+
artifactRoot
|
|
771
758
|
});
|
|
772
759
|
} catch (error) {
|
|
773
760
|
process.stderr.write(
|
|
@@ -775,16 +762,14 @@ async function prepareArtifactEnvForShell(input) {
|
|
|
775
762
|
`
|
|
776
763
|
);
|
|
777
764
|
}
|
|
778
|
-
const artifactsDir = sessionArtifactDir(
|
|
765
|
+
const artifactsDir = sessionArtifactDir(artifactRoot, input.sessionId);
|
|
779
766
|
import_node_fs.default.mkdirSync(artifactsDir, { recursive: true });
|
|
780
|
-
return
|
|
781
|
-
R5D_ARTIFACTS_DIR: artifactsDir
|
|
782
|
-
};
|
|
767
|
+
return env;
|
|
783
768
|
}
|
|
784
769
|
function preparePlanEnvForShell(input) {
|
|
785
|
-
const plansDir = targetPlanDir(input.
|
|
770
|
+
const plansDir = targetPlanDir(import_node_path.default.join(input.rootDir, "plans"), input.target);
|
|
786
771
|
import_node_fs.default.mkdirSync(plansDir, { recursive: true });
|
|
787
|
-
return planEnv(input.
|
|
772
|
+
return planEnv(input.activePlanId);
|
|
788
773
|
}
|
|
789
774
|
async function verifyR5dctlAuth(baseUrl, token, workerLabel) {
|
|
790
775
|
const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
|
|
@@ -952,30 +937,17 @@ function resolveVirtualWorkerFilePath(inputPath, parsed, builtInPaths) {
|
|
|
952
937
|
if (parsed.relativePath.split("/").includes("..")) {
|
|
953
938
|
throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
|
|
954
939
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
rootPath = builtInPaths?.plansDir;
|
|
961
|
-
} else {
|
|
962
|
-
if (parsed.relativePath) {
|
|
963
|
-
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} must reference the active plan file directly`);
|
|
964
|
-
}
|
|
965
|
-
rootPath = builtInPaths?.plansDir;
|
|
966
|
-
absolutePath = builtInPaths?.activePlanFile;
|
|
967
|
-
}
|
|
968
|
-
if (!rootPath || parsed.ref === R5D_ACTIVE_PLAN_FILE_REF && !absolutePath) {
|
|
969
|
-
const requirement = parsed.ref === R5D_ARTIFACTS_DIR_REF ? "an active chat session" : "an active plan";
|
|
970
|
-
throw new Error(`${parsed.ref} paths require ${requirement}`);
|
|
971
|
-
}
|
|
972
|
-
const normalizedPath = parsed.relativePath ? import_node_path.default.posix.normalize(parsed.relativePath).replace(/^\/+|\/+$/g, "") : "";
|
|
940
|
+
const rootPath = builtInPaths?.rootDir;
|
|
941
|
+
if (!rootPath) throw new Error(`${R5D_ROOT_REF} paths require an available worker root`);
|
|
942
|
+
const expandedPath = expandRootPathSegments(parsed.relativePath, builtInPaths);
|
|
943
|
+
if (expandedPath.includes("..")) throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
|
|
944
|
+
const normalizedPath = expandedPath.length > 0 ? import_node_path.default.posix.normalize(expandedPath.join("/")).replace(/^\/+|\/+$/g, "") : "";
|
|
973
945
|
const normalizedRelativePath = normalizedPath === "." ? "" : normalizedPath;
|
|
974
|
-
absolutePath
|
|
946
|
+
const absolutePath = normalizedRelativePath ? import_node_path.default.resolve(rootPath, ...normalizedRelativePath.split("/")) : import_node_path.default.resolve(rootPath);
|
|
975
947
|
assertPathInsideRealRoot(rootPath, absolutePath, `${parsed.ref} path`);
|
|
976
948
|
return {
|
|
977
949
|
absolutePath,
|
|
978
|
-
displayPath:
|
|
950
|
+
displayPath: parsed.relativePath ? `${parsed.ref}/${parsed.relativePath}` : parsed.ref,
|
|
979
951
|
repoRelativePath: null,
|
|
980
952
|
scope: "virtual",
|
|
981
953
|
virtualRootPath: import_node_path.default.resolve(rootPath)
|
|
@@ -2219,8 +2191,7 @@ async function executeReadFileOperation(input) {
|
|
|
2219
2191
|
token: input.token,
|
|
2220
2192
|
sessionId: input.message.sessionId,
|
|
2221
2193
|
activePlanId: input.message.activePlanId,
|
|
2222
|
-
|
|
2223
|
-
planRoot: input.planRoot,
|
|
2194
|
+
rootDir: input.rootDir,
|
|
2224
2195
|
access: "read"
|
|
2225
2196
|
});
|
|
2226
2197
|
return readWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
|
|
@@ -2233,8 +2204,7 @@ async function executeWriteFileOperation(input) {
|
|
|
2233
2204
|
token: input.token,
|
|
2234
2205
|
sessionId: input.message.sessionId,
|
|
2235
2206
|
activePlanId: input.message.activePlanId,
|
|
2236
|
-
|
|
2237
|
-
planRoot: input.planRoot,
|
|
2207
|
+
rootDir: input.rootDir,
|
|
2238
2208
|
access: "write"
|
|
2239
2209
|
});
|
|
2240
2210
|
input.assertAdmission();
|
|
@@ -2252,8 +2222,7 @@ async function executeEditFileOperation(input) {
|
|
|
2252
2222
|
token: input.token,
|
|
2253
2223
|
sessionId: input.message.sessionId,
|
|
2254
2224
|
activePlanId: input.message.activePlanId,
|
|
2255
|
-
|
|
2256
|
-
planRoot: input.planRoot,
|
|
2225
|
+
rootDir: input.rootDir,
|
|
2257
2226
|
access: "write"
|
|
2258
2227
|
});
|
|
2259
2228
|
input.assertAdmission();
|
|
@@ -2380,8 +2349,7 @@ async function executeGrepOperation(input) {
|
|
|
2380
2349
|
token: input.token,
|
|
2381
2350
|
sessionId: input.message.sessionId,
|
|
2382
2351
|
activePlanId: input.message.activePlanId,
|
|
2383
|
-
|
|
2384
|
-
planRoot: input.planRoot,
|
|
2352
|
+
rootDir: input.rootDir,
|
|
2385
2353
|
access: "read"
|
|
2386
2354
|
});
|
|
2387
2355
|
return grepWorkerFiles(
|
|
@@ -2439,8 +2407,7 @@ async function executeFindOperation(input) {
|
|
|
2439
2407
|
token: input.token,
|
|
2440
2408
|
sessionId: input.message.sessionId,
|
|
2441
2409
|
activePlanId: input.message.activePlanId,
|
|
2442
|
-
|
|
2443
|
-
planRoot: input.planRoot,
|
|
2410
|
+
rootDir: input.rootDir,
|
|
2444
2411
|
access: "read"
|
|
2445
2412
|
});
|
|
2446
2413
|
return findWorkerFiles(
|
|
@@ -2481,8 +2448,7 @@ async function executeLsOperation(input) {
|
|
|
2481
2448
|
token: input.token,
|
|
2482
2449
|
sessionId: input.message.sessionId,
|
|
2483
2450
|
activePlanId: input.message.activePlanId,
|
|
2484
|
-
|
|
2485
|
-
planRoot: input.planRoot,
|
|
2451
|
+
rootDir: input.rootDir,
|
|
2486
2452
|
access: "read"
|
|
2487
2453
|
});
|
|
2488
2454
|
return listWorkerDirectory(input.resolvedTarget.rootPath, input.message.path, input.message.limit, builtInPaths);
|
|
@@ -2588,8 +2554,7 @@ async function executeViewFileBytesOperation(input) {
|
|
|
2588
2554
|
token: input.token,
|
|
2589
2555
|
sessionId: input.message.sessionId,
|
|
2590
2556
|
activePlanId: input.message.activePlanId,
|
|
2591
|
-
|
|
2592
|
-
planRoot: input.planRoot,
|
|
2557
|
+
rootDir: input.rootDir,
|
|
2593
2558
|
access: "read"
|
|
2594
2559
|
});
|
|
2595
2560
|
return readWorkerImageFile(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
@@ -2629,27 +2594,30 @@ function targetIdentityEnv(target) {
|
|
|
2629
2594
|
R5D_BRANCH_NAME: ""
|
|
2630
2595
|
};
|
|
2631
2596
|
}
|
|
2597
|
+
function targetProcessEnv(rootDir, target) {
|
|
2598
|
+
return { [R5D_ROOT_ENV]: rootDir, ...targetIdentityEnv(target) };
|
|
2599
|
+
}
|
|
2632
2600
|
async function prepareShellEnvForTarget(input) {
|
|
2633
2601
|
const artifactProcessEnv = await prepareArtifactEnvForShell({
|
|
2634
2602
|
baseUrl: input.baseUrl,
|
|
2635
2603
|
token: input.token,
|
|
2636
2604
|
sessionId: input.sessionId,
|
|
2637
|
-
|
|
2605
|
+
rootDir: input.rootDir
|
|
2638
2606
|
});
|
|
2639
2607
|
if (input.target.type === "workspace" && input.target.rootProfile === "canonical_sync") {
|
|
2640
2608
|
return {
|
|
2641
2609
|
...artifactProcessEnv,
|
|
2642
|
-
...
|
|
2610
|
+
...targetProcessEnv(input.rootDir, input.target)
|
|
2643
2611
|
};
|
|
2644
2612
|
}
|
|
2645
2613
|
return {
|
|
2646
2614
|
...artifactProcessEnv,
|
|
2647
2615
|
...preparePlanEnvForShell({
|
|
2648
2616
|
target: input.target,
|
|
2649
|
-
|
|
2617
|
+
rootDir: input.rootDir,
|
|
2650
2618
|
activePlanId: input.activePlanId
|
|
2651
2619
|
}),
|
|
2652
|
-
...
|
|
2620
|
+
...targetProcessEnv(input.rootDir, input.target)
|
|
2653
2621
|
};
|
|
2654
2622
|
}
|
|
2655
2623
|
async function executeCommand(input) {
|
|
@@ -2664,13 +2632,12 @@ async function executeCommand(input) {
|
|
|
2664
2632
|
};
|
|
2665
2633
|
input.setPreparationHeartbeatLease(true);
|
|
2666
2634
|
try {
|
|
2667
|
-
const
|
|
2635
|
+
const targetProcessEnv2 = await prepareShellEnvForTarget({
|
|
2668
2636
|
target: input.resolvedTarget.target,
|
|
2669
2637
|
baseUrl: input.baseUrl,
|
|
2670
2638
|
token: input.token,
|
|
2671
2639
|
sessionId: input.message.sessionId,
|
|
2672
|
-
|
|
2673
|
-
planRoot: input.planRoot,
|
|
2640
|
+
rootDir: input.rootDir,
|
|
2674
2641
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2675
2642
|
});
|
|
2676
2643
|
const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
|
|
@@ -2680,7 +2647,7 @@ async function executeCommand(input) {
|
|
|
2680
2647
|
stdout: "pipe",
|
|
2681
2648
|
stderr: "pipe",
|
|
2682
2649
|
detached: true,
|
|
2683
|
-
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {},
|
|
2650
|
+
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv2])
|
|
2684
2651
|
});
|
|
2685
2652
|
spawnedProcess = subprocess;
|
|
2686
2653
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
@@ -2769,13 +2736,12 @@ async function executeStreamingCommand(input) {
|
|
|
2769
2736
|
});
|
|
2770
2737
|
return;
|
|
2771
2738
|
}
|
|
2772
|
-
const
|
|
2739
|
+
const targetProcessEnv2 = await prepareShellEnvForTarget({
|
|
2773
2740
|
target: input.resolvedTarget.target,
|
|
2774
2741
|
baseUrl: input.baseUrl,
|
|
2775
2742
|
token: input.token,
|
|
2776
2743
|
sessionId: input.message.sessionId,
|
|
2777
|
-
|
|
2778
|
-
planRoot: input.planRoot,
|
|
2744
|
+
rootDir: input.rootDir,
|
|
2779
2745
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
2780
2746
|
});
|
|
2781
2747
|
const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
|
|
@@ -2789,7 +2755,7 @@ async function executeStreamingCommand(input) {
|
|
|
2789
2755
|
stdout: "pipe",
|
|
2790
2756
|
stderr: "pipe",
|
|
2791
2757
|
detached: true,
|
|
2792
|
-
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {},
|
|
2758
|
+
env: workerChildProcessEnvironment([githubProcessEnv(), input.message.env ?? {}, targetProcessEnv2])
|
|
2793
2759
|
});
|
|
2794
2760
|
spawnedProcess = subprocess;
|
|
2795
2761
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
@@ -3253,10 +3219,10 @@ async function openPty(input) {
|
|
|
3253
3219
|
}
|
|
3254
3220
|
const planProcessEnv = !(input.resolvedTarget.target.type === "workspace" && input.resolvedTarget.target.rootProfile === "canonical_sync") ? preparePlanEnvForShell({
|
|
3255
3221
|
target: input.resolvedTarget.target,
|
|
3256
|
-
|
|
3222
|
+
rootDir: input.rootDir,
|
|
3257
3223
|
activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
|
|
3258
3224
|
}) : {};
|
|
3259
|
-
const
|
|
3225
|
+
const targetEnv = targetProcessEnv(input.rootDir, input.resolvedTarget.target);
|
|
3260
3226
|
const shell = resolveHostShell(input.message.command);
|
|
3261
3227
|
const outputCoalescer = (0, import_pty_output_coalescer.createPtyOutputCoalescer)({
|
|
3262
3228
|
send: (data) => {
|
|
@@ -3288,7 +3254,7 @@ async function openPty(input) {
|
|
|
3288
3254
|
githubProcessEnv(),
|
|
3289
3255
|
resolvePtyEnvFileReferences(input.message.env ?? {}, stagedEnvFiles),
|
|
3290
3256
|
planProcessEnv,
|
|
3291
|
-
|
|
3257
|
+
targetEnv,
|
|
3292
3258
|
shell.env ?? {}
|
|
3293
3259
|
])
|
|
3294
3260
|
},
|
|
@@ -3461,12 +3427,13 @@ async function startWorker(options) {
|
|
|
3461
3427
|
initializeCredentialStoreDirectoryOnce();
|
|
3462
3428
|
const label = requireValue(options.label, "Missing required --label <label>");
|
|
3463
3429
|
validateLabel(label);
|
|
3464
|
-
const
|
|
3465
|
-
const
|
|
3430
|
+
const rootDir = import_node_path.default.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
|
|
3431
|
+
const projectsRoot = import_node_path.default.join(rootDir, "projects");
|
|
3432
|
+
const syncRoot = import_node_path.default.join(rootDir, "sync");
|
|
3466
3433
|
const workspaceShadowRoot = import_node_path.default.join(syncRoot, "workspace");
|
|
3467
3434
|
const projectWorktreeSnapshotsRoot = import_node_path.default.join(syncRoot, "project-worktree-snapshots");
|
|
3468
|
-
const artifactRoot = import_node_path.default.
|
|
3469
|
-
const planRoot = import_node_path.default.
|
|
3435
|
+
const artifactRoot = import_node_path.default.join(rootDir, "artifacts");
|
|
3436
|
+
const planRoot = import_node_path.default.join(rootDir, "plans");
|
|
3470
3437
|
(0, import_managed_paths.assertDisjointManagedRoots)([
|
|
3471
3438
|
{ label: "projects root", rootPath: projectsRoot },
|
|
3472
3439
|
{ label: "sync root", rootPath: syncRoot },
|
|
@@ -3475,13 +3442,7 @@ async function startWorker(options) {
|
|
|
3475
3442
|
]);
|
|
3476
3443
|
process.stdout.write(`[r5d-worker] label: ${label}
|
|
3477
3444
|
`);
|
|
3478
|
-
process.stdout.write(`[r5d-worker]
|
|
3479
|
-
`);
|
|
3480
|
-
process.stdout.write(`[r5d-worker] sync root: ${syncRoot}
|
|
3481
|
-
`);
|
|
3482
|
-
process.stdout.write(`[r5d-worker] artifact root: ${artifactRoot}
|
|
3483
|
-
`);
|
|
3484
|
-
process.stdout.write(`[r5d-worker] plan root: ${planRoot}
|
|
3445
|
+
process.stdout.write(`[r5d-worker] root: ${rootDir}
|
|
3485
3446
|
`);
|
|
3486
3447
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
3487
3448
|
`);
|
|
@@ -3506,9 +3467,29 @@ async function startWorker(options) {
|
|
|
3506
3467
|
const reconciledProjectConfigFingerprints = /* @__PURE__ */ new Map();
|
|
3507
3468
|
const lastObservedProjectHeads = /* @__PURE__ */ new Map();
|
|
3508
3469
|
const advertisedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
|
|
3470
|
+
const advertisedProjectMirrorMountTokens = /* @__PURE__ */ new Map();
|
|
3471
|
+
let advertisedWorkspaceHead;
|
|
3509
3472
|
const fetchedProjectMirrorRefsTokens = /* @__PURE__ */ new Map();
|
|
3510
|
-
const
|
|
3473
|
+
const fetchedProjectMirrorMountTokens = /* @__PURE__ */ new Map();
|
|
3511
3474
|
const projectMirrorFetchSkippingEnabled = process.env.R5D_PROJECT_MIRROR_FETCH_SKIP !== "0";
|
|
3475
|
+
const forgetLocalProjectMirrorRefsTokens = (projectId, branchName) => {
|
|
3476
|
+
fetchedProjectMirrorRefsTokens.delete(projectId);
|
|
3477
|
+
if (branchName === void 0) fetchedProjectMirrorMountTokens.delete(projectId);
|
|
3478
|
+
else fetchedProjectMirrorMountTokens.get(projectId)?.delete(branchName);
|
|
3479
|
+
};
|
|
3480
|
+
const recordLocalProjectMirrorRefsTokens = async (project) => {
|
|
3481
|
+
try {
|
|
3482
|
+
const tokens = await (0, import_project_worktrees.computeLocalProjectMirrorRefsTokens)({
|
|
3483
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
3484
|
+
primaryBranchName: primaryProjectBranch(project),
|
|
3485
|
+
mounts: project.branches.map(({ branchId, branchName }) => ({ branchId, branchName }))
|
|
3486
|
+
});
|
|
3487
|
+
fetchedProjectMirrorRefsTokens.set(project.projectId, tokens.project);
|
|
3488
|
+
fetchedProjectMirrorMountTokens.set(project.projectId, tokens.mounts);
|
|
3489
|
+
} catch {
|
|
3490
|
+
forgetLocalProjectMirrorRefsTokens(project.projectId);
|
|
3491
|
+
}
|
|
3492
|
+
};
|
|
3512
3493
|
const pendingMirrorDeletes = /* @__PURE__ */ new Map();
|
|
3513
3494
|
let workspaceRemoteUrl = null;
|
|
3514
3495
|
let workspaceCredentialHelper = null;
|
|
@@ -3523,10 +3504,12 @@ async function startWorker(options) {
|
|
|
3523
3504
|
let workspacePeriodicTimer;
|
|
3524
3505
|
let pendingAutomaticTrigger;
|
|
3525
3506
|
const pendingCreatedBranchPublicationNotBefore = /* @__PURE__ */ new Map();
|
|
3507
|
+
const workspaceProjectionLedger = new import_workspace_projection_ledger.WorkspaceProjectionLedger();
|
|
3526
3508
|
let automaticSyncInFlight = false;
|
|
3527
3509
|
let terminalReplayTimer;
|
|
3528
3510
|
let workspaceSyncRequestsInFlight = 0;
|
|
3529
3511
|
let lastWorkspaceSyncMirrorObservationMs = 0;
|
|
3512
|
+
let lastInventoriedMissingCheckouts;
|
|
3530
3513
|
let sessionArtifactSyncRequestsInFlight = 0;
|
|
3531
3514
|
let cliUpdateInProgress = false;
|
|
3532
3515
|
let reloadAfterClose = false;
|
|
@@ -3584,8 +3567,15 @@ async function startWorker(options) {
|
|
|
3584
3567
|
const primaryBranchName = primaryProjectBranch(project);
|
|
3585
3568
|
const connection = projectConnection(project);
|
|
3586
3569
|
const sourcePath = configuredProjectBranchPath(projectsRoot, project, branchName);
|
|
3587
|
-
|
|
3588
|
-
|
|
3570
|
+
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryBranchName);
|
|
3571
|
+
if (requireLocalBranch || import_node_fs.default.existsSync(sourcePath) && hasProjectWorktree(primaryPath)) {
|
|
3572
|
+
const deleted = (0, import_project_worktrees.deleteLinkedProjectBranch)({ projectRoot, primaryBranchName, branchName });
|
|
3573
|
+
if (deleted.movedAsidePath) {
|
|
3574
|
+
process.stderr.write(
|
|
3575
|
+
`[r5d-worker] set aside ${sourcePath}, which was not a linked worktree of ${project.projectPath}, at ${deleted.movedAsidePath}
|
|
3576
|
+
`
|
|
3577
|
+
);
|
|
3578
|
+
}
|
|
3589
3579
|
}
|
|
3590
3580
|
const planSourcePath = import_node_path.default.join(planRoot, project.projectId, ...branchName.split("/"));
|
|
3591
3581
|
import_node_fs.default.rmSync(planSourcePath, { recursive: true, force: true });
|
|
@@ -3658,11 +3648,17 @@ async function startWorker(options) {
|
|
|
3658
3648
|
if (branchName === primaryBranchName) {
|
|
3659
3649
|
throw new Error(`Cannot hide preserve-only project branch ${project.projectId}/${branchName} because it is the active anchor`);
|
|
3660
3650
|
}
|
|
3661
|
-
(0, import_project_worktrees.deleteLinkedProjectBranch)({
|
|
3651
|
+
const deleted = (0, import_project_worktrees.deleteLinkedProjectBranch)({
|
|
3662
3652
|
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
3663
3653
|
primaryBranchName,
|
|
3664
3654
|
branchName
|
|
3665
3655
|
});
|
|
3656
|
+
if (deleted.movedAsidePath) {
|
|
3657
|
+
process.stderr.write(
|
|
3658
|
+
`[r5d-worker] set aside ${branchPath}, which was not a linked worktree of ${project.projectPath}, at ${deleted.movedAsidePath}
|
|
3659
|
+
`
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3666
3662
|
import_node_fs.default.rmSync(import_node_path.default.join(planRoot, project.projectId, ...branchName.split("/")), { recursive: true, force: true });
|
|
3667
3663
|
readyProjectIds.delete(project.projectId);
|
|
3668
3664
|
reconciledProjectConfigFingerprints.delete(project.projectId);
|
|
@@ -3831,6 +3827,40 @@ async function startWorker(options) {
|
|
|
3831
3827
|
});
|
|
3832
3828
|
};
|
|
3833
3829
|
const projectBranchMountBusy = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectBranchMountActivityBusy(projectId, branchName, branchPath);
|
|
3830
|
+
const inventoryConfiguredProjectCheckouts = () => {
|
|
3831
|
+
const inventory = { projects: [], missingCheckouts: [] };
|
|
3832
|
+
for (const project of projectConfigById.values()) {
|
|
3833
|
+
if (project.executionDisabled || !readyProjectIds.has(project.projectId) || project.branches.length === 0) {
|
|
3834
|
+
inventory.projects.push(project);
|
|
3835
|
+
continue;
|
|
3836
|
+
}
|
|
3837
|
+
const checkouts = (0, import_project_worktrees.inventoryProjectCheckouts)({
|
|
3838
|
+
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
3839
|
+
primaryBranchName: primaryProjectBranch(project),
|
|
3840
|
+
branchNames: project.branches.map(({ branchName }) => branchName)
|
|
3841
|
+
});
|
|
3842
|
+
if (checkouts.missingCheckouts.length === 0) {
|
|
3843
|
+
inventory.projects.push(project);
|
|
3844
|
+
continue;
|
|
3845
|
+
}
|
|
3846
|
+
for (const missing of checkouts.missingCheckouts) inventory.missingCheckouts.push({ projectId: project.projectId, ...missing });
|
|
3847
|
+
const linked = new Set(checkouts.linkedBranchNames);
|
|
3848
|
+
inventory.projects.push({ ...project, branches: project.branches.filter(({ branchName }) => linked.has(branchName)) });
|
|
3849
|
+
}
|
|
3850
|
+
inventory.missingCheckouts.sort(
|
|
3851
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
3852
|
+
);
|
|
3853
|
+
return inventory;
|
|
3854
|
+
};
|
|
3855
|
+
const reportedMissingCheckouts = (inventory) => inventory.missingCheckouts.map(({ projectId, branchName, reason }) => ({ projectId, branchName, reason }));
|
|
3856
|
+
const logMissingCheckouts = (inventory) => {
|
|
3857
|
+
if (inventory.missingCheckouts.length === 0) return;
|
|
3858
|
+
const described = inventory.missingCheckouts.map(({ checkoutPath, reason }) => `${checkoutPath} (${reason === "missing" ? "directory missing" : "not a linked worktree of the project"})`).join(", ");
|
|
3859
|
+
process.stderr.write(
|
|
3860
|
+
`[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
|
|
3861
|
+
`
|
|
3862
|
+
);
|
|
3863
|
+
};
|
|
3834
3864
|
const buildWorkspaceMounts = (projects = projectConfigById.values()) => {
|
|
3835
3865
|
const mounts = [];
|
|
3836
3866
|
const creatorLocalIncarnations = new Map(
|
|
@@ -3892,7 +3922,7 @@ async function startWorker(options) {
|
|
|
3892
3922
|
workspaceRelativePath: "workspace-plans",
|
|
3893
3923
|
sourceMode: "all",
|
|
3894
3924
|
hydrateDeletionMode: "all",
|
|
3895
|
-
// Workspace-level agents
|
|
3925
|
+
// Workspace-level agents reach this directory through $R5D_ROOT/plans.
|
|
3896
3926
|
// Keep both projection and hydration away from it while any visible-
|
|
3897
3927
|
// projects process, PTY, or built-in write/edit is active or reserved.
|
|
3898
3928
|
busy: () => {
|
|
@@ -3930,7 +3960,10 @@ async function startWorker(options) {
|
|
|
3930
3960
|
deleteWhenSourceMissing: true
|
|
3931
3961
|
});
|
|
3932
3962
|
}
|
|
3933
|
-
return mounts
|
|
3963
|
+
return mounts.map((mount) => {
|
|
3964
|
+
const lastProjectedMutationToken = workspaceProjectionLedger.lastProjectedToken(mount);
|
|
3965
|
+
return lastProjectedMutationToken === void 0 ? mount : { ...mount, lastProjectedMutationToken };
|
|
3966
|
+
});
|
|
3934
3967
|
};
|
|
3935
3968
|
const workspaceLocalHead = async () => {
|
|
3936
3969
|
if (!import_node_fs.default.existsSync(import_node_path.default.join(workspaceShadowRoot, ".git"))) return null;
|
|
@@ -4106,9 +4139,9 @@ async function startWorker(options) {
|
|
|
4106
4139
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
4107
4140
|
);
|
|
4108
4141
|
};
|
|
4109
|
-
const captureProjectHeadsForWorkspacePublication = async () => {
|
|
4142
|
+
const captureProjectHeadsForWorkspacePublication = async (projects = projectConfigById.values()) => {
|
|
4110
4143
|
const heads = /* @__PURE__ */ new Map();
|
|
4111
|
-
for (const project of
|
|
4144
|
+
for (const project of projects) {
|
|
4112
4145
|
if (!readyProjectIds.has(project.projectId)) continue;
|
|
4113
4146
|
for (const branch of project.branches) {
|
|
4114
4147
|
const branchPath = configuredProjectBranchPath(projectsRoot, project, branch.branchName);
|
|
@@ -4118,9 +4151,9 @@ async function startWorker(options) {
|
|
|
4118
4151
|
}
|
|
4119
4152
|
return heads;
|
|
4120
4153
|
};
|
|
4121
|
-
const pushChangedProjectHeads = async (activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, suppressedMountIds = /* @__PURE__ */ new Set()) => {
|
|
4154
|
+
const pushChangedProjectHeads = async (activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, suppressedMountIds = /* @__PURE__ */ new Set(), projects = projectConfigById.values()) => {
|
|
4122
4155
|
const active = new Set(activeMountIds);
|
|
4123
|
-
for (const project of
|
|
4156
|
+
for (const project of projects) {
|
|
4124
4157
|
if (!readyProjectIds.has(project.projectId) || !(0, import_repository_transition_policy.repositoryMirrorWritesAllowed)(project)) continue;
|
|
4125
4158
|
const changed = /* @__PURE__ */ new Map();
|
|
4126
4159
|
for (const branch of project.branches) {
|
|
@@ -4155,17 +4188,7 @@ async function startWorker(options) {
|
|
|
4155
4188
|
for (const result of results) {
|
|
4156
4189
|
lastObservedProjectHeads.set(projectBranchKey(project.projectId, result.branchName), result.head);
|
|
4157
4190
|
}
|
|
4158
|
-
|
|
4159
|
-
fetchedProjectMirrorRefsTokens.set(
|
|
4160
|
-
project.projectId,
|
|
4161
|
-
await (0, import_project_worktrees.computeLocalProjectMirrorRefsToken)({
|
|
4162
|
-
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
4163
|
-
primaryBranchName: primaryProjectBranch(project)
|
|
4164
|
-
})
|
|
4165
|
-
);
|
|
4166
|
-
} catch {
|
|
4167
|
-
fetchedProjectMirrorRefsTokens.delete(project.projectId);
|
|
4168
|
-
}
|
|
4191
|
+
await recordLocalProjectMirrorRefsTokens(project);
|
|
4169
4192
|
}
|
|
4170
4193
|
const publishedTombstoneIds = [
|
|
4171
4194
|
...new Set(
|
|
@@ -4191,7 +4214,7 @@ async function startWorker(options) {
|
|
|
4191
4214
|
credentialHelper: deletion.credentialHelper,
|
|
4192
4215
|
credentialUsername: deletion.credentialUsername
|
|
4193
4216
|
});
|
|
4194
|
-
|
|
4217
|
+
forgetLocalProjectMirrorRefsTokens(deletion.projectId, deletion.branchName);
|
|
4195
4218
|
}
|
|
4196
4219
|
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
4197
4220
|
projectWorkspaceState = projectWorkspaceStateStore.recordMirrorRefDeletion({
|
|
@@ -4203,8 +4226,8 @@ async function startWorker(options) {
|
|
|
4203
4226
|
if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
|
|
4204
4227
|
removeProjectCredentialStore(deletion.projectId);
|
|
4205
4228
|
advertisedProjectMirrorRefsTokens.delete(deletion.projectId);
|
|
4206
|
-
|
|
4207
|
-
|
|
4229
|
+
advertisedProjectMirrorMountTokens.delete(deletion.projectId);
|
|
4230
|
+
forgetLocalProjectMirrorRefsTokens(deletion.projectId);
|
|
4208
4231
|
}
|
|
4209
4232
|
lastObservedProjectHeads.delete(key);
|
|
4210
4233
|
}
|
|
@@ -4213,71 +4236,63 @@ async function startWorker(options) {
|
|
|
4213
4236
|
const connection = projectConnection(project);
|
|
4214
4237
|
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
4215
4238
|
const primaryBranchName = primaryProjectBranch(project);
|
|
4216
|
-
|
|
4217
|
-
const advertisedBeforeFetch = advertisedProjectMirrorRefsTokens.get(project.projectId);
|
|
4218
|
-
const observations = await (0, import_project_worktrees.observeProjectMirrorHeads)({
|
|
4219
|
-
projectRoot,
|
|
4220
|
-
primaryBranchName,
|
|
4221
|
-
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4222
|
-
mirrorUrl: connection.mirrorUrl,
|
|
4223
|
-
credentialHelper: connection.credentialHelper,
|
|
4224
|
-
credentialUsername: connection.mirrorCredentialUsername,
|
|
4225
|
-
allowNonFastForward: options2.allowNonFastForward,
|
|
4226
|
-
branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId]))
|
|
4227
|
-
});
|
|
4228
|
-
if (advertisedBeforeFetch === void 0) fetchedProjectMirrorRefsTokens.delete(project.projectId);
|
|
4229
|
-
else fetchedProjectMirrorRefsTokens.set(project.projectId, advertisedBeforeFetch);
|
|
4230
|
-
lastProjectMirrorFetchAtMs.set(project.projectId, Date.now());
|
|
4231
|
-
return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
|
|
4232
|
-
}
|
|
4233
|
-
return {
|
|
4234
|
-
projectId: project.projectId,
|
|
4239
|
+
const observations = await (0, import_project_worktrees.observeProjectMirrorHeads)({
|
|
4235
4240
|
projectRoot,
|
|
4236
4241
|
primaryBranchName,
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
};
|
|
4242
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4243
|
+
mirrorUrl: connection.mirrorUrl,
|
|
4244
|
+
credentialHelper: connection.credentialHelper,
|
|
4245
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
4246
|
+
allowNonFastForward: options2.allowNonFastForward,
|
|
4247
|
+
branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId])),
|
|
4248
|
+
refreshRemote: options2.fetch.scope !== "none",
|
|
4249
|
+
...options2.fetch.scope === "mounts" ? { fetchOnlyBranches: new Set(options2.fetch.mounts.map(({ branchName }) => branchName)) } : {}
|
|
4250
|
+
});
|
|
4251
|
+
if (options2.fetch.scope !== "none") await recordLocalProjectMirrorRefsTokens(project);
|
|
4252
|
+
return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
|
|
4249
4253
|
};
|
|
4250
4254
|
const observeProjectHeadsBeforeOuterWorkspace = async (input) => {
|
|
4251
4255
|
const observationStartedAt = Date.now();
|
|
4252
|
-
const eligibleProjects = [...projectConfigById.values()].filter(
|
|
4256
|
+
const eligibleProjects = [...input.projects ?? projectConfigById.values()].filter(
|
|
4253
4257
|
(project) => !project.executionDisabled && readyProjectIds.has(project.projectId) && project.branches.length > 0
|
|
4254
4258
|
);
|
|
4255
4259
|
try {
|
|
4256
4260
|
if (input.failClosed) {
|
|
4257
4261
|
const bundles2 = [];
|
|
4258
4262
|
for (const project of eligibleProjects) {
|
|
4259
|
-
bundles2.push(
|
|
4263
|
+
bundles2.push(
|
|
4264
|
+
await observeSingleProjectMirrorBundle(project, {
|
|
4265
|
+
allowNonFastForward: input.allowNonFastForward,
|
|
4266
|
+
fetch: { scope: "project", reason: "fail_closed" }
|
|
4267
|
+
})
|
|
4268
|
+
);
|
|
4260
4269
|
}
|
|
4261
4270
|
return bundles2;
|
|
4262
4271
|
}
|
|
4263
|
-
let
|
|
4272
|
+
let wholeMirrorFetches = 0;
|
|
4273
|
+
let scopedMirrorFetches = 0;
|
|
4274
|
+
let fetchedMounts = 0;
|
|
4275
|
+
let eligibleMounts = 0;
|
|
4264
4276
|
const bundles = await mapWithConcurrencyLimit(
|
|
4265
4277
|
eligibleProjects,
|
|
4266
4278
|
PROJECT_MIRROR_OBSERVE_CONCURRENCY,
|
|
4267
4279
|
async (project) => {
|
|
4268
4280
|
try {
|
|
4269
|
-
const
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4281
|
+
const fetch2 = (0, import_project_mirror_fetch_policy.planProjectMirrorObservationFetch)({
|
|
4282
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4283
|
+
advertisedProjectToken: advertisedProjectMirrorRefsTokens.get(project.projectId),
|
|
4284
|
+
advertisedMountTokens: advertisedProjectMirrorMountTokens.get(project.projectId),
|
|
4285
|
+
fetchedProjectToken: fetchedProjectMirrorRefsTokens.get(project.projectId),
|
|
4286
|
+
fetchedMountTokens: fetchedProjectMirrorMountTokens.get(project.projectId) ?? /* @__PURE__ */ new Map(),
|
|
4274
4287
|
skippingEnabled: projectMirrorFetchSkippingEnabled
|
|
4275
4288
|
});
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4289
|
+
eligibleMounts += project.branches.length;
|
|
4290
|
+
if (fetch2.scope === "project") wholeMirrorFetches += 1;
|
|
4291
|
+
if (fetch2.scope === "mounts") {
|
|
4292
|
+
scopedMirrorFetches += 1;
|
|
4293
|
+
fetchedMounts += fetch2.mounts.length;
|
|
4294
|
+
}
|
|
4295
|
+
return await observeSingleProjectMirrorBundle(project, { allowNonFastForward: input.allowNonFastForward, fetch: fetch2 });
|
|
4281
4296
|
} catch (error) {
|
|
4282
4297
|
process.stderr.write(
|
|
4283
4298
|
`[r5d-worker] failed to observe project mirror heads before workspace update: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -4287,9 +4302,9 @@ async function startWorker(options) {
|
|
|
4287
4302
|
}
|
|
4288
4303
|
}
|
|
4289
4304
|
);
|
|
4290
|
-
if (
|
|
4305
|
+
if (wholeMirrorFetches + scopedMirrorFetches > 0) {
|
|
4291
4306
|
process.stderr.write(
|
|
4292
|
-
`[r5d-worker] project mirror observation fetched ${
|
|
4307
|
+
`[r5d-worker] project mirror observation fetched ${wholeMirrorFetches + scopedMirrorFetches} of ${eligibleProjects.length} mirror(s) (${wholeMirrorFetches} whole, ${scopedMirrorFetches} scoped to ${fetchedMounts} of ${eligibleMounts} mount(s))
|
|
4293
4308
|
`
|
|
4294
4309
|
);
|
|
4295
4310
|
}
|
|
@@ -4348,7 +4363,7 @@ async function startWorker(options) {
|
|
|
4348
4363
|
}
|
|
4349
4364
|
}
|
|
4350
4365
|
};
|
|
4351
|
-
const mapWorkspaceGitResult = async (attemptId, trigger, result) => {
|
|
4366
|
+
const mapWorkspaceGitResult = async (attemptId, trigger, result, missingCheckouts) => {
|
|
4352
4367
|
const activeProjectBranchPublications = (0, import_workspace_publication_evidence.activeProjectBranchPublicationEvidence)([...projectConfigById.values()], result.activeMountIds);
|
|
4353
4368
|
return {
|
|
4354
4369
|
type: "workspace_sync",
|
|
@@ -4366,6 +4381,7 @@ async function startWorker(options) {
|
|
|
4366
4381
|
affectedPaths: result.affectedPaths,
|
|
4367
4382
|
activeMountIds: result.activeMountIds,
|
|
4368
4383
|
skippedMountIds: result.skippedMountIds,
|
|
4384
|
+
...missingCheckouts ? { missingCheckouts } : {},
|
|
4369
4385
|
activeProjectBranchPublications,
|
|
4370
4386
|
discardedPaths: [],
|
|
4371
4387
|
localChangesDiscarded: false,
|
|
@@ -4393,6 +4409,7 @@ async function startWorker(options) {
|
|
|
4393
4409
|
affectedPaths: [],
|
|
4394
4410
|
discardedPaths: [],
|
|
4395
4411
|
localChangesDiscarded: false,
|
|
4412
|
+
...lastInventoriedMissingCheckouts ? { missingCheckouts: lastInventoriedMissingCheckouts } : {},
|
|
4396
4413
|
error: error instanceof Error ? error.message : String(error)
|
|
4397
4414
|
};
|
|
4398
4415
|
};
|
|
@@ -4401,8 +4418,13 @@ async function startWorker(options) {
|
|
|
4401
4418
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4402
4419
|
throw new Error("Worker workspace configuration has not been received");
|
|
4403
4420
|
}
|
|
4404
|
-
const
|
|
4421
|
+
const checkoutInventory = inventoryConfiguredProjectCheckouts();
|
|
4422
|
+
logMissingCheckouts(checkoutInventory);
|
|
4423
|
+
const missingCheckouts = reportedMissingCheckouts(checkoutInventory);
|
|
4424
|
+
lastInventoriedMissingCheckouts = missingCheckouts;
|
|
4425
|
+
const configuredMounts = buildWorkspaceMounts(checkoutInventory.projects);
|
|
4405
4426
|
if (input.resetToCanonical) {
|
|
4427
|
+
workspaceProjectionLedger.reset();
|
|
4406
4428
|
const resetHasBusyLiveMount = configuredMounts.some(
|
|
4407
4429
|
(mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
4408
4430
|
);
|
|
@@ -4411,7 +4433,8 @@ async function startWorker(options) {
|
|
|
4411
4433
|
}
|
|
4412
4434
|
const observedProjectHeads2 = await observeProjectHeadsBeforeOuterWorkspace({
|
|
4413
4435
|
allowNonFastForward: true,
|
|
4414
|
-
failClosed: true
|
|
4436
|
+
failClosed: true,
|
|
4437
|
+
projects: checkoutInventory.projects
|
|
4415
4438
|
});
|
|
4416
4439
|
const reset = await (0, import_workspace_git_sync.resetWorkspaceGit)({
|
|
4417
4440
|
workspacePath: workspaceShadowRoot,
|
|
@@ -4439,13 +4462,15 @@ async function startWorker(options) {
|
|
|
4439
4462
|
affectedPaths: reset.discardedPaths,
|
|
4440
4463
|
activeMountIds: reset.activeMountIds,
|
|
4441
4464
|
skippedMountIds: reset.skippedMountIds,
|
|
4465
|
+
missingCheckouts,
|
|
4442
4466
|
discardedPaths: reset.discardedPaths,
|
|
4443
4467
|
localChangesDiscarded: reset.discardedPaths.length > 0 || reset.startingHead !== reset.remoteHead
|
|
4444
4468
|
};
|
|
4445
4469
|
}
|
|
4446
4470
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
4447
4471
|
const mounts = outerRemediation ? (0, import_workspace_git_sync.workspaceGitMountsForRemediation)(configuredMounts) : configuredMounts;
|
|
4448
|
-
|
|
4472
|
+
if (outerRemediation) workspaceProjectionLedger.reset();
|
|
4473
|
+
const observedProjectHeads = outerRemediation ? [] : await observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false, projects: checkoutInventory.projects });
|
|
4449
4474
|
const expectedMirrorHeads = /* @__PURE__ */ new Map();
|
|
4450
4475
|
for (const bundle of observedProjectHeads) {
|
|
4451
4476
|
for (const observation of bundle.observations) {
|
|
@@ -4457,7 +4482,7 @@ async function startWorker(options) {
|
|
|
4457
4482
|
({ projectId, observations }) => observations.filter(({ shouldMove }) => shouldMove).map(({ branchName }) => projectMountId(projectId, branchName))
|
|
4458
4483
|
)
|
|
4459
4484
|
);
|
|
4460
|
-
const publicationHeads = outerRemediation ? /* @__PURE__ */ new Map() : await captureProjectHeadsForWorkspacePublication();
|
|
4485
|
+
const publicationHeads = outerRemediation ? /* @__PURE__ */ new Map() : await captureProjectHeadsForWorkspacePublication(checkoutInventory.projects);
|
|
4461
4486
|
const result = await (0, import_workspace_git_sync.synchronizeWorkspaceGit)({
|
|
4462
4487
|
attemptId: input.attemptId,
|
|
4463
4488
|
workerLabel: label,
|
|
@@ -4471,9 +4496,20 @@ async function startWorker(options) {
|
|
|
4471
4496
|
allowLargeDiff: input.confirmedLargeDiff,
|
|
4472
4497
|
skipMountMirror: outerRemediation,
|
|
4473
4498
|
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4499
|
+
// Same gate as the mirror fetches: while the server's read of the hidden
|
|
4500
|
+
// workspace head equals the head this clone last fetched or pushed, the
|
|
4501
|
+
// cycle's workspace head fetches would transfer nothing.
|
|
4502
|
+
advertisedRemoteHead: projectMirrorFetchSkippingEnabled && !outerRemediation ? advertisedWorkspaceHead : void 0,
|
|
4474
4503
|
assertStillAdmitted: input.assertStillAdmitted,
|
|
4475
4504
|
afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
|
|
4476
|
-
await pushChangedProjectHeads(
|
|
4505
|
+
await pushChangedProjectHeads(
|
|
4506
|
+
activeMountIds,
|
|
4507
|
+
publishedHead,
|
|
4508
|
+
publicationHeads,
|
|
4509
|
+
expectedMirrorHeads,
|
|
4510
|
+
inboundMoveMountIds,
|
|
4511
|
+
checkoutInventory.projects
|
|
4512
|
+
);
|
|
4477
4513
|
},
|
|
4478
4514
|
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4479
4515
|
});
|
|
@@ -4492,7 +4528,11 @@ async function startWorker(options) {
|
|
|
4492
4528
|
result.localHead ?? result.remoteHead
|
|
4493
4529
|
);
|
|
4494
4530
|
}
|
|
4495
|
-
|
|
4531
|
+
const mapped = await mapWorkspaceGitResult(input.attemptId, input.trigger, result, missingCheckouts);
|
|
4532
|
+
if (!outerRemediation) {
|
|
4533
|
+
workspaceProjectionLedger.recordCycle({ result: mapped, mounts, projectedMutationTokens: result.projectedMutationTokens });
|
|
4534
|
+
}
|
|
4535
|
+
return mapped;
|
|
4496
4536
|
};
|
|
4497
4537
|
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
4498
4538
|
try {
|
|
@@ -4530,17 +4570,143 @@ async function startWorker(options) {
|
|
|
4530
4570
|
}, unsafeWorkspaceSyncRecoveryBounceDelayMs(attempt));
|
|
4531
4571
|
timer.unref();
|
|
4532
4572
|
};
|
|
4573
|
+
const captureWorkspaceSyncAdmission = () => ({
|
|
4574
|
+
generation: workerAdmissionGeneration,
|
|
4575
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
4576
|
+
deferredIncidentId: deferredWorkspaceConfiguration?.incidentId ?? null
|
|
4577
|
+
});
|
|
4578
|
+
const workspaceSyncAdmissionSnapshotsMatch = (left, right) => left.generation === right.generation && left.activeIncidentId === right.activeIncidentId && left.deferredIncidentId === right.deferredIncidentId;
|
|
4579
|
+
const assertWorkspaceSyncStillAdmitted = (admitted, trigger) => {
|
|
4580
|
+
if (!workspaceSyncAdmissionSnapshotsMatch(admitted, captureWorkspaceSyncAdmission()) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, trigger)) {
|
|
4581
|
+
throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4582
|
+
}
|
|
4583
|
+
};
|
|
4584
|
+
const executeWorkspaceSyncCycle = async (input) => {
|
|
4585
|
+
const attemptId = input.attemptId;
|
|
4586
|
+
lastWorkspaceSyncMirrorObservationMs = 0;
|
|
4587
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4588
|
+
return await failedWorkspaceSyncResult(
|
|
4589
|
+
attemptId,
|
|
4590
|
+
input.trigger,
|
|
4591
|
+
new Error(
|
|
4592
|
+
`Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
4593
|
+
)
|
|
4594
|
+
);
|
|
4595
|
+
}
|
|
4596
|
+
try {
|
|
4597
|
+
input.assertStillAdmitted();
|
|
4598
|
+
const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
|
|
4599
|
+
return await performWorkspaceSync({
|
|
4600
|
+
attemptId,
|
|
4601
|
+
trigger: input.trigger,
|
|
4602
|
+
confirmedLargeDiff: input.confirmedLargeDiff === true || hasDurableBranchDeletion,
|
|
4603
|
+
confirmationReason: input.confirmationReason ?? (hasDurableBranchDeletion ? "durable project branch deletion" : void 0),
|
|
4604
|
+
resetToCanonical: input.resetToCanonical,
|
|
4605
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4606
|
+
assertStillAdmitted: input.assertStillAdmitted
|
|
4607
|
+
});
|
|
4608
|
+
} catch (error) {
|
|
4609
|
+
workspaceProjectionLedger.reset();
|
|
4610
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4611
|
+
const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
|
|
4612
|
+
error,
|
|
4613
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4614
|
+
inspectRecoverableHydration: () => (0, import_workspace_git_sync.workspaceGitHydrationIsRecoverable)(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4615
|
+
});
|
|
4616
|
+
if (hydrationRecoverable) {
|
|
4617
|
+
if (error instanceof WorkspaceSyncAdmissionChangedError) {
|
|
4618
|
+
process.stderr.write(
|
|
4619
|
+
`[r5d-worker] workspace synchronization was cancelled with every mount at a recorded basis; the next cycle converges it: ${errorMessage}
|
|
4620
|
+
`
|
|
4621
|
+
);
|
|
4622
|
+
}
|
|
4623
|
+
return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
|
|
4624
|
+
}
|
|
4625
|
+
const fenced = fenceUnsafeWorkspaceSyncFailure({
|
|
4626
|
+
hydrationRecoverable,
|
|
4627
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4628
|
+
recoveryAttempts: unsafeWorkspaceSyncRecoveryAttempts,
|
|
4629
|
+
maxRecoveryAttempts: WORKSPACE_UNSAFE_SYNC_MAX_RECOVERY_ATTEMPTS,
|
|
4630
|
+
hasRetainableWork: activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0,
|
|
4631
|
+
bouncePending: unsafeWorkspaceSyncBouncePending,
|
|
4632
|
+
invalidateExecution: () => {
|
|
4633
|
+
workspaceConfigured = false;
|
|
4634
|
+
advanceWorkerAdmissionGeneration();
|
|
4635
|
+
},
|
|
4636
|
+
scheduleRecoveryBounce: () => {
|
|
4637
|
+
unsafeWorkspaceSyncRecoveryAttempts += 1;
|
|
4638
|
+
scheduleUnsafeWorkspaceSyncRecoveryBounce(errorMessage);
|
|
4639
|
+
},
|
|
4640
|
+
fatalExit: (exitCode) => {
|
|
4641
|
+
process.stderr.write(
|
|
4642
|
+
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
|
|
4643
|
+
`
|
|
4644
|
+
);
|
|
4645
|
+
process.exit(exitCode);
|
|
4646
|
+
},
|
|
4647
|
+
exitCode: 1
|
|
4648
|
+
});
|
|
4649
|
+
if (fenced === "recovery_pending") {
|
|
4650
|
+
process.stderr.write(
|
|
4651
|
+
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis while in-process recovery is already pending: ${errorMessage}
|
|
4652
|
+
`
|
|
4653
|
+
);
|
|
4654
|
+
}
|
|
4655
|
+
return {
|
|
4656
|
+
...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
|
|
4657
|
+
failureReason: "unsafe_hydration",
|
|
4658
|
+
recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
};
|
|
4662
|
+
const REMEDIATION_TRIGGER_TYPES = /* @__PURE__ */ new Set(["remediation", "remediation_confirm", "remediation_reset"]);
|
|
4663
|
+
const isOrdinaryWorkspaceSyncRequest = (input) => input.confirmedLargeDiff !== true && input.resetToCanonical !== true && !(input.requiredAncestorHeads && input.requiredAncestorHeads.length > 0) && !REMEDIATION_TRIGGER_TYPES.has(input.trigger.type);
|
|
4664
|
+
const LOCAL_ACTIVITY_TRIGGER_TYPES = /* @__PURE__ */ new Set([
|
|
4665
|
+
"write",
|
|
4666
|
+
"edit",
|
|
4667
|
+
"shell_inline",
|
|
4668
|
+
"process_terminal",
|
|
4669
|
+
"process_cancel"
|
|
4670
|
+
]);
|
|
4671
|
+
const answerOrdinaryWorkspaceSyncFromLastCycle = (requests) => {
|
|
4672
|
+
if (!workspaceConfigured) return null;
|
|
4673
|
+
return workspaceProjectionLedger.answerFromLastCycle({
|
|
4674
|
+
// A workspace-rooted command carries no project target because it may
|
|
4675
|
+
// have touched any checkout.
|
|
4676
|
+
targetMountIds: requests.map(
|
|
4677
|
+
({ trigger }) => LOCAL_ACTIVITY_TRIGGER_TYPES.has(trigger.type) && trigger.projectId && trigger.branchName ? projectMountId(trigger.projectId, trigger.branchName) : null
|
|
4678
|
+
),
|
|
4679
|
+
mounts: buildWorkspaceMounts(),
|
|
4680
|
+
pendingDeletions: pendingMirrorDeletes.size > 0 || projectWorkspaceState.pendingTreeDeletions.length > 0 || projectWorkspaceState.pendingMirrorRefDeletions.length > 0
|
|
4681
|
+
});
|
|
4682
|
+
};
|
|
4683
|
+
const ordinaryWorkspaceSyncCoalescer = new import_workspace_sync_coalescer.WorkspaceSyncCoalescer({
|
|
4684
|
+
runExclusive: (operation) => workspaceSyncSingleFlight.runExclusive(operation),
|
|
4685
|
+
runBatch: async (requests) => {
|
|
4686
|
+
const startedAtMs = Date.now();
|
|
4687
|
+
const admission = captureWorkspaceSyncAdmission();
|
|
4688
|
+
const staleAttemptIds = /* @__PURE__ */ new Set();
|
|
4689
|
+
const admitted = [];
|
|
4690
|
+
for (const request of requests) {
|
|
4691
|
+
if (workspaceSyncAdmissionSnapshotsMatch(request.admission, admission)) admitted.push(request);
|
|
4692
|
+
else staleAttemptIds.add(request.attemptId);
|
|
4693
|
+
}
|
|
4694
|
+
const leader = admitted[0];
|
|
4695
|
+
if (!leader) return { result: null, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
|
|
4696
|
+
const answered = answerOrdinaryWorkspaceSyncFromLastCycle(admitted);
|
|
4697
|
+
if (answered) return { result: answered, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
|
|
4698
|
+
const result = await executeWorkspaceSyncCycle({
|
|
4699
|
+
attemptId: leader.attemptId,
|
|
4700
|
+
trigger: leader.trigger,
|
|
4701
|
+
assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, leader.trigger)
|
|
4702
|
+
});
|
|
4703
|
+
return { result, ranCycle: true, startedAtMs, finishedAtMs: Date.now(), prepareMs: lastWorkspaceSyncMirrorObservationMs, staleAttemptIds };
|
|
4704
|
+
}
|
|
4705
|
+
});
|
|
4533
4706
|
const runWorkspaceSync = async (input) => {
|
|
4534
4707
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
4535
4708
|
const requestedAt = Date.now();
|
|
4536
|
-
const
|
|
4537
|
-
const admittedActiveIncidentId = activeWorkspaceIncidentId;
|
|
4538
|
-
const admittedDeferredIncidentId = deferredWorkspaceConfiguration?.incidentId ?? null;
|
|
4539
|
-
const assertStillAdmitted = () => {
|
|
4540
|
-
if (admittedGeneration !== workerAdmissionGeneration || admittedActiveIncidentId !== activeWorkspaceIncidentId || admittedDeferredIncidentId !== (deferredWorkspaceConfiguration?.incidentId ?? null) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4541
|
-
throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4542
|
-
}
|
|
4543
|
-
};
|
|
4709
|
+
const admission = captureWorkspaceSyncAdmission();
|
|
4544
4710
|
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4545
4711
|
const result2 = {
|
|
4546
4712
|
...await failedWorkspaceSyncResult(
|
|
@@ -4566,104 +4732,47 @@ async function startWorker(options) {
|
|
|
4566
4732
|
}
|
|
4567
4733
|
ws.send(JSON.stringify({ type: "workspace_sync_started", attemptId }));
|
|
4568
4734
|
}
|
|
4569
|
-
let
|
|
4570
|
-
let
|
|
4735
|
+
let queueMs = 0;
|
|
4736
|
+
let prepareMs = 0;
|
|
4737
|
+
let synchronizeMs = 0;
|
|
4571
4738
|
workspaceSyncRequestsInFlight += 1;
|
|
4572
4739
|
let result;
|
|
4573
4740
|
try {
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
|
|
4590
|
-
return await performWorkspaceSync({
|
|
4741
|
+
if (isOrdinaryWorkspaceSyncRequest(input)) {
|
|
4742
|
+
const settlement = await ordinaryWorkspaceSyncCoalescer.request({ attemptId, trigger: input.trigger, admission });
|
|
4743
|
+
queueMs = settlement.startedAtMs - requestedAt;
|
|
4744
|
+
prepareMs = settlement.prepareMs;
|
|
4745
|
+
synchronizeMs = settlement.finishedAtMs - settlement.startedAtMs;
|
|
4746
|
+
result = settlement.staleAttemptIds.has(attemptId) || settlement.result === null ? await failedWorkspaceSyncResult(
|
|
4747
|
+
attemptId,
|
|
4748
|
+
input.trigger,
|
|
4749
|
+
new Error("Workspace synchronization admission changed while the request was queued")
|
|
4750
|
+
) : { ...settlement.result, attemptId, trigger: input.trigger };
|
|
4751
|
+
} else {
|
|
4752
|
+
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4753
|
+
const startedAt = Date.now();
|
|
4754
|
+
queueMs = startedAt - requestedAt;
|
|
4755
|
+
const cycleResult = await executeWorkspaceSyncCycle({
|
|
4591
4756
|
attemptId,
|
|
4592
4757
|
trigger: input.trigger,
|
|
4593
|
-
confirmedLargeDiff: input.confirmedLargeDiff
|
|
4594
|
-
confirmationReason: input.confirmationReason
|
|
4758
|
+
confirmedLargeDiff: input.confirmedLargeDiff,
|
|
4759
|
+
confirmationReason: input.confirmationReason,
|
|
4595
4760
|
resetToCanonical: input.resetToCanonical,
|
|
4596
4761
|
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4597
|
-
assertStillAdmitted
|
|
4762
|
+
assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, input.trigger)
|
|
4598
4763
|
});
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
inspectRecoverableHydration: () => (0, import_workspace_git_sync.workspaceGitHydrationIsRecoverable)(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4605
|
-
});
|
|
4606
|
-
if (hydrationRecoverable) {
|
|
4607
|
-
if (error instanceof WorkspaceSyncAdmissionChangedError) {
|
|
4608
|
-
process.stderr.write(
|
|
4609
|
-
`[r5d-worker] workspace synchronization was cancelled with every mount at a recorded basis; the next cycle converges it: ${errorMessage}
|
|
4610
|
-
`
|
|
4611
|
-
);
|
|
4612
|
-
}
|
|
4613
|
-
return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
|
|
4614
|
-
}
|
|
4615
|
-
const fenced = fenceUnsafeWorkspaceSyncFailure({
|
|
4616
|
-
hydrationRecoverable,
|
|
4617
|
-
resetToCanonical: input.resetToCanonical === true,
|
|
4618
|
-
recoveryAttempts: unsafeWorkspaceSyncRecoveryAttempts,
|
|
4619
|
-
maxRecoveryAttempts: WORKSPACE_UNSAFE_SYNC_MAX_RECOVERY_ATTEMPTS,
|
|
4620
|
-
hasRetainableWork: activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0,
|
|
4621
|
-
bouncePending: unsafeWorkspaceSyncBouncePending,
|
|
4622
|
-
invalidateExecution: () => {
|
|
4623
|
-
workspaceConfigured = false;
|
|
4624
|
-
advanceWorkerAdmissionGeneration();
|
|
4625
|
-
},
|
|
4626
|
-
scheduleRecoveryBounce: () => {
|
|
4627
|
-
unsafeWorkspaceSyncRecoveryAttempts += 1;
|
|
4628
|
-
scheduleUnsafeWorkspaceSyncRecoveryBounce(errorMessage);
|
|
4629
|
-
},
|
|
4630
|
-
fatalExit: (exitCode) => {
|
|
4631
|
-
process.stderr.write(
|
|
4632
|
-
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
|
|
4633
|
-
`
|
|
4634
|
-
);
|
|
4635
|
-
process.exit(exitCode);
|
|
4636
|
-
},
|
|
4637
|
-
exitCode: 1
|
|
4638
|
-
});
|
|
4639
|
-
if (fenced === "recovery_pending") {
|
|
4640
|
-
process.stderr.write(
|
|
4641
|
-
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis while in-process recovery is already pending: ${errorMessage}
|
|
4642
|
-
`
|
|
4643
|
-
);
|
|
4644
|
-
}
|
|
4645
|
-
return {
|
|
4646
|
-
...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
|
|
4647
|
-
failureReason: "unsafe_hydration",
|
|
4648
|
-
recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
|
|
4649
|
-
};
|
|
4650
|
-
}
|
|
4651
|
-
});
|
|
4764
|
+
prepareMs = lastWorkspaceSyncMirrorObservationMs;
|
|
4765
|
+
synchronizeMs = Date.now() - startedAt;
|
|
4766
|
+
return cycleResult;
|
|
4767
|
+
});
|
|
4768
|
+
}
|
|
4652
4769
|
} finally {
|
|
4653
4770
|
workspaceSyncRequestsInFlight -= 1;
|
|
4654
4771
|
}
|
|
4655
4772
|
if (result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
|
|
4656
|
-
const finishedAt = Date.now();
|
|
4657
4773
|
result = {
|
|
4658
4774
|
...result,
|
|
4659
|
-
telemetry: {
|
|
4660
|
-
totalMs: finishedAt - requestedAt,
|
|
4661
|
-
queueMs: queueEnteredAt - requestedAt,
|
|
4662
|
-
// Duration of the pre-outer mirror-head observation phase; near zero
|
|
4663
|
-
// on a cycle whose token-gated observation skipped every fetch.
|
|
4664
|
-
prepareMs: lastWorkspaceSyncMirrorObservationMs,
|
|
4665
|
-
synchronizeMs: finishedAt - syncStartedAt
|
|
4666
|
-
}
|
|
4775
|
+
telemetry: { totalMs: Date.now() - requestedAt, queueMs, prepareMs, synchronizeMs }
|
|
4667
4776
|
};
|
|
4668
4777
|
if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result);
|
|
4669
4778
|
sendLifecycleProgress(
|
|
@@ -4933,6 +5042,7 @@ async function startWorker(options) {
|
|
|
4933
5042
|
);
|
|
4934
5043
|
}
|
|
4935
5044
|
workspaceConfigured = false;
|
|
5045
|
+
workspaceProjectionLedger.reset();
|
|
4936
5046
|
const preserveOnlyBranches = message.projects.flatMap(
|
|
4937
5047
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
4938
5048
|
);
|
|
@@ -4942,6 +5052,16 @@ async function startWorker(options) {
|
|
|
4942
5052
|
preserveOnlyBranches
|
|
4943
5053
|
});
|
|
4944
5054
|
}
|
|
5055
|
+
for (const project of message.projects) {
|
|
5056
|
+
for (const branch of project.branches) {
|
|
5057
|
+
const pending2 = projectWorkspaceState.locallyPendingCreatedBranches.find(
|
|
5058
|
+
(candidate) => candidate.projectId === project.projectId && candidate.branchName === branch.branchName
|
|
5059
|
+
);
|
|
5060
|
+
if (pending2 && pending2.branchId === branch.branchId) {
|
|
5061
|
+
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pending2);
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
4945
5065
|
const stillPendingCreatedBranches = new Map(
|
|
4946
5066
|
projectWorkspaceState.locallyPendingCreatedBranches.map((branch) => [
|
|
4947
5067
|
(0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(branch.projectId, branch.branchName),
|
|
@@ -5383,11 +5503,10 @@ async function startWorker(options) {
|
|
|
5383
5503
|
workspaceConfigResetToCanonicalV1: true,
|
|
5384
5504
|
projectBranchDeletionFastAckV1: true,
|
|
5385
5505
|
projectMirrorLeaseV1: true,
|
|
5386
|
-
projectMirrorRefsTokensV1: true
|
|
5506
|
+
projectMirrorRefsTokensV1: true,
|
|
5507
|
+
projectBranchWorkingTreeModeV1: true
|
|
5387
5508
|
},
|
|
5388
|
-
|
|
5389
|
-
artifactRoot,
|
|
5390
|
-
planRoot
|
|
5509
|
+
root: rootDir
|
|
5391
5510
|
}
|
|
5392
5511
|
};
|
|
5393
5512
|
ws.send(JSON.stringify(hello));
|
|
@@ -5422,14 +5541,12 @@ async function startWorker(options) {
|
|
|
5422
5541
|
return;
|
|
5423
5542
|
}
|
|
5424
5543
|
if (message.type === "project_mirror_refs_tokens") {
|
|
5544
|
+
const advertised = (0, import_project_mirror_fetch_policy.parseProjectMirrorRefsTokensMessage)(message);
|
|
5425
5545
|
advertisedProjectMirrorRefsTokens.clear();
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5546
|
+
for (const [projectId, token2] of advertised.projectTokens) advertisedProjectMirrorRefsTokens.set(projectId, token2);
|
|
5547
|
+
advertisedProjectMirrorMountTokens.clear();
|
|
5548
|
+
for (const [projectId, tokens] of advertised.mountTokens) advertisedProjectMirrorMountTokens.set(projectId, tokens);
|
|
5549
|
+
advertisedWorkspaceHead = advertised.workspaceHead;
|
|
5433
5550
|
return;
|
|
5434
5551
|
}
|
|
5435
5552
|
if (message.type === "workspace_config") {
|
|
@@ -5502,13 +5619,21 @@ async function startWorker(options) {
|
|
|
5502
5619
|
});
|
|
5503
5620
|
return;
|
|
5504
5621
|
}
|
|
5622
|
+
sendWorkerMessage(ws, {
|
|
5623
|
+
type: "operation_queued",
|
|
5624
|
+
requestId: message.requestId,
|
|
5625
|
+
queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
|
|
5626
|
+
});
|
|
5505
5627
|
try {
|
|
5628
|
+
if (message.workingTree !== "carry" && message.workingTree !== "clean") {
|
|
5629
|
+
throw new Error('Project branch creation requires workingTree "carry" or "clean"');
|
|
5630
|
+
}
|
|
5506
5631
|
const pendingBranch = {
|
|
5507
5632
|
branchId: message.branchId,
|
|
5508
5633
|
projectId: message.projectId,
|
|
5509
5634
|
branchName: message.targetBranch
|
|
5510
5635
|
};
|
|
5511
|
-
const created = await workspaceSyncSingleFlight.
|
|
5636
|
+
const created = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
5512
5637
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5513
5638
|
throw new Error(
|
|
5514
5639
|
`Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -5549,6 +5674,7 @@ async function startWorker(options) {
|
|
|
5549
5674
|
primaryBranchName: primaryProjectBranch(project),
|
|
5550
5675
|
sourceBranchName: message.sourceBranch,
|
|
5551
5676
|
branchName: message.targetBranch,
|
|
5677
|
+
workingTree: message.workingTree,
|
|
5552
5678
|
pendingRetry: pendingWasRecorded
|
|
5553
5679
|
});
|
|
5554
5680
|
gitBranchCreated = true;
|
|
@@ -5605,9 +5731,14 @@ async function startWorker(options) {
|
|
|
5605
5731
|
});
|
|
5606
5732
|
return;
|
|
5607
5733
|
}
|
|
5734
|
+
sendWorkerMessage(ws, {
|
|
5735
|
+
type: "operation_queued",
|
|
5736
|
+
requestId: message.requestId,
|
|
5737
|
+
queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
|
|
5738
|
+
});
|
|
5608
5739
|
try {
|
|
5609
5740
|
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
5610
|
-
const deletionNeedsSync = await workspaceSyncSingleFlight.
|
|
5741
|
+
const deletionNeedsSync = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
5611
5742
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5612
5743
|
throw new Error(
|
|
5613
5744
|
`Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -5638,6 +5769,9 @@ async function startWorker(options) {
|
|
|
5638
5769
|
projectId: message.projectId,
|
|
5639
5770
|
branchName: message.branchName
|
|
5640
5771
|
});
|
|
5772
|
+
if (pendingLocalBranch) {
|
|
5773
|
+
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pendingLocalBranch);
|
|
5774
|
+
}
|
|
5641
5775
|
pendingCreatedBranchPublicationNotBefore.delete((0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(message.projectId, message.branchName));
|
|
5642
5776
|
if (pendingMirrorDeletes.has(deletionKey)) return true;
|
|
5643
5777
|
stageProjectBranchDeletion(project, message.branchName, true);
|
|
@@ -5915,7 +6049,7 @@ async function startWorker(options) {
|
|
|
5915
6049
|
ws,
|
|
5916
6050
|
message,
|
|
5917
6051
|
resolvedTarget,
|
|
5918
|
-
|
|
6052
|
+
rootDir,
|
|
5919
6053
|
assertAdmission: assertMessageAdmission,
|
|
5920
6054
|
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
|
|
5921
6055
|
...targetMayMutateVisibleWorkspace(message.target) ? {
|
|
@@ -6013,8 +6147,7 @@ async function startWorker(options) {
|
|
|
6013
6147
|
resolvedTarget,
|
|
6014
6148
|
baseUrl,
|
|
6015
6149
|
token,
|
|
6016
|
-
|
|
6017
|
-
planRoot,
|
|
6150
|
+
rootDir,
|
|
6018
6151
|
setPreparationHeartbeatLease: (active) => {
|
|
6019
6152
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
6020
6153
|
type: "heartbeat_lease",
|
|
@@ -6083,8 +6216,7 @@ async function startWorker(options) {
|
|
|
6083
6216
|
resolvedTarget,
|
|
6084
6217
|
baseUrl,
|
|
6085
6218
|
token,
|
|
6086
|
-
|
|
6087
|
-
planRoot,
|
|
6219
|
+
rootDir,
|
|
6088
6220
|
setPreparationHeartbeatLease: (active) => {
|
|
6089
6221
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
6090
6222
|
type: "heartbeat_lease",
|
|
@@ -6166,8 +6298,7 @@ async function startWorker(options) {
|
|
|
6166
6298
|
resolvedTarget,
|
|
6167
6299
|
baseUrl,
|
|
6168
6300
|
token,
|
|
6169
|
-
|
|
6170
|
-
planRoot,
|
|
6301
|
+
rootDir,
|
|
6171
6302
|
assertAdmission: assertMessageAdmission
|
|
6172
6303
|
});
|
|
6173
6304
|
});
|