@ricsam/r5d-worker 0.0.100 → 0.0.103
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 +390 -322
- 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 +49 -32
- package/dist/cjs/working-tree-mirror.cjs +347 -99
- package/dist/cjs/workspace-git-sync.cjs +189 -28
- package/dist/cjs/workspace-merge-projection.cjs +5 -19
- 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 +395 -324
- 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 +52 -33
- package/dist/mjs/working-tree-mirror.mjs +347 -99
- package/dist/mjs/workspace-git-sync.mjs +189 -28
- package/dist/mjs/workspace-merge-projection.mjs +5 -19
- 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 +29 -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 +26 -8
- package/dist/types/working-tree-mirror.d.ts +56 -11
- package/dist/types/workspace-git-sync.d.ts +41 -4
- 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,6 +3504,7 @@ 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;
|
|
@@ -3892,7 +3874,7 @@ async function startWorker(options) {
|
|
|
3892
3874
|
workspaceRelativePath: "workspace-plans",
|
|
3893
3875
|
sourceMode: "all",
|
|
3894
3876
|
hydrateDeletionMode: "all",
|
|
3895
|
-
// Workspace-level agents
|
|
3877
|
+
// Workspace-level agents reach this directory through $R5D_ROOT/plans.
|
|
3896
3878
|
// Keep both projection and hydration away from it while any visible-
|
|
3897
3879
|
// projects process, PTY, or built-in write/edit is active or reserved.
|
|
3898
3880
|
busy: () => {
|
|
@@ -3930,7 +3912,10 @@ async function startWorker(options) {
|
|
|
3930
3912
|
deleteWhenSourceMissing: true
|
|
3931
3913
|
});
|
|
3932
3914
|
}
|
|
3933
|
-
return mounts
|
|
3915
|
+
return mounts.map((mount) => {
|
|
3916
|
+
const lastProjectedMutationToken = workspaceProjectionLedger.lastProjectedToken(mount);
|
|
3917
|
+
return lastProjectedMutationToken === void 0 ? mount : { ...mount, lastProjectedMutationToken };
|
|
3918
|
+
});
|
|
3934
3919
|
};
|
|
3935
3920
|
const workspaceLocalHead = async () => {
|
|
3936
3921
|
if (!import_node_fs.default.existsSync(import_node_path.default.join(workspaceShadowRoot, ".git"))) return null;
|
|
@@ -4155,17 +4140,7 @@ async function startWorker(options) {
|
|
|
4155
4140
|
for (const result of results) {
|
|
4156
4141
|
lastObservedProjectHeads.set(projectBranchKey(project.projectId, result.branchName), result.head);
|
|
4157
4142
|
}
|
|
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
|
-
}
|
|
4143
|
+
await recordLocalProjectMirrorRefsTokens(project);
|
|
4169
4144
|
}
|
|
4170
4145
|
const publishedTombstoneIds = [
|
|
4171
4146
|
...new Set(
|
|
@@ -4191,7 +4166,7 @@ async function startWorker(options) {
|
|
|
4191
4166
|
credentialHelper: deletion.credentialHelper,
|
|
4192
4167
|
credentialUsername: deletion.credentialUsername
|
|
4193
4168
|
});
|
|
4194
|
-
|
|
4169
|
+
forgetLocalProjectMirrorRefsTokens(deletion.projectId, deletion.branchName);
|
|
4195
4170
|
}
|
|
4196
4171
|
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
4197
4172
|
projectWorkspaceState = projectWorkspaceStateStore.recordMirrorRefDeletion({
|
|
@@ -4203,8 +4178,8 @@ async function startWorker(options) {
|
|
|
4203
4178
|
if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
|
|
4204
4179
|
removeProjectCredentialStore(deletion.projectId);
|
|
4205
4180
|
advertisedProjectMirrorRefsTokens.delete(deletion.projectId);
|
|
4206
|
-
|
|
4207
|
-
|
|
4181
|
+
advertisedProjectMirrorMountTokens.delete(deletion.projectId);
|
|
4182
|
+
forgetLocalProjectMirrorRefsTokens(deletion.projectId);
|
|
4208
4183
|
}
|
|
4209
4184
|
lastObservedProjectHeads.delete(key);
|
|
4210
4185
|
}
|
|
@@ -4213,39 +4188,20 @@ async function startWorker(options) {
|
|
|
4213
4188
|
const connection = projectConnection(project);
|
|
4214
4189
|
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
4215
4190
|
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,
|
|
4191
|
+
const observations = await (0, import_project_worktrees.observeProjectMirrorHeads)({
|
|
4235
4192
|
projectRoot,
|
|
4236
4193
|
primaryBranchName,
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
};
|
|
4194
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4195
|
+
mirrorUrl: connection.mirrorUrl,
|
|
4196
|
+
credentialHelper: connection.credentialHelper,
|
|
4197
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
4198
|
+
allowNonFastForward: options2.allowNonFastForward,
|
|
4199
|
+
branchIds: new Map(project.branches.map(({ branchId, branchName }) => [branchName, branchId])),
|
|
4200
|
+
refreshRemote: options2.fetch.scope !== "none",
|
|
4201
|
+
...options2.fetch.scope === "mounts" ? { fetchOnlyBranches: new Set(options2.fetch.mounts.map(({ branchName }) => branchName)) } : {}
|
|
4202
|
+
});
|
|
4203
|
+
if (options2.fetch.scope !== "none") await recordLocalProjectMirrorRefsTokens(project);
|
|
4204
|
+
return { projectId: project.projectId, projectRoot, primaryBranchName, observations };
|
|
4249
4205
|
};
|
|
4250
4206
|
const observeProjectHeadsBeforeOuterWorkspace = async (input) => {
|
|
4251
4207
|
const observationStartedAt = Date.now();
|
|
@@ -4256,28 +4212,39 @@ async function startWorker(options) {
|
|
|
4256
4212
|
if (input.failClosed) {
|
|
4257
4213
|
const bundles2 = [];
|
|
4258
4214
|
for (const project of eligibleProjects) {
|
|
4259
|
-
bundles2.push(
|
|
4215
|
+
bundles2.push(
|
|
4216
|
+
await observeSingleProjectMirrorBundle(project, {
|
|
4217
|
+
allowNonFastForward: input.allowNonFastForward,
|
|
4218
|
+
fetch: { scope: "project", reason: "fail_closed" }
|
|
4219
|
+
})
|
|
4220
|
+
);
|
|
4260
4221
|
}
|
|
4261
4222
|
return bundles2;
|
|
4262
4223
|
}
|
|
4263
|
-
let
|
|
4224
|
+
let wholeMirrorFetches = 0;
|
|
4225
|
+
let scopedMirrorFetches = 0;
|
|
4226
|
+
let fetchedMounts = 0;
|
|
4227
|
+
let eligibleMounts = 0;
|
|
4264
4228
|
const bundles = await mapWithConcurrencyLimit(
|
|
4265
4229
|
eligibleProjects,
|
|
4266
4230
|
PROJECT_MIRROR_OBSERVE_CONCURRENCY,
|
|
4267
4231
|
async (project) => {
|
|
4268
4232
|
try {
|
|
4269
|
-
const
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4233
|
+
const fetch2 = (0, import_project_mirror_fetch_policy.planProjectMirrorObservationFetch)({
|
|
4234
|
+
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
4235
|
+
advertisedProjectToken: advertisedProjectMirrorRefsTokens.get(project.projectId),
|
|
4236
|
+
advertisedMountTokens: advertisedProjectMirrorMountTokens.get(project.projectId),
|
|
4237
|
+
fetchedProjectToken: fetchedProjectMirrorRefsTokens.get(project.projectId),
|
|
4238
|
+
fetchedMountTokens: fetchedProjectMirrorMountTokens.get(project.projectId) ?? /* @__PURE__ */ new Map(),
|
|
4274
4239
|
skippingEnabled: projectMirrorFetchSkippingEnabled
|
|
4275
4240
|
});
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4241
|
+
eligibleMounts += project.branches.length;
|
|
4242
|
+
if (fetch2.scope === "project") wholeMirrorFetches += 1;
|
|
4243
|
+
if (fetch2.scope === "mounts") {
|
|
4244
|
+
scopedMirrorFetches += 1;
|
|
4245
|
+
fetchedMounts += fetch2.mounts.length;
|
|
4246
|
+
}
|
|
4247
|
+
return await observeSingleProjectMirrorBundle(project, { allowNonFastForward: input.allowNonFastForward, fetch: fetch2 });
|
|
4281
4248
|
} catch (error) {
|
|
4282
4249
|
process.stderr.write(
|
|
4283
4250
|
`[r5d-worker] failed to observe project mirror heads before workspace update: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -4287,9 +4254,9 @@ async function startWorker(options) {
|
|
|
4287
4254
|
}
|
|
4288
4255
|
}
|
|
4289
4256
|
);
|
|
4290
|
-
if (
|
|
4257
|
+
if (wholeMirrorFetches + scopedMirrorFetches > 0) {
|
|
4291
4258
|
process.stderr.write(
|
|
4292
|
-
`[r5d-worker] project mirror observation fetched ${
|
|
4259
|
+
`[r5d-worker] project mirror observation fetched ${wholeMirrorFetches + scopedMirrorFetches} of ${eligibleProjects.length} mirror(s) (${wholeMirrorFetches} whole, ${scopedMirrorFetches} scoped to ${fetchedMounts} of ${eligibleMounts} mount(s))
|
|
4293
4260
|
`
|
|
4294
4261
|
);
|
|
4295
4262
|
}
|
|
@@ -4403,6 +4370,7 @@ async function startWorker(options) {
|
|
|
4403
4370
|
}
|
|
4404
4371
|
const configuredMounts = buildWorkspaceMounts();
|
|
4405
4372
|
if (input.resetToCanonical) {
|
|
4373
|
+
workspaceProjectionLedger.reset();
|
|
4406
4374
|
const resetHasBusyLiveMount = configuredMounts.some(
|
|
4407
4375
|
(mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
4408
4376
|
);
|
|
@@ -4445,6 +4413,7 @@ async function startWorker(options) {
|
|
|
4445
4413
|
}
|
|
4446
4414
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
4447
4415
|
const mounts = outerRemediation ? (0, import_workspace_git_sync.workspaceGitMountsForRemediation)(configuredMounts) : configuredMounts;
|
|
4416
|
+
if (outerRemediation) workspaceProjectionLedger.reset();
|
|
4448
4417
|
const observedProjectHeads = outerRemediation ? [] : await observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
4449
4418
|
const expectedMirrorHeads = /* @__PURE__ */ new Map();
|
|
4450
4419
|
for (const bundle of observedProjectHeads) {
|
|
@@ -4471,6 +4440,10 @@ async function startWorker(options) {
|
|
|
4471
4440
|
allowLargeDiff: input.confirmedLargeDiff,
|
|
4472
4441
|
skipMountMirror: outerRemediation,
|
|
4473
4442
|
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4443
|
+
// Same gate as the mirror fetches: while the server's read of the hidden
|
|
4444
|
+
// workspace head equals the head this clone last fetched or pushed, the
|
|
4445
|
+
// cycle's workspace head fetches would transfer nothing.
|
|
4446
|
+
advertisedRemoteHead: projectMirrorFetchSkippingEnabled && !outerRemediation ? advertisedWorkspaceHead : void 0,
|
|
4474
4447
|
assertStillAdmitted: input.assertStillAdmitted,
|
|
4475
4448
|
afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
|
|
4476
4449
|
await pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, inboundMoveMountIds);
|
|
@@ -4492,7 +4465,11 @@ async function startWorker(options) {
|
|
|
4492
4465
|
result.localHead ?? result.remoteHead
|
|
4493
4466
|
);
|
|
4494
4467
|
}
|
|
4495
|
-
|
|
4468
|
+
const mapped = await mapWorkspaceGitResult(input.attemptId, input.trigger, result);
|
|
4469
|
+
if (!outerRemediation) {
|
|
4470
|
+
workspaceProjectionLedger.recordCycle({ result: mapped, mounts, projectedMutationTokens: result.projectedMutationTokens });
|
|
4471
|
+
}
|
|
4472
|
+
return mapped;
|
|
4496
4473
|
};
|
|
4497
4474
|
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
4498
4475
|
try {
|
|
@@ -4530,17 +4507,143 @@ async function startWorker(options) {
|
|
|
4530
4507
|
}, unsafeWorkspaceSyncRecoveryBounceDelayMs(attempt));
|
|
4531
4508
|
timer.unref();
|
|
4532
4509
|
};
|
|
4510
|
+
const captureWorkspaceSyncAdmission = () => ({
|
|
4511
|
+
generation: workerAdmissionGeneration,
|
|
4512
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
4513
|
+
deferredIncidentId: deferredWorkspaceConfiguration?.incidentId ?? null
|
|
4514
|
+
});
|
|
4515
|
+
const workspaceSyncAdmissionSnapshotsMatch = (left, right) => left.generation === right.generation && left.activeIncidentId === right.activeIncidentId && left.deferredIncidentId === right.deferredIncidentId;
|
|
4516
|
+
const assertWorkspaceSyncStillAdmitted = (admitted, trigger) => {
|
|
4517
|
+
if (!workspaceSyncAdmissionSnapshotsMatch(admitted, captureWorkspaceSyncAdmission()) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, trigger)) {
|
|
4518
|
+
throw new WorkspaceSyncAdmissionChangedError("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4519
|
+
}
|
|
4520
|
+
};
|
|
4521
|
+
const executeWorkspaceSyncCycle = async (input) => {
|
|
4522
|
+
const attemptId = input.attemptId;
|
|
4523
|
+
lastWorkspaceSyncMirrorObservationMs = 0;
|
|
4524
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4525
|
+
return await failedWorkspaceSyncResult(
|
|
4526
|
+
attemptId,
|
|
4527
|
+
input.trigger,
|
|
4528
|
+
new Error(
|
|
4529
|
+
`Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
4530
|
+
)
|
|
4531
|
+
);
|
|
4532
|
+
}
|
|
4533
|
+
try {
|
|
4534
|
+
input.assertStillAdmitted();
|
|
4535
|
+
const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
|
|
4536
|
+
return await performWorkspaceSync({
|
|
4537
|
+
attemptId,
|
|
4538
|
+
trigger: input.trigger,
|
|
4539
|
+
confirmedLargeDiff: input.confirmedLargeDiff === true || hasDurableBranchDeletion,
|
|
4540
|
+
confirmationReason: input.confirmationReason ?? (hasDurableBranchDeletion ? "durable project branch deletion" : void 0),
|
|
4541
|
+
resetToCanonical: input.resetToCanonical,
|
|
4542
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4543
|
+
assertStillAdmitted: input.assertStillAdmitted
|
|
4544
|
+
});
|
|
4545
|
+
} catch (error) {
|
|
4546
|
+
workspaceProjectionLedger.reset();
|
|
4547
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4548
|
+
const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
|
|
4549
|
+
error,
|
|
4550
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4551
|
+
inspectRecoverableHydration: () => (0, import_workspace_git_sync.workspaceGitHydrationIsRecoverable)(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4552
|
+
});
|
|
4553
|
+
if (hydrationRecoverable) {
|
|
4554
|
+
if (error instanceof WorkspaceSyncAdmissionChangedError) {
|
|
4555
|
+
process.stderr.write(
|
|
4556
|
+
`[r5d-worker] workspace synchronization was cancelled with every mount at a recorded basis; the next cycle converges it: ${errorMessage}
|
|
4557
|
+
`
|
|
4558
|
+
);
|
|
4559
|
+
}
|
|
4560
|
+
return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
|
|
4561
|
+
}
|
|
4562
|
+
const fenced = fenceUnsafeWorkspaceSyncFailure({
|
|
4563
|
+
hydrationRecoverable,
|
|
4564
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4565
|
+
recoveryAttempts: unsafeWorkspaceSyncRecoveryAttempts,
|
|
4566
|
+
maxRecoveryAttempts: WORKSPACE_UNSAFE_SYNC_MAX_RECOVERY_ATTEMPTS,
|
|
4567
|
+
hasRetainableWork: activeProcesses.size > 0 || credentialBearingProcessGroups.size > 0 || activePtys.size > 0 || pendingProcessTerminals.size > 0,
|
|
4568
|
+
bouncePending: unsafeWorkspaceSyncBouncePending,
|
|
4569
|
+
invalidateExecution: () => {
|
|
4570
|
+
workspaceConfigured = false;
|
|
4571
|
+
advanceWorkerAdmissionGeneration();
|
|
4572
|
+
},
|
|
4573
|
+
scheduleRecoveryBounce: () => {
|
|
4574
|
+
unsafeWorkspaceSyncRecoveryAttempts += 1;
|
|
4575
|
+
scheduleUnsafeWorkspaceSyncRecoveryBounce(errorMessage);
|
|
4576
|
+
},
|
|
4577
|
+
fatalExit: (exitCode) => {
|
|
4578
|
+
process.stderr.write(
|
|
4579
|
+
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis; exiting for exact recovery after ${unsafeWorkspaceSyncRecoveryAttempts} in-process recovery attempt(s): ${errorMessage}
|
|
4580
|
+
`
|
|
4581
|
+
);
|
|
4582
|
+
process.exit(exitCode);
|
|
4583
|
+
},
|
|
4584
|
+
exitCode: 1
|
|
4585
|
+
});
|
|
4586
|
+
if (fenced === "recovery_pending") {
|
|
4587
|
+
process.stderr.write(
|
|
4588
|
+
`[r5d-worker] workspace synchronization failed without a recoverable hydration basis while in-process recovery is already pending: ${errorMessage}
|
|
4589
|
+
`
|
|
4590
|
+
);
|
|
4591
|
+
}
|
|
4592
|
+
return {
|
|
4593
|
+
...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
|
|
4594
|
+
failureReason: "unsafe_hydration",
|
|
4595
|
+
recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
};
|
|
4599
|
+
const REMEDIATION_TRIGGER_TYPES = /* @__PURE__ */ new Set(["remediation", "remediation_confirm", "remediation_reset"]);
|
|
4600
|
+
const isOrdinaryWorkspaceSyncRequest = (input) => input.confirmedLargeDiff !== true && input.resetToCanonical !== true && !(input.requiredAncestorHeads && input.requiredAncestorHeads.length > 0) && !REMEDIATION_TRIGGER_TYPES.has(input.trigger.type);
|
|
4601
|
+
const LOCAL_ACTIVITY_TRIGGER_TYPES = /* @__PURE__ */ new Set([
|
|
4602
|
+
"write",
|
|
4603
|
+
"edit",
|
|
4604
|
+
"shell_inline",
|
|
4605
|
+
"process_terminal",
|
|
4606
|
+
"process_cancel"
|
|
4607
|
+
]);
|
|
4608
|
+
const answerOrdinaryWorkspaceSyncFromLastCycle = (requests) => {
|
|
4609
|
+
if (!workspaceConfigured) return null;
|
|
4610
|
+
return workspaceProjectionLedger.answerFromLastCycle({
|
|
4611
|
+
// A workspace-rooted command carries no project target because it may
|
|
4612
|
+
// have touched any checkout.
|
|
4613
|
+
targetMountIds: requests.map(
|
|
4614
|
+
({ trigger }) => LOCAL_ACTIVITY_TRIGGER_TYPES.has(trigger.type) && trigger.projectId && trigger.branchName ? projectMountId(trigger.projectId, trigger.branchName) : null
|
|
4615
|
+
),
|
|
4616
|
+
mounts: buildWorkspaceMounts(),
|
|
4617
|
+
pendingDeletions: pendingMirrorDeletes.size > 0 || projectWorkspaceState.pendingTreeDeletions.length > 0 || projectWorkspaceState.pendingMirrorRefDeletions.length > 0
|
|
4618
|
+
});
|
|
4619
|
+
};
|
|
4620
|
+
const ordinaryWorkspaceSyncCoalescer = new import_workspace_sync_coalescer.WorkspaceSyncCoalescer({
|
|
4621
|
+
runExclusive: (operation) => workspaceSyncSingleFlight.runExclusive(operation),
|
|
4622
|
+
runBatch: async (requests) => {
|
|
4623
|
+
const startedAtMs = Date.now();
|
|
4624
|
+
const admission = captureWorkspaceSyncAdmission();
|
|
4625
|
+
const staleAttemptIds = /* @__PURE__ */ new Set();
|
|
4626
|
+
const admitted = [];
|
|
4627
|
+
for (const request of requests) {
|
|
4628
|
+
if (workspaceSyncAdmissionSnapshotsMatch(request.admission, admission)) admitted.push(request);
|
|
4629
|
+
else staleAttemptIds.add(request.attemptId);
|
|
4630
|
+
}
|
|
4631
|
+
const leader = admitted[0];
|
|
4632
|
+
if (!leader) return { result: null, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
|
|
4633
|
+
const answered = answerOrdinaryWorkspaceSyncFromLastCycle(admitted);
|
|
4634
|
+
if (answered) return { result: answered, ranCycle: false, startedAtMs, finishedAtMs: Date.now(), prepareMs: 0, staleAttemptIds };
|
|
4635
|
+
const result = await executeWorkspaceSyncCycle({
|
|
4636
|
+
attemptId: leader.attemptId,
|
|
4637
|
+
trigger: leader.trigger,
|
|
4638
|
+
assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, leader.trigger)
|
|
4639
|
+
});
|
|
4640
|
+
return { result, ranCycle: true, startedAtMs, finishedAtMs: Date.now(), prepareMs: lastWorkspaceSyncMirrorObservationMs, staleAttemptIds };
|
|
4641
|
+
}
|
|
4642
|
+
});
|
|
4533
4643
|
const runWorkspaceSync = async (input) => {
|
|
4534
4644
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
4535
4645
|
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
|
-
};
|
|
4646
|
+
const admission = captureWorkspaceSyncAdmission();
|
|
4544
4647
|
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4545
4648
|
const result2 = {
|
|
4546
4649
|
...await failedWorkspaceSyncResult(
|
|
@@ -4566,104 +4669,47 @@ async function startWorker(options) {
|
|
|
4566
4669
|
}
|
|
4567
4670
|
ws.send(JSON.stringify({ type: "workspace_sync_started", attemptId }));
|
|
4568
4671
|
}
|
|
4569
|
-
let
|
|
4570
|
-
let
|
|
4672
|
+
let queueMs = 0;
|
|
4673
|
+
let prepareMs = 0;
|
|
4674
|
+
let synchronizeMs = 0;
|
|
4571
4675
|
workspaceSyncRequestsInFlight += 1;
|
|
4572
4676
|
let result;
|
|
4573
4677
|
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({
|
|
4678
|
+
if (isOrdinaryWorkspaceSyncRequest(input)) {
|
|
4679
|
+
const settlement = await ordinaryWorkspaceSyncCoalescer.request({ attemptId, trigger: input.trigger, admission });
|
|
4680
|
+
queueMs = settlement.startedAtMs - requestedAt;
|
|
4681
|
+
prepareMs = settlement.prepareMs;
|
|
4682
|
+
synchronizeMs = settlement.finishedAtMs - settlement.startedAtMs;
|
|
4683
|
+
result = settlement.staleAttemptIds.has(attemptId) || settlement.result === null ? await failedWorkspaceSyncResult(
|
|
4684
|
+
attemptId,
|
|
4685
|
+
input.trigger,
|
|
4686
|
+
new Error("Workspace synchronization admission changed while the request was queued")
|
|
4687
|
+
) : { ...settlement.result, attemptId, trigger: input.trigger };
|
|
4688
|
+
} else {
|
|
4689
|
+
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4690
|
+
const startedAt = Date.now();
|
|
4691
|
+
queueMs = startedAt - requestedAt;
|
|
4692
|
+
const cycleResult = await executeWorkspaceSyncCycle({
|
|
4591
4693
|
attemptId,
|
|
4592
4694
|
trigger: input.trigger,
|
|
4593
|
-
confirmedLargeDiff: input.confirmedLargeDiff
|
|
4594
|
-
confirmationReason: input.confirmationReason
|
|
4695
|
+
confirmedLargeDiff: input.confirmedLargeDiff,
|
|
4696
|
+
confirmationReason: input.confirmationReason,
|
|
4595
4697
|
resetToCanonical: input.resetToCanonical,
|
|
4596
4698
|
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4597
|
-
assertStillAdmitted
|
|
4598
|
-
});
|
|
4599
|
-
} catch (error) {
|
|
4600
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4601
|
-
const hydrationRecoverable = workspaceSyncFailureHydrationIsSafe({
|
|
4602
|
-
error,
|
|
4603
|
-
resetToCanonical: input.resetToCanonical === true,
|
|
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
|
|
4699
|
+
assertStillAdmitted: () => assertWorkspaceSyncStillAdmitted(admission, input.trigger)
|
|
4638
4700
|
});
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
}
|
|
4645
|
-
return {
|
|
4646
|
-
...await failedWorkspaceSyncResult(attemptId, input.trigger, error),
|
|
4647
|
-
failureReason: "unsafe_hydration",
|
|
4648
|
-
recoveryAttempt: unsafeWorkspaceSyncRecoveryAttempts
|
|
4649
|
-
};
|
|
4650
|
-
}
|
|
4651
|
-
});
|
|
4701
|
+
prepareMs = lastWorkspaceSyncMirrorObservationMs;
|
|
4702
|
+
synchronizeMs = Date.now() - startedAt;
|
|
4703
|
+
return cycleResult;
|
|
4704
|
+
});
|
|
4705
|
+
}
|
|
4652
4706
|
} finally {
|
|
4653
4707
|
workspaceSyncRequestsInFlight -= 1;
|
|
4654
4708
|
}
|
|
4655
4709
|
if (result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
|
|
4656
|
-
const finishedAt = Date.now();
|
|
4657
4710
|
result = {
|
|
4658
4711
|
...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
|
-
}
|
|
4712
|
+
telemetry: { totalMs: Date.now() - requestedAt, queueMs, prepareMs, synchronizeMs }
|
|
4667
4713
|
};
|
|
4668
4714
|
if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result);
|
|
4669
4715
|
sendLifecycleProgress(
|
|
@@ -4933,6 +4979,7 @@ async function startWorker(options) {
|
|
|
4933
4979
|
);
|
|
4934
4980
|
}
|
|
4935
4981
|
workspaceConfigured = false;
|
|
4982
|
+
workspaceProjectionLedger.reset();
|
|
4936
4983
|
const preserveOnlyBranches = message.projects.flatMap(
|
|
4937
4984
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
4938
4985
|
);
|
|
@@ -4942,6 +4989,16 @@ async function startWorker(options) {
|
|
|
4942
4989
|
preserveOnlyBranches
|
|
4943
4990
|
});
|
|
4944
4991
|
}
|
|
4992
|
+
for (const project of message.projects) {
|
|
4993
|
+
for (const branch of project.branches) {
|
|
4994
|
+
const pending2 = projectWorkspaceState.locallyPendingCreatedBranches.find(
|
|
4995
|
+
(candidate) => candidate.projectId === project.projectId && candidate.branchName === branch.branchName
|
|
4996
|
+
);
|
|
4997
|
+
if (pending2 && pending2.branchId === branch.branchId) {
|
|
4998
|
+
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pending2);
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
4945
5002
|
const stillPendingCreatedBranches = new Map(
|
|
4946
5003
|
projectWorkspaceState.locallyPendingCreatedBranches.map((branch) => [
|
|
4947
5004
|
(0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(branch.projectId, branch.branchName),
|
|
@@ -5383,11 +5440,10 @@ async function startWorker(options) {
|
|
|
5383
5440
|
workspaceConfigResetToCanonicalV1: true,
|
|
5384
5441
|
projectBranchDeletionFastAckV1: true,
|
|
5385
5442
|
projectMirrorLeaseV1: true,
|
|
5386
|
-
projectMirrorRefsTokensV1: true
|
|
5443
|
+
projectMirrorRefsTokensV1: true,
|
|
5444
|
+
projectBranchWorkingTreeModeV1: true
|
|
5387
5445
|
},
|
|
5388
|
-
|
|
5389
|
-
artifactRoot,
|
|
5390
|
-
planRoot
|
|
5446
|
+
root: rootDir
|
|
5391
5447
|
}
|
|
5392
5448
|
};
|
|
5393
5449
|
ws.send(JSON.stringify(hello));
|
|
@@ -5422,14 +5478,12 @@ async function startWorker(options) {
|
|
|
5422
5478
|
return;
|
|
5423
5479
|
}
|
|
5424
5480
|
if (message.type === "project_mirror_refs_tokens") {
|
|
5481
|
+
const advertised = (0, import_project_mirror_fetch_policy.parseProjectMirrorRefsTokensMessage)(message);
|
|
5425
5482
|
advertisedProjectMirrorRefsTokens.clear();
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5483
|
+
for (const [projectId, token2] of advertised.projectTokens) advertisedProjectMirrorRefsTokens.set(projectId, token2);
|
|
5484
|
+
advertisedProjectMirrorMountTokens.clear();
|
|
5485
|
+
for (const [projectId, tokens] of advertised.mountTokens) advertisedProjectMirrorMountTokens.set(projectId, tokens);
|
|
5486
|
+
advertisedWorkspaceHead = advertised.workspaceHead;
|
|
5433
5487
|
return;
|
|
5434
5488
|
}
|
|
5435
5489
|
if (message.type === "workspace_config") {
|
|
@@ -5502,13 +5556,21 @@ async function startWorker(options) {
|
|
|
5502
5556
|
});
|
|
5503
5557
|
return;
|
|
5504
5558
|
}
|
|
5559
|
+
sendWorkerMessage(ws, {
|
|
5560
|
+
type: "operation_queued",
|
|
5561
|
+
requestId: message.requestId,
|
|
5562
|
+
queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
|
|
5563
|
+
});
|
|
5505
5564
|
try {
|
|
5565
|
+
if (message.workingTree !== "carry" && message.workingTree !== "clean") {
|
|
5566
|
+
throw new Error('Project branch creation requires workingTree "carry" or "clean"');
|
|
5567
|
+
}
|
|
5506
5568
|
const pendingBranch = {
|
|
5507
5569
|
branchId: message.branchId,
|
|
5508
5570
|
projectId: message.projectId,
|
|
5509
5571
|
branchName: message.targetBranch
|
|
5510
5572
|
};
|
|
5511
|
-
const created = await workspaceSyncSingleFlight.
|
|
5573
|
+
const created = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
5512
5574
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5513
5575
|
throw new Error(
|
|
5514
5576
|
`Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -5549,6 +5611,7 @@ async function startWorker(options) {
|
|
|
5549
5611
|
primaryBranchName: primaryProjectBranch(project),
|
|
5550
5612
|
sourceBranchName: message.sourceBranch,
|
|
5551
5613
|
branchName: message.targetBranch,
|
|
5614
|
+
workingTree: message.workingTree,
|
|
5552
5615
|
pendingRetry: pendingWasRecorded
|
|
5553
5616
|
});
|
|
5554
5617
|
gitBranchCreated = true;
|
|
@@ -5605,9 +5668,14 @@ async function startWorker(options) {
|
|
|
5605
5668
|
});
|
|
5606
5669
|
return;
|
|
5607
5670
|
}
|
|
5671
|
+
sendWorkerMessage(ws, {
|
|
5672
|
+
type: "operation_queued",
|
|
5673
|
+
requestId: message.requestId,
|
|
5674
|
+
queuePosition: workspaceSyncSingleFlight.branchMutationQueuePosition()
|
|
5675
|
+
});
|
|
5608
5676
|
try {
|
|
5609
5677
|
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
5610
|
-
const deletionNeedsSync = await workspaceSyncSingleFlight.
|
|
5678
|
+
const deletionNeedsSync = await workspaceSyncSingleFlight.runBranchMutation(() => {
|
|
5611
5679
|
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5612
5680
|
throw new Error(
|
|
5613
5681
|
`Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
@@ -5638,6 +5706,9 @@ async function startWorker(options) {
|
|
|
5638
5706
|
projectId: message.projectId,
|
|
5639
5707
|
branchName: message.branchName
|
|
5640
5708
|
});
|
|
5709
|
+
if (pendingLocalBranch) {
|
|
5710
|
+
projectWorkspaceState = projectWorkspaceStateStore.clearPendingCreatedBranch(pendingLocalBranch);
|
|
5711
|
+
}
|
|
5641
5712
|
pendingCreatedBranchPublicationNotBefore.delete((0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(message.projectId, message.branchName));
|
|
5642
5713
|
if (pendingMirrorDeletes.has(deletionKey)) return true;
|
|
5643
5714
|
stageProjectBranchDeletion(project, message.branchName, true);
|
|
@@ -5915,7 +5986,7 @@ async function startWorker(options) {
|
|
|
5915
5986
|
ws,
|
|
5916
5987
|
message,
|
|
5917
5988
|
resolvedTarget,
|
|
5918
|
-
|
|
5989
|
+
rootDir,
|
|
5919
5990
|
assertAdmission: assertMessageAdmission,
|
|
5920
5991
|
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
|
|
5921
5992
|
...targetMayMutateVisibleWorkspace(message.target) ? {
|
|
@@ -6013,8 +6084,7 @@ async function startWorker(options) {
|
|
|
6013
6084
|
resolvedTarget,
|
|
6014
6085
|
baseUrl,
|
|
6015
6086
|
token,
|
|
6016
|
-
|
|
6017
|
-
planRoot,
|
|
6087
|
+
rootDir,
|
|
6018
6088
|
setPreparationHeartbeatLease: (active) => {
|
|
6019
6089
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
6020
6090
|
type: "heartbeat_lease",
|
|
@@ -6083,8 +6153,7 @@ async function startWorker(options) {
|
|
|
6083
6153
|
resolvedTarget,
|
|
6084
6154
|
baseUrl,
|
|
6085
6155
|
token,
|
|
6086
|
-
|
|
6087
|
-
planRoot,
|
|
6156
|
+
rootDir,
|
|
6088
6157
|
setPreparationHeartbeatLease: (active) => {
|
|
6089
6158
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
6090
6159
|
type: "heartbeat_lease",
|
|
@@ -6166,8 +6235,7 @@ async function startWorker(options) {
|
|
|
6166
6235
|
resolvedTarget,
|
|
6167
6236
|
baseUrl,
|
|
6168
6237
|
token,
|
|
6169
|
-
|
|
6170
|
-
planRoot,
|
|
6238
|
+
rootDir,
|
|
6171
6239
|
assertAdmission: assertMessageAdmission
|
|
6172
6240
|
});
|
|
6173
6241
|
});
|