@ricsam/r5d-worker 0.0.57 → 0.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/main.cjs +133 -12
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync-fast-path.cjs +108 -0
- package/dist/cjs/workspace-sync.cjs +124 -53
- package/dist/mjs/main.mjs +137 -12
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-sync-fast-path.mjs +81 -0
- package/dist/mjs/workspace-sync.mjs +124 -53
- package/dist/types/workspace-sync-fast-path.d.ts +69 -0
- package/dist/types/workspace-sync.d.ts +13 -1
- package/package.json +1 -1
|
@@ -960,7 +960,7 @@ function hydrateProjectFromShadowOptimistically(input, relativeRoot, visibleRoot
|
|
|
960
960
|
expectedCheckoutSnapshot = checkoutGitSnapshot(visibleRoot);
|
|
961
961
|
return expectedCheckoutSnapshot !== null;
|
|
962
962
|
};
|
|
963
|
-
|
|
963
|
+
const currentGitlinks = new Map(checkoutGitlinks(visibleRoot).map((entry) => [entry.filePath, entry]));
|
|
964
964
|
const changedPaths = [.../* @__PURE__ */ new Set([...preimage.keys(), ...desired.keys()])].filter(
|
|
965
965
|
(filePath) => !hydrationEntriesEqual(preimage.get(filePath), desired.get(filePath))
|
|
966
966
|
);
|
|
@@ -1582,6 +1582,33 @@ function resetUncommittedShadowSnapshot(input) {
|
|
|
1582
1582
|
}
|
|
1583
1583
|
runGit(input, input.shadowRoot, ["clean", "-fd"], "clean interrupted workspace snapshot");
|
|
1584
1584
|
}
|
|
1585
|
+
function activeWorkspaceRebase(input) {
|
|
1586
|
+
for (const name of ["rebase-merge", "rebase-apply"]) {
|
|
1587
|
+
const resolved = runGitResult(input, input.shadowRoot, ["rev-parse", "--git-path", name]);
|
|
1588
|
+
if (resolved.exitCode !== 0 || !resolved.stdout) continue;
|
|
1589
|
+
const gitPath = import_node_path.default.isAbsolute(resolved.stdout) ? resolved.stdout : import_node_path.default.resolve(input.shadowRoot, resolved.stdout);
|
|
1590
|
+
if (import_node_fs.default.existsSync(gitPath)) return name;
|
|
1591
|
+
}
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
function remediationRebaseInProgressResult(input, result) {
|
|
1595
|
+
if (input.trigger.type !== "remediation" && input.trigger.type !== "remediation_confirm") return null;
|
|
1596
|
+
const rebaseKind = activeWorkspaceRebase(input);
|
|
1597
|
+
if (!rebaseKind) return null;
|
|
1598
|
+
const conflictPaths = runGitResult(input, input.shadowRoot, ["diff", "--name-only", "--diff-filter=U", "-z"]).stdout.split("\0").filter(Boolean).sort();
|
|
1599
|
+
const status = reportedStatus(gitStatus(input));
|
|
1600
|
+
return {
|
|
1601
|
+
...result,
|
|
1602
|
+
outcome: "conflict_blocked",
|
|
1603
|
+
expectedHead: revParse(input, `origin/${WORKSPACE_BRANCH}`),
|
|
1604
|
+
candidateHead: revParse(input, "ORIG_HEAD") ?? revParse(input, "HEAD") ?? void 0,
|
|
1605
|
+
rebaseCount: 1,
|
|
1606
|
+
gitStatus: status,
|
|
1607
|
+
affectedProjects: affectedProjects(conflictPaths),
|
|
1608
|
+
affectedPaths: reportedPaths(conflictPaths),
|
|
1609
|
+
error: `Workspace conflict remediation has an in-progress ${rebaseKind}; the index and worktree were preserved until the resolver completes or aborts it.`
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1585
1612
|
function fastForwardCleanShadowForNewCheckouts(input) {
|
|
1586
1613
|
if (!input.newVisibleCheckouts?.length) return null;
|
|
1587
1614
|
runGit(input, input.shadowRoot, ["add", "-A"], "stage existing workspace changes before checkout hydration");
|
|
@@ -1801,17 +1828,66 @@ function convergeRedundantInboundCandidate(input, result) {
|
|
|
1801
1828
|
affectedPaths: []
|
|
1802
1829
|
};
|
|
1803
1830
|
}
|
|
1831
|
+
function preserveWorkspaceConflictCandidate(input, observed, options) {
|
|
1832
|
+
try {
|
|
1833
|
+
if (!options.baseRevision) throw new Error("the preserved workspace does not have an old-base commit");
|
|
1834
|
+
runGit(input, input.shadowRoot, ["reset", "--hard", options.baseRevision], "restore old-base workspace conflict candidate");
|
|
1835
|
+
runGit(input, input.shadowRoot, ["clean", "-fd"], "clean old-base workspace conflict candidate");
|
|
1836
|
+
if (!options.preserveShadowCandidate) {
|
|
1837
|
+
const projection = mirrorVisibleWorkspaceToShadow(input);
|
|
1838
|
+
restoreUnscopedCanonicalCheckoutSubtrees(input);
|
|
1839
|
+
const stagePathspecs = projection.opaqueRoots.flatMap((root) => [`:(exclude,literal)${root}`]);
|
|
1840
|
+
runGit(input, input.shadowRoot, ["add", "-A", "--", ".", ...stagePathspecs], "stage preserved workspace conflict candidate");
|
|
1841
|
+
forceStageCanonicalCheckoutFiles(input, projection.entries, projection.opaqueRoots);
|
|
1842
|
+
if (stagedPaths(input).length > 0) {
|
|
1843
|
+
runGit(input, input.shadowRoot, ["commit", "-m", commitMessage(input)], "commit preserved workspace conflict candidate");
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
const candidateHead = revParse(input, "HEAD");
|
|
1847
|
+
if (!candidateHead) throw new Error("the preserved workspace conflict candidate does not have a commit");
|
|
1848
|
+
assertUnscopedCanonicalCheckoutSubtreesMatchRemote(input, candidateHead);
|
|
1849
|
+
const upload = uploadWorkspaceCandidate(input, candidateHead);
|
|
1850
|
+
if (upload.exitCode !== 0) {
|
|
1851
|
+
throw new Error(upload.stderr || upload.stdout || "workspace conflict candidate upload failed");
|
|
1852
|
+
}
|
|
1853
|
+
const conflictPaths = [...new Set(options.conflictPaths)].sort();
|
|
1854
|
+
return {
|
|
1855
|
+
...observed,
|
|
1856
|
+
outcome: "conflict_blocked",
|
|
1857
|
+
expectedHead: options.expectedHead,
|
|
1858
|
+
candidateHead,
|
|
1859
|
+
quarantineRef: input.quarantineRef,
|
|
1860
|
+
...options.publishedHead ? { publishedHead: options.publishedHead } : {},
|
|
1861
|
+
rebaseCount: options.rebaseCount ?? 0,
|
|
1862
|
+
affectedProjects: affectedProjects(conflictPaths),
|
|
1863
|
+
affectedPaths: reportedPaths(conflictPaths),
|
|
1864
|
+
error: options.error,
|
|
1865
|
+
gitStatus: gitStatus(input)
|
|
1866
|
+
};
|
|
1867
|
+
} catch (error) {
|
|
1868
|
+
return {
|
|
1869
|
+
...observed,
|
|
1870
|
+
outcome: "failed",
|
|
1871
|
+
rebaseCount: options.rebaseCount ?? 0,
|
|
1872
|
+
affectedProjects: affectedProjects(options.conflictPaths),
|
|
1873
|
+
affectedPaths: reportedPaths(options.conflictPaths),
|
|
1874
|
+
error: `Workspace synchronization found a conflict but could not preserve a durable candidate: ${error instanceof Error ? error.message : String(error)}`,
|
|
1875
|
+
gitStatus: gitStatus(input)
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1804
1879
|
async function synchronizeWorkspace(rawInput) {
|
|
1805
1880
|
let input = { ...rawInput, attemptId: rawInput.attemptId ?? crypto.randomUUID() };
|
|
1806
1881
|
try {
|
|
1807
1882
|
input = { ...input, projects: workspaceProjectsForSync(input.projects, input.trigger) };
|
|
1883
|
+
const isCanonicalRemediationSync = input.skipVisibleMirror === true && (input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm");
|
|
1808
1884
|
let mirroredGitlinkProjection = { entries: [], opaqueRoots: [], visiblePreimages: /* @__PURE__ */ new Map() };
|
|
1809
1885
|
let fastForwardedFromHead = null;
|
|
1810
1886
|
const shadowWasCreated = ensureShadowWorkspace(input);
|
|
1811
1887
|
let hydrationPreimages = input.skipVisibleMirror ? hydrationPreimagesFromShadowRevision(input, shadowWasCreated ? null : revParse(input, "HEAD")) : /* @__PURE__ */ new Map();
|
|
1812
1888
|
const remoteHeadAtStart = revParse(input, `origin/${WORKSPACE_BRANCH}`);
|
|
1813
1889
|
const result = baseResult(input, remoteHeadAtStart);
|
|
1814
|
-
const inboundConflictsAtStart = input.skipVisibleMirror && remoteHeadAtStart ? visibleHydrationConflictPaths(input, hydrationPreimages, `origin/${WORKSPACE_BRANCH}`) : [];
|
|
1890
|
+
const inboundConflictsAtStart = input.skipVisibleMirror && !isCanonicalRemediationSync && remoteHeadAtStart ? visibleHydrationConflictPaths(input, hydrationPreimages, `origin/${WORKSPACE_BRANCH}`) : [];
|
|
1815
1891
|
if (input.resetToCanonical) {
|
|
1816
1892
|
if (input.trigger.canonicalCheckoutOnly) {
|
|
1817
1893
|
throw new Error("A canonical-checkout-only synchronization cannot reset the authoritative resolver checkout");
|
|
@@ -1825,6 +1901,8 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1825
1901
|
gitStatus: gitStatus(input)
|
|
1826
1902
|
};
|
|
1827
1903
|
}
|
|
1904
|
+
const inProgressRemediation = remediationRebaseInProgressResult(input, result);
|
|
1905
|
+
if (inProgressRemediation) return inProgressRemediation;
|
|
1828
1906
|
if (!input.skipVisibleMirror) resetUncommittedShadowSnapshot(input);
|
|
1829
1907
|
restoreUnscopedCanonicalCheckoutSubtrees(input);
|
|
1830
1908
|
if (!input.skipVisibleMirror) {
|
|
@@ -1880,15 +1958,12 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1880
1958
|
const hasUnpushedCommit = candidateHead ? !hasRemoteMain || !tryGit(input, input.shadowRoot, ["merge-base", "--is-ancestor", candidateHead, `origin/${WORKSPACE_BRANCH}`]) : false;
|
|
1881
1959
|
if (hasUnpushedCommit && inboundConflictsAtStart.length > 0) {
|
|
1882
1960
|
const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...inboundConflictsAtStart])].sort();
|
|
1883
|
-
return {
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
error: "Inbound workspace update overlaps local visible changes; the old-base candidate was preserved for reconciliation.",
|
|
1890
|
-
gitStatus: gitStatus(input)
|
|
1891
|
-
};
|
|
1961
|
+
return preserveWorkspaceConflictCandidate(input, observed, {
|
|
1962
|
+
baseRevision: candidateHead,
|
|
1963
|
+
expectedHead: revParse(input, `origin/${WORKSPACE_BRANCH}`),
|
|
1964
|
+
conflictPaths,
|
|
1965
|
+
error: "Inbound workspace update overlaps local visible changes; the old-base candidate was preserved for reconciliation."
|
|
1966
|
+
});
|
|
1892
1967
|
}
|
|
1893
1968
|
if (!hasUnpushedCommit) {
|
|
1894
1969
|
const localHead = revParse(input, "HEAD");
|
|
@@ -1897,22 +1972,21 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1897
1972
|
runGit(input, input.shadowRoot, ["reset", "--hard", `origin/${WORKSPACE_BRANCH}`], "update workspace from canonical state");
|
|
1898
1973
|
runGit(input, input.shadowRoot, ["clean", "-fd"], "clean updated workspace");
|
|
1899
1974
|
assertPublishedCanonicalCheckoutTree(input, currentRemoteHead, sampledCanonicalCheckoutTreeHash);
|
|
1900
|
-
|
|
1901
|
-
|
|
1975
|
+
if (!isCanonicalRemediationSync) {
|
|
1976
|
+
mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
|
|
1977
|
+
}
|
|
1978
|
+
const inboundConflicts = input.trigger.canonicalCheckoutOnly || isCanonicalRemediationSync ? [] : localHead ? hydrationConflictPaths(input, hydrationPreimages, localHead, currentRemoteHead) : visibleHydrationConflictPaths(input, hydrationPreimages, currentRemoteHead);
|
|
1902
1979
|
if (inboundConflicts.length > 0 && localHead) {
|
|
1903
1980
|
runGit(input, input.shadowRoot, ["reset", "--hard", localHead], "preserve old workspace base after inbound conflict");
|
|
1904
1981
|
runGit(input, input.shadowRoot, ["clean", "-fd"], "clean preserved old workspace base");
|
|
1905
1982
|
const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...inboundConflicts])].sort();
|
|
1906
|
-
return {
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
candidateHead: candidateHead ?? void 0,
|
|
1983
|
+
return preserveWorkspaceConflictCandidate(input, observed, {
|
|
1984
|
+
baseRevision: localHead,
|
|
1985
|
+
expectedHead: currentRemoteHead,
|
|
1910
1986
|
publishedHead: currentRemoteHead,
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
gitStatus: gitStatus(input)
|
|
1915
|
-
};
|
|
1987
|
+
conflictPaths,
|
|
1988
|
+
error: "Inbound workspace update overlaps local visible changes; disjoint updates were hydrated and the old base was preserved for reconciliation."
|
|
1989
|
+
});
|
|
1916
1990
|
}
|
|
1917
1991
|
return {
|
|
1918
1992
|
...observed,
|
|
@@ -1925,8 +1999,10 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1925
1999
|
const authoritativeHead = currentRemoteHead ?? localHead;
|
|
1926
2000
|
if (authoritativeHead) {
|
|
1927
2001
|
assertPublishedCanonicalCheckoutTree(input, authoritativeHead, sampledCanonicalCheckoutTreeHash);
|
|
1928
|
-
|
|
1929
|
-
|
|
2002
|
+
if (!isCanonicalRemediationSync) {
|
|
2003
|
+
mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
|
|
2004
|
+
}
|
|
2005
|
+
const hydrationConflicts2 = input.trigger.canonicalCheckoutOnly || isCanonicalRemediationSync ? [] : fastForwardedFromHead ? hydrationConflictPaths(input, hydrationPreimages, fastForwardedFromHead, authoritativeHead) : visibleHydrationConflictPaths(input, hydrationPreimages, authoritativeHead);
|
|
1930
2006
|
if (hydrationConflicts2.length > 0 && fastForwardedFromHead) {
|
|
1931
2007
|
runGit(
|
|
1932
2008
|
input,
|
|
@@ -1935,16 +2011,13 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1935
2011
|
"preserve old workspace base after new-checkout fast-forward conflict"
|
|
1936
2012
|
);
|
|
1937
2013
|
runGit(input, input.shadowRoot, ["clean", "-fd"], "clean preserved new-checkout workspace base");
|
|
1938
|
-
return {
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
candidateHead: candidateHead ?? void 0,
|
|
2014
|
+
return preserveWorkspaceConflictCandidate(input, observed, {
|
|
2015
|
+
baseRevision: fastForwardedFromHead,
|
|
2016
|
+
expectedHead: currentRemoteHead,
|
|
1942
2017
|
publishedHead: currentRemoteHead ?? void 0,
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
gitStatus: gitStatus(input)
|
|
1947
|
-
};
|
|
2018
|
+
conflictPaths: hydrationConflicts2,
|
|
2019
|
+
error: "Workspace fast-forward for a new checkout overlaps local visible changes; compatible updates were hydrated and the old base was preserved for reconciliation."
|
|
2020
|
+
});
|
|
1948
2021
|
}
|
|
1949
2022
|
}
|
|
1950
2023
|
return {
|
|
@@ -1964,17 +2037,14 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1964
2037
|
const conflicted = runGitResult(input, input.shadowRoot, ["diff", "--name-only", "--diff-filter=U", "-z"]);
|
|
1965
2038
|
const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...conflicted.stdout.split("\0").filter(Boolean)])].sort();
|
|
1966
2039
|
runGit(input, input.shadowRoot, ["rebase", "--abort"], "preserve workspace candidate after rebase conflict");
|
|
1967
|
-
return {
|
|
1968
|
-
|
|
1969
|
-
outcome: "failed",
|
|
2040
|
+
return preserveWorkspaceConflictCandidate(input, observed, {
|
|
2041
|
+
baseRevision: candidateHead,
|
|
1970
2042
|
expectedHead,
|
|
1971
|
-
candidateHead: candidateHead ?? void 0,
|
|
1972
2043
|
rebaseCount,
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
error: `Workspace candidate conflicts with canonical head ${expectedHead}; local changes were preserved for reconciliation
|
|
1976
|
-
|
|
1977
|
-
};
|
|
2044
|
+
preserveShadowCandidate: isCanonicalRemediationSync,
|
|
2045
|
+
conflictPaths,
|
|
2046
|
+
error: `Workspace candidate conflicts with canonical head ${expectedHead}; local changes were preserved for reconciliation.`
|
|
2047
|
+
});
|
|
1978
2048
|
}
|
|
1979
2049
|
candidateHead = revParse(input, "HEAD");
|
|
1980
2050
|
assertPublishedCanonicalCheckoutTree(input, "HEAD", sampledCanonicalCheckoutTreeHash);
|
|
@@ -1990,8 +2060,10 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
1990
2060
|
};
|
|
1991
2061
|
}
|
|
1992
2062
|
assertPublishedCanonicalCheckoutTree(input, candidateHead, sampledCanonicalCheckoutTreeHash);
|
|
1993
|
-
|
|
1994
|
-
|
|
2063
|
+
if (!isCanonicalRemediationSync) {
|
|
2064
|
+
mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
|
|
2065
|
+
}
|
|
2066
|
+
const hydrationConflicts = input.trigger.canonicalCheckoutOnly || isCanonicalRemediationSync ? [] : candidateHeadBeforeRebase ? hydrationConflictPaths(input, hydrationPreimages, candidateHeadBeforeRebase, "HEAD") : visibleHydrationConflictPaths(input, hydrationPreimages, "HEAD");
|
|
1995
2067
|
if (hydrationConflicts.length > 0 && candidateHeadBeforeRebase) {
|
|
1996
2068
|
runGit(
|
|
1997
2069
|
input,
|
|
@@ -2001,17 +2073,13 @@ async function synchronizeWorkspace(rawInput) {
|
|
|
2001
2073
|
);
|
|
2002
2074
|
runGit(input, input.shadowRoot, ["clean", "-fd"], "clean old-base workspace candidate");
|
|
2003
2075
|
const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...hydrationConflicts])].sort();
|
|
2004
|
-
return {
|
|
2005
|
-
|
|
2006
|
-
outcome: "failed",
|
|
2076
|
+
return preserveWorkspaceConflictCandidate(input, observed, {
|
|
2077
|
+
baseRevision: candidateHeadBeforeRebase,
|
|
2007
2078
|
expectedHead,
|
|
2008
|
-
candidateHead,
|
|
2009
2079
|
rebaseCount,
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
gitStatus: gitStatus(input)
|
|
2014
|
-
};
|
|
2080
|
+
conflictPaths,
|
|
2081
|
+
error: "Workspace changed while its candidate was reconciling; visible changes and the old-base candidate were preserved."
|
|
2082
|
+
});
|
|
2015
2083
|
}
|
|
2016
2084
|
const upload = uploadWorkspaceCandidate(input, candidateHead);
|
|
2017
2085
|
if (upload.exitCode !== 0) {
|
|
@@ -2096,6 +2164,9 @@ class WorkspaceSyncSingleFlight {
|
|
|
2096
2164
|
fingerprintPrepared(prepare) {
|
|
2097
2165
|
return this.enqueue(() => calculateWorkspaceDiffFingerprint(prepare()));
|
|
2098
2166
|
}
|
|
2167
|
+
runExclusive(operation) {
|
|
2168
|
+
return this.enqueue(operation);
|
|
2169
|
+
}
|
|
2099
2170
|
runMutation(operation) {
|
|
2100
2171
|
return this.mutationGate.runMutation(operation);
|
|
2101
2172
|
}
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -15,6 +15,11 @@ import { terminateProcessTree } from "./process-tree.mjs";
|
|
|
15
15
|
import { openWorkerPortForwardRelay } from "./port-forward-client.mjs";
|
|
16
16
|
import { applyWorkspaceIncidentUpdate, releasePendingWorkspaceHead } from "./workspace-incident-state.mjs";
|
|
17
17
|
import { acquireWorkspaceCommandMutation, runWorkspaceCommand } from "./workspace-command-sync-policy.mjs";
|
|
18
|
+
import {
|
|
19
|
+
RecentWorkspaceSyncProofCache,
|
|
20
|
+
readShadowWorkspaceCanonicalHead,
|
|
21
|
+
workspaceSyncScopeKey
|
|
22
|
+
} from "./workspace-sync-fast-path.mjs";
|
|
18
23
|
import {
|
|
19
24
|
isRetryableWorkerServerStatus,
|
|
20
25
|
superviseWorkerRuntime,
|
|
@@ -31,6 +36,9 @@ import {
|
|
|
31
36
|
synchronizeWorkspace,
|
|
32
37
|
workspaceProjectsForSync
|
|
33
38
|
} from "./workspace-sync.mjs";
|
|
39
|
+
function workspaceSyncFastPathTelemetry(decision) {
|
|
40
|
+
return decision.hit ? { hit: true, reason: decision.reason, proofAgeMs: decision.proofAgeMs } : { hit: false, reason: decision.reason };
|
|
41
|
+
}
|
|
34
42
|
class WorkerServerUnavailableError extends Error {
|
|
35
43
|
name = "WorkerServerUnavailableError";
|
|
36
44
|
}
|
|
@@ -2369,6 +2377,7 @@ async function startWorker(options) {
|
|
|
2369
2377
|
let sessionArtifactSyncRequestsInFlight = 0;
|
|
2370
2378
|
let cliUpdateInProgress = false;
|
|
2371
2379
|
let reloadAfterClose = false;
|
|
2380
|
+
const recentWorkspaceSyncProof = new RecentWorkspaceSyncProofCache();
|
|
2372
2381
|
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2373
2382
|
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2374
2383
|
const projects = workspaceProjectsForSync([...manifestByProjectId.values()], trigger);
|
|
@@ -2460,19 +2469,64 @@ async function startWorker(options) {
|
|
|
2460
2469
|
}
|
|
2461
2470
|
return created;
|
|
2462
2471
|
};
|
|
2463
|
-
const runRequestedWorkspaceSync = async (authority, trigger, overrides = {}) => {
|
|
2472
|
+
const runRequestedWorkspaceSync = async (authority, trigger, overrides = {}, authoritativeCanonicalHead, allowRecentNoChangeFastPath) => {
|
|
2473
|
+
const requestedAt = Date.now();
|
|
2474
|
+
let queueEnteredAt = requestedAt;
|
|
2475
|
+
let prepareStartedAt = requestedAt;
|
|
2476
|
+
let synchronizeStartedAt = requestedAt;
|
|
2477
|
+
let synchronizeFinishedAt = requestedAt;
|
|
2478
|
+
let fastPathDecision = { hit: false, reason: "no_recent_proof" };
|
|
2464
2479
|
workspaceSyncRequestsInFlight += 1;
|
|
2465
2480
|
try {
|
|
2466
2481
|
let pendingTargets = [];
|
|
2467
|
-
const result = await workspaceSyncSingleFlight.
|
|
2482
|
+
const result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2483
|
+
queueEnteredAt = Date.now();
|
|
2468
2484
|
const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
|
|
2469
2485
|
const syncProjects = workspaceProjectsForSync([...manifestByProjectId.values()], trigger);
|
|
2486
|
+
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2487
|
+
const scopeKey = workspaceSyncScopeKey(workspaceRemoteUrl, syncProjects);
|
|
2470
2488
|
const allowedCheckoutKeys = new Set(
|
|
2471
2489
|
syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
|
|
2472
2490
|
);
|
|
2473
2491
|
const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
|
|
2474
2492
|
(target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2475
2493
|
);
|
|
2494
|
+
const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
|
|
2495
|
+
fastPathDecision = recentWorkspaceSyncProof.evaluate({
|
|
2496
|
+
trigger,
|
|
2497
|
+
allowRecentNoChangeFastPath,
|
|
2498
|
+
canonicalHead: authoritativeCanonicalHead,
|
|
2499
|
+
scopeKey,
|
|
2500
|
+
nowMs: Date.now(),
|
|
2501
|
+
skipVisibleMirror: overrides.skipVisibleMirror,
|
|
2502
|
+
resetToCanonical: overrides.resetToCanonical,
|
|
2503
|
+
incidentActive: activeWorkspaceIncidentId !== null,
|
|
2504
|
+
workerBusy,
|
|
2505
|
+
pendingCheckouts: scopedPendingTargets.length > 0 || Boolean(overrides.newVisibleCheckouts?.length)
|
|
2506
|
+
});
|
|
2507
|
+
if (fastPathDecision.hit) {
|
|
2508
|
+
prepareStartedAt = queueEnteredAt;
|
|
2509
|
+
synchronizeStartedAt = queueEnteredAt;
|
|
2510
|
+
synchronizeFinishedAt = queueEnteredAt;
|
|
2511
|
+
return {
|
|
2512
|
+
type: "workspace_sync",
|
|
2513
|
+
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2514
|
+
workerLabel: label,
|
|
2515
|
+
trigger,
|
|
2516
|
+
outcome: "no_change",
|
|
2517
|
+
startingHead: fastPathDecision.canonicalHead,
|
|
2518
|
+
expectedHead: fastPathDecision.canonicalHead,
|
|
2519
|
+
publishedHead: fastPathDecision.canonicalHead,
|
|
2520
|
+
rebaseCount: 0,
|
|
2521
|
+
diffSizeBytes: 0,
|
|
2522
|
+
gitStatus: "",
|
|
2523
|
+
affectedProjects: [],
|
|
2524
|
+
affectedPaths: [],
|
|
2525
|
+
discardedPaths: [],
|
|
2526
|
+
localChangesDiscarded: false
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
prepareStartedAt = Date.now();
|
|
2476
2530
|
let checkoutTargets = [];
|
|
2477
2531
|
if (shouldEnsureCheckouts) {
|
|
2478
2532
|
checkoutTargets = workspaceSyncCheckoutTargets({
|
|
@@ -2492,21 +2546,53 @@ async function startWorker(options) {
|
|
|
2492
2546
|
if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
|
|
2493
2547
|
}
|
|
2494
2548
|
const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
|
|
2495
|
-
|
|
2549
|
+
const input = workspaceSyncInput(trigger, {
|
|
2496
2550
|
...overrides,
|
|
2497
2551
|
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2498
2552
|
});
|
|
2553
|
+
synchronizeStartedAt = Date.now();
|
|
2554
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2555
|
+
const observationGeneration = recentWorkspaceSyncProof.beginObservation();
|
|
2556
|
+
const workerWasBusyDuringObservation = workerBusy;
|
|
2557
|
+
let synchronized;
|
|
2558
|
+
try {
|
|
2559
|
+
synchronized = await synchronizeWorkspace(input);
|
|
2560
|
+
} finally {
|
|
2561
|
+
synchronizeFinishedAt = Date.now();
|
|
2562
|
+
}
|
|
2563
|
+
if ((synchronized.outcome === "no_change" || synchronized.outcome === "updated") && pendingTargets.length === 0) {
|
|
2564
|
+
recentWorkspaceSyncProof.recordStable({
|
|
2565
|
+
observationGeneration,
|
|
2566
|
+
canonicalHead: synchronized.publishedHead ?? null,
|
|
2567
|
+
scopeKey,
|
|
2568
|
+
observedAtMs: synchronizeFinishedAt,
|
|
2569
|
+
workerBusy: workerWasBusyDuringObservation || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress || activeWorkspaceIncidentId !== null
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
return synchronized;
|
|
2499
2573
|
});
|
|
2500
|
-
|
|
2574
|
+
const completedAt = Date.now();
|
|
2575
|
+
const resultWithTelemetry = {
|
|
2576
|
+
...result,
|
|
2577
|
+
telemetry: {
|
|
2578
|
+
totalMs: completedAt - requestedAt,
|
|
2579
|
+
queueMs: queueEnteredAt - requestedAt,
|
|
2580
|
+
prepareMs: synchronizeStartedAt - prepareStartedAt,
|
|
2581
|
+
synchronizeMs: synchronizeFinishedAt - synchronizeStartedAt,
|
|
2582
|
+
fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
if (resultWithTelemetry.outcome === "no_change" || resultWithTelemetry.outcome === "published" || resultWithTelemetry.outcome === "updated" || resultWithTelemetry.outcome === "conflict_reset") {
|
|
2501
2586
|
for (const target of pendingTargets) {
|
|
2502
2587
|
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2503
2588
|
if (pendingManifestCheckouts.get(key) === target) pendingManifestCheckouts.delete(key);
|
|
2504
2589
|
}
|
|
2505
2590
|
}
|
|
2506
2591
|
previousPeriodicFingerprint = null;
|
|
2507
|
-
sendWorkspaceSyncResult(authority,
|
|
2508
|
-
return
|
|
2592
|
+
sendWorkspaceSyncResult(authority, resultWithTelemetry);
|
|
2593
|
+
return resultWithTelemetry;
|
|
2509
2594
|
} catch (error) {
|
|
2595
|
+
const completedAt = Date.now();
|
|
2510
2596
|
const result = {
|
|
2511
2597
|
type: "workspace_sync",
|
|
2512
2598
|
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
@@ -2521,6 +2607,13 @@ async function startWorker(options) {
|
|
|
2521
2607
|
affectedPaths: [],
|
|
2522
2608
|
discardedPaths: [],
|
|
2523
2609
|
localChangesDiscarded: false,
|
|
2610
|
+
telemetry: {
|
|
2611
|
+
totalMs: completedAt - requestedAt,
|
|
2612
|
+
queueMs: queueEnteredAt - requestedAt,
|
|
2613
|
+
prepareMs: Math.max(0, synchronizeStartedAt - prepareStartedAt),
|
|
2614
|
+
synchronizeMs: Math.max(0, synchronizeFinishedAt - synchronizeStartedAt),
|
|
2615
|
+
fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
|
|
2616
|
+
},
|
|
2524
2617
|
error: error instanceof Error ? error.message : String(error)
|
|
2525
2618
|
};
|
|
2526
2619
|
previousPeriodicFingerprint = null;
|
|
@@ -2596,6 +2689,7 @@ async function startWorker(options) {
|
|
|
2596
2689
|
return;
|
|
2597
2690
|
}
|
|
2598
2691
|
if (message.type === "workspace_manifest") {
|
|
2692
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2599
2693
|
await workspaceSyncSingleFlight.runMutation(() => {
|
|
2600
2694
|
configureGitHubAuth(message.githubCredential);
|
|
2601
2695
|
visibleGitIdentity = message.gitIdentity;
|
|
@@ -2640,6 +2734,7 @@ async function startWorker(options) {
|
|
|
2640
2734
|
(target) => genericCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2641
2735
|
);
|
|
2642
2736
|
if (hasPendingGenericCheckout) {
|
|
2737
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2643
2738
|
previousPeriodicFingerprint = null;
|
|
2644
2739
|
requestWorkspaceSync({
|
|
2645
2740
|
type: "periodic",
|
|
@@ -2647,17 +2742,36 @@ async function startWorker(options) {
|
|
|
2647
2742
|
});
|
|
2648
2743
|
return;
|
|
2649
2744
|
}
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2745
|
+
const observation = await workspaceSyncSingleFlight.runExclusive(() => {
|
|
2746
|
+
const observationGeneration = recentWorkspaceSyncProof.beginObservation();
|
|
2747
|
+
const input = workspaceSyncInput({ type: "periodic" });
|
|
2748
|
+
const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
|
|
2749
|
+
return {
|
|
2750
|
+
observationGeneration,
|
|
2751
|
+
scopeKey: workspaceSyncScopeKey(input.remoteUrl, input.projects),
|
|
2752
|
+
fingerprint: calculateWorkspaceDiffFingerprint(input),
|
|
2753
|
+
canonicalHead: readShadowWorkspaceCanonicalHead(workspaceShadowRoot),
|
|
2754
|
+
workerBusy
|
|
2755
|
+
};
|
|
2756
|
+
});
|
|
2757
|
+
if (observation.fingerprint === emptyFingerprint) {
|
|
2758
|
+
recentWorkspaceSyncProof.recordStable({
|
|
2759
|
+
observationGeneration: observation.observationGeneration,
|
|
2760
|
+
canonicalHead: observation.canonicalHead,
|
|
2761
|
+
scopeKey: observation.scopeKey,
|
|
2762
|
+
observedAtMs: Date.now(),
|
|
2763
|
+
workerBusy: observation.workerBusy || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress
|
|
2764
|
+
});
|
|
2652
2765
|
previousPeriodicFingerprint = null;
|
|
2653
2766
|
return;
|
|
2654
2767
|
}
|
|
2655
|
-
|
|
2656
|
-
|
|
2768
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2769
|
+
if (previousPeriodicFingerprint !== observation.fingerprint) {
|
|
2770
|
+
previousPeriodicFingerprint = observation.fingerprint;
|
|
2657
2771
|
return;
|
|
2658
2772
|
}
|
|
2659
2773
|
previousPeriodicFingerprint = null;
|
|
2660
|
-
requestWorkspaceSync({ type: "periodic" }, fingerprint);
|
|
2774
|
+
requestWorkspaceSync({ type: "periodic" }, observation.fingerprint);
|
|
2661
2775
|
})().catch((error) => {
|
|
2662
2776
|
process.stderr.write(
|
|
2663
2777
|
`[r5d-worker] periodic workspace synchronization failed: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -2688,7 +2802,9 @@ async function startWorker(options) {
|
|
|
2688
2802
|
confirmationReason: message.confirmationReason,
|
|
2689
2803
|
resetToCanonical: message.resetToCanonical,
|
|
2690
2804
|
skipVisibleMirror: message.skipVisibleMirror
|
|
2691
|
-
}
|
|
2805
|
+
},
|
|
2806
|
+
message.canonicalHead,
|
|
2807
|
+
message.allowRecentNoChangeFastPath
|
|
2692
2808
|
);
|
|
2693
2809
|
return;
|
|
2694
2810
|
}
|
|
@@ -2719,6 +2835,7 @@ async function startWorker(options) {
|
|
|
2719
2835
|
return;
|
|
2720
2836
|
}
|
|
2721
2837
|
if (message.type === "workspace_incident_updated") {
|
|
2838
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2722
2839
|
const previousIncidentId = activeWorkspaceIncidentId;
|
|
2723
2840
|
activeWorkspaceIncidentId = applyWorkspaceIncidentUpdate(activeWorkspaceIncidentId, message);
|
|
2724
2841
|
previousPeriodicFingerprint = null;
|
|
@@ -2765,6 +2882,7 @@ async function startWorker(options) {
|
|
|
2765
2882
|
return;
|
|
2766
2883
|
}
|
|
2767
2884
|
cliUpdateInProgress = true;
|
|
2885
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2768
2886
|
try {
|
|
2769
2887
|
const result = await installCliUpdate(message);
|
|
2770
2888
|
sendWorkerMessage(ws, {
|
|
@@ -2827,6 +2945,7 @@ async function startWorker(options) {
|
|
|
2827
2945
|
});
|
|
2828
2946
|
return;
|
|
2829
2947
|
}
|
|
2948
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2830
2949
|
const releaseWorkspaceMutation = await acquireWorkspaceCommandMutation(message.target, workspaceSyncSingleFlight);
|
|
2831
2950
|
let leaseTransferred = false;
|
|
2832
2951
|
try {
|
|
@@ -2854,6 +2973,7 @@ async function startWorker(options) {
|
|
|
2854
2973
|
return;
|
|
2855
2974
|
}
|
|
2856
2975
|
if (message.type === "pty_input") {
|
|
2976
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2857
2977
|
writePty(ws, message);
|
|
2858
2978
|
return;
|
|
2859
2979
|
}
|
|
@@ -2862,6 +2982,7 @@ async function startWorker(options) {
|
|
|
2862
2982
|
return;
|
|
2863
2983
|
}
|
|
2864
2984
|
if (message.type === "pty_close") {
|
|
2985
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2865
2986
|
closePty(message);
|
|
2866
2987
|
return;
|
|
2867
2988
|
}
|
|
@@ -2876,6 +2997,7 @@ async function startWorker(options) {
|
|
|
2876
2997
|
});
|
|
2877
2998
|
return;
|
|
2878
2999
|
}
|
|
3000
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2879
3001
|
let result;
|
|
2880
3002
|
try {
|
|
2881
3003
|
const runCommand = async () => {
|
|
@@ -2908,6 +3030,7 @@ async function startWorker(options) {
|
|
|
2908
3030
|
});
|
|
2909
3031
|
return;
|
|
2910
3032
|
}
|
|
3033
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2911
3034
|
sendWorkerMessage(ws, {
|
|
2912
3035
|
type: "exec_accepted",
|
|
2913
3036
|
requestId: message.requestId,
|
|
@@ -2939,6 +3062,7 @@ async function startWorker(options) {
|
|
|
2939
3062
|
return;
|
|
2940
3063
|
}
|
|
2941
3064
|
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
|
|
3065
|
+
if (message.type === "write" || message.type === "edit") recentWorkspaceSyncProof.invalidate();
|
|
2942
3066
|
try {
|
|
2943
3067
|
const result = await workspaceSyncSingleFlight.runMutation(async () => {
|
|
2944
3068
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
@@ -2987,6 +3111,7 @@ async function startWorker(options) {
|
|
|
2987
3111
|
});
|
|
2988
3112
|
});
|
|
2989
3113
|
ws.addEventListener("close", (event) => {
|
|
3114
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2990
3115
|
stopHeartbeatWatchdog();
|
|
2991
3116
|
if (periodicWorkspaceScan) {
|
|
2992
3117
|
clearInterval(periodicWorkspaceScan);
|
package/dist/mjs/package.json
CHANGED