rivet-design 0.17.2 → 0.17.4
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 +1 -1
- package/README.npm.md +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.d.ts +17 -22
- package/dist/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.js +126 -214
- package/dist/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +0 -1
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.js +2 -2
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/agent-variants/contracts.d.ts +127 -127
- package/dist/agent-variants/contracts.d.ts.map +1 -1
- package/dist/agent-variants/contracts.js +2 -2
- package/dist/agent-variants/contracts.js.map +1 -1
- package/dist/cli/browser.d.ts +19 -0
- package/dist/cli/browser.d.ts.map +1 -0
- package/dist/cli/browser.js +55 -0
- package/dist/cli/browser.js.map +1 -0
- package/dist/cli/commands/libraryOpen.d.ts.map +1 -1
- package/dist/cli/commands/libraryOpen.js +19 -49
- package/dist/cli/commands/libraryOpen.js.map +1 -1
- package/dist/cli/commands/open.d.ts +4 -9
- package/dist/cli/commands/open.d.ts.map +1 -1
- package/dist/cli/commands/open.js +10 -34
- package/dist/cli/commands/open.js.map +1 -1
- package/dist/cli/router.d.ts.map +1 -1
- package/dist/cli/router.js +1 -0
- package/dist/cli/router.js.map +1 -1
- package/dist/cli/telemetry.d.ts +7 -0
- package/dist/cli/telemetry.d.ts.map +1 -1
- package/dist/cli/telemetry.js +15 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/proxy-middleware/preview-bridge.d.ts.map +1 -1
- package/dist/proxy-middleware/preview-bridge.js +7 -5
- package/dist/proxy-middleware/preview-bridge.js.map +1 -1
- package/dist/proxy-middleware/preview-connections.d.ts +9 -0
- package/dist/proxy-middleware/preview-connections.d.ts.map +1 -0
- package/dist/proxy-middleware/preview-connections.js +30 -0
- package/dist/proxy-middleware/preview-connections.js.map +1 -0
- package/dist/proxy-middleware/proxy-config.d.ts.map +1 -1
- package/dist/proxy-middleware/proxy-config.js +41 -43
- package/dist/proxy-middleware/proxy-config.js.map +1 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +8 -5
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +5 -1
- package/dist/routes/mcp.js.map +1 -1
- package/dist/services/DependencyPreparationService.d.ts +34 -0
- package/dist/services/DependencyPreparationService.d.ts.map +1 -0
- package/dist/services/DependencyPreparationService.js +344 -0
- package/dist/services/DependencyPreparationService.js.map +1 -0
- package/dist/services/HistoryReplayService.d.ts +5 -4
- package/dist/services/HistoryReplayService.d.ts.map +1 -1
- package/dist/services/HistoryReplayService.js +57 -14
- package/dist/services/HistoryReplayService.js.map +1 -1
- package/dist/services/OperatingSystemServices.d.ts +0 -10
- package/dist/services/OperatingSystemServices.d.ts.map +1 -1
- package/dist/services/OperatingSystemServices.js +23 -74
- package/dist/services/OperatingSystemServices.js.map +1 -1
- package/dist/services/ProjectPackageManager.d.ts +35 -0
- package/dist/services/ProjectPackageManager.d.ts.map +1 -0
- package/dist/services/ProjectPackageManager.js +201 -0
- package/dist/services/ProjectPackageManager.js.map +1 -0
- package/dist/services/ProjectRuntimeService.d.ts.map +1 -1
- package/dist/services/ProjectRuntimeService.js +13 -4
- package/dist/services/ProjectRuntimeService.js.map +1 -1
- package/dist/services/TelemetryService.d.ts +2 -2
- package/dist/services/TelemetryService.d.ts.map +1 -1
- package/dist/services/TelemetryService.js +2 -0
- package/dist/services/TelemetryService.js.map +1 -1
- package/dist/services/TreeStore.d.ts.map +1 -1
- package/dist/services/TreeStore.js +3 -9
- package/dist/services/TreeStore.js.map +1 -1
- package/dist/services/WorktreeManager.d.ts +8 -10
- package/dist/services/WorktreeManager.d.ts.map +1 -1
- package/dist/services/WorktreeManager.js +87 -66
- package/dist/services/WorktreeManager.js.map +1 -1
- package/dist/utils/cloneOrCopyDirSync.d.ts.map +1 -1
- package/dist/utils/cloneOrCopyDirSync.js +2 -0
- package/dist/utils/cloneOrCopyDirSync.js.map +1 -1
- package/dist/utils/devServerError.d.ts +3 -1
- package/dist/utils/devServerError.d.ts.map +1 -1
- package/dist/utils/devServerError.js +5 -1
- package/dist/utils/devServerError.js.map +1 -1
- package/dist/utils/htmlDocument.d.ts +4 -5
- package/dist/utils/htmlDocument.d.ts.map +1 -1
- package/dist/utils/htmlDocument.js +20 -11
- package/dist/utils/htmlDocument.js.map +1 -1
- package/dist/utils/internalGitIdentity.d.ts +7 -0
- package/dist/utils/internalGitIdentity.d.ts.map +1 -0
- package/dist/utils/internalGitIdentity.js +17 -0
- package/dist/utils/internalGitIdentity.js.map +1 -0
- package/dist/utils/portUtils.d.ts +4 -2
- package/dist/utils/portUtils.d.ts.map +1 -1
- package/dist/utils/portUtils.js +4 -3
- package/dist/utils/portUtils.js.map +1 -1
- package/dist/utils/skills/claude-skill.d.ts +1 -1
- package/dist/utils/skills/claude-skill.js +1 -1
- package/dist/utils/skills/cli-guidance.d.ts +1 -1
- package/dist/utils/skills/cli-guidance.js +2 -2
- package/dist/utils/skills/cursor-rules.d.ts +1 -1
- package/dist/utils/skills/cursor-rules.js +1 -1
- package/package.json +4 -2
- package/src/ui/dist/assets/{LibraryApp-DXPBLgDE.js → LibraryApp-qbLqcNxM.js} +1 -1
- package/src/ui/dist/assets/{main-CQ8l2PxN.js → main-touv07Dk.js} +75 -75
- package/src/ui/dist/index.html +1 -1
|
@@ -10,7 +10,9 @@ const events_1 = require("events");
|
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
const os_1 = __importDefault(require("os"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
|
-
const
|
|
13
|
+
const DependencyPreparationService_1 = require("../services/DependencyPreparationService");
|
|
14
|
+
const ProjectPackageManager_1 = require("../services/ProjectPackageManager");
|
|
15
|
+
const devServerCommand_1 = require("../utils/devServerCommand");
|
|
14
16
|
const simple_git_1 = require("simple-git");
|
|
15
17
|
const logger_1 = require("../utils/logger");
|
|
16
18
|
const devServerError_1 = require("../utils/devServerError");
|
|
@@ -1785,37 +1787,54 @@ class AgentVariantsOrchestrator {
|
|
|
1785
1787
|
return this.resources.get(sessionId)?.worktrees.get(workItemId)?.port;
|
|
1786
1788
|
}
|
|
1787
1789
|
/**
|
|
1788
|
-
* Restores a Rivet-owned
|
|
1789
|
-
*
|
|
1790
|
-
* attempts to restart user-owned or repeatedly failing servers.
|
|
1790
|
+
* Restores a Rivet-owned preview after idle reclamation or an explicit retry
|
|
1791
|
+
* of a failed start. Reuses the saved worktree without leasing generation work.
|
|
1791
1792
|
*
|
|
1792
1793
|
* @effect Clears the idle marker and prior crash budget, starts the worktree
|
|
1793
1794
|
* dev server, and emits preview-state snapshots.
|
|
1794
1795
|
* @deps Uses the live session resources, project context, and dev-server
|
|
1795
1796
|
* manager owned by this orchestrator.
|
|
1796
1797
|
*/
|
|
1797
|
-
async restoreVariantPreview(sessionId, workItemId) {
|
|
1798
|
+
async restoreVariantPreview(sessionId, workItemId, options = {}) {
|
|
1798
1799
|
const resources = this.resources.get(sessionId);
|
|
1799
1800
|
const record = resources?.worktrees.get(workItemId);
|
|
1800
|
-
if (!resources || !record)
|
|
1801
|
+
if (!resources || !record || resources.cleanupStarted)
|
|
1801
1802
|
return undefined;
|
|
1803
|
+
if (record.previewRestoration)
|
|
1804
|
+
return record.previewRestoration;
|
|
1802
1805
|
if (record.port !== undefined && record.devServerProcess) {
|
|
1803
1806
|
this.touchSession(sessionId);
|
|
1804
1807
|
return record.port;
|
|
1805
1808
|
}
|
|
1806
|
-
|
|
1809
|
+
const failure = resources.previewFailures.get(workItemId);
|
|
1810
|
+
const isSucceeded = this.store
|
|
1811
|
+
.getVariants(sessionId)
|
|
1812
|
+
.some((variant) => variant.workItemId === workItemId && variant.status === 'succeeded');
|
|
1813
|
+
const isExplicitRetry = options.retryFailed === true &&
|
|
1814
|
+
failure?.reason !== 'missing_static_entry';
|
|
1815
|
+
if (!failure ||
|
|
1816
|
+
!isSucceeded ||
|
|
1817
|
+
(failure.reason !== 'idle_timeout' && !isExplicitRetry)) {
|
|
1807
1818
|
return undefined;
|
|
1808
1819
|
}
|
|
1809
1820
|
resources.previewFailures.delete(workItemId);
|
|
1810
1821
|
record.devServerAutoRestarts = 0;
|
|
1811
1822
|
this.emitChange();
|
|
1812
1823
|
const projectContext = this.store.getProjectContext(sessionId);
|
|
1813
|
-
|
|
1824
|
+
const restoration = this.startVariantDevServer({
|
|
1814
1825
|
sessionId,
|
|
1815
1826
|
workItemId,
|
|
1816
1827
|
record,
|
|
1817
1828
|
isFresh: projectContext.kind === 'fresh',
|
|
1818
1829
|
});
|
|
1830
|
+
record.previewRestoration = restoration;
|
|
1831
|
+
try {
|
|
1832
|
+
return await restoration;
|
|
1833
|
+
}
|
|
1834
|
+
finally {
|
|
1835
|
+
if (record.previewRestoration === restoration)
|
|
1836
|
+
record.previewRestoration = undefined;
|
|
1837
|
+
}
|
|
1819
1838
|
}
|
|
1820
1839
|
/**
|
|
1821
1840
|
* @effect Resolves the dev server port for a preview request and refreshes
|
|
@@ -1958,8 +1977,8 @@ class AgentVariantsOrchestrator {
|
|
|
1958
1977
|
});
|
|
1959
1978
|
this.emitChange();
|
|
1960
1979
|
// Await provisioning before returning so request_work always sees a
|
|
1961
|
-
// populated worktreePath on each leased item. Provisioning
|
|
1962
|
-
//
|
|
1980
|
+
// populated worktreePath on each leased item. Provisioning creates a
|
|
1981
|
+
// git worktree and copies dependencies for each variant, and the
|
|
1963
1982
|
// agent has nothing useful to do between approve and request_work
|
|
1964
1983
|
// anyway — running them serially is the contract the skill assumes.
|
|
1965
1984
|
try {
|
|
@@ -2972,12 +2991,6 @@ class AgentVariantsOrchestrator {
|
|
|
2972
2991
|
// because teardown runs AFTER the rename — too late to prevent the
|
|
2973
2992
|
// stub-dir race.
|
|
2974
2993
|
await this.stopAllDevServersForSession(resources);
|
|
2975
|
-
// node_modules in fresh variants 1..N is a symlink into variant 0
|
|
2976
|
-
// (see runBackgroundInstall). After rename, variant 0's
|
|
2977
|
-
// node_modules vanishes during cleanup of the sibling worktrees,
|
|
2978
|
-
// leaving the chosen variant with a dangling link. Resolve the
|
|
2979
|
-
// symlink to a real directory first so the destination has real
|
|
2980
|
-
// deps and the rename is self-contained.
|
|
2981
2994
|
isDependencyInstallRequired = await resolveNodeModulesSymlink(record.worktreePath);
|
|
2982
2995
|
await this.renameWorktreeIntoDestination(record.worktreePath, destinationPath);
|
|
2983
2996
|
usedRename = true;
|
|
@@ -3404,8 +3417,7 @@ class AgentVariantsOrchestrator {
|
|
|
3404
3417
|
}
|
|
3405
3418
|
/**
|
|
3406
3419
|
* Existing-project flow: clone the user's repo N times via git worktree.
|
|
3407
|
-
*
|
|
3408
|
-
* node_modules is symlinked from the source project).
|
|
3420
|
+
* Waits for source and dependency copies before making work available.
|
|
3409
3421
|
*/
|
|
3410
3422
|
async provisionExistingWorktrees(sessionId, approveResult) {
|
|
3411
3423
|
const resources = this.ensureResources(sessionId);
|
|
@@ -3545,24 +3557,17 @@ class AgentVariantsOrchestrator {
|
|
|
3545
3557
|
async runBackgroundInstall(sessionId, scaffoldWorkItemId, worktreePaths, scaffoldStartedAt) {
|
|
3546
3558
|
const installStartedAt = Date.now();
|
|
3547
3559
|
try {
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
const link = path_1.default.join(p, 'node_modules');
|
|
3553
|
-
// The fresh template has no node_modules until install runs, so
|
|
3554
|
-
// the link target is guaranteed absent in `p` — no need to remove
|
|
3555
|
-
// first.
|
|
3556
|
-
try {
|
|
3557
|
-
fs_1.default.symlinkSync(target, link, 'dir');
|
|
3558
|
-
}
|
|
3559
|
-
catch (symlinkErr) {
|
|
3560
|
-
log.warn(`Symlink node_modules failed for ${p}, falling back to install`, symlinkErr);
|
|
3561
|
-
await this.installDependencies(p);
|
|
3562
|
-
}
|
|
3563
|
-
}));
|
|
3560
|
+
await Promise.all(worktreePaths.map((worktreePath) => this.installDependencies(worktreePath, {
|
|
3561
|
+
workspaceRoot: worktreePath,
|
|
3562
|
+
signal: this.resources.get(sessionId)?.dependencyAbortController.signal,
|
|
3563
|
+
})));
|
|
3564
3564
|
}
|
|
3565
3565
|
catch (err) {
|
|
3566
|
+
// Teardown aborts in-flight preparation; a cancelled session is not a
|
|
3567
|
+
// scaffold failure.
|
|
3568
|
+
const resources = this.resources.get(sessionId);
|
|
3569
|
+
if (!resources || resources.cleanupStarted)
|
|
3570
|
+
return;
|
|
3566
3571
|
const message = err instanceof Error ? err.message : String(err);
|
|
3567
3572
|
log.warn(`npm install failed for session ${sessionId}; marking scaffold_base failed`, err);
|
|
3568
3573
|
trackScaffoldFailed(this.telemetry, {
|
|
@@ -3845,8 +3850,10 @@ class AgentVariantsOrchestrator {
|
|
|
3845
3850
|
let port;
|
|
3846
3851
|
try {
|
|
3847
3852
|
port = await this.worktrees.findFreePortFrom(scanFrom);
|
|
3848
|
-
const dev = await this.resolveDevServer(args.sessionId, args.
|
|
3853
|
+
const dev = await this.resolveDevServer(args.sessionId, args.record.worktreePath, port, args.isFresh);
|
|
3849
3854
|
lastDev = dev;
|
|
3855
|
+
if (!canContinueStart())
|
|
3856
|
+
return undefined;
|
|
3850
3857
|
const proc = await this.worktrees.startDevServer(dev.cwd, port, dev.cmd, dev.args, dev.env);
|
|
3851
3858
|
// The boot can outlive the session (a cancel during the readiness
|
|
3852
3859
|
// wait, or during an auto-restart's delay). At this point the child
|
|
@@ -3982,13 +3989,33 @@ class AgentVariantsOrchestrator {
|
|
|
3982
3989
|
if (missingDeps && lastDev) {
|
|
3983
3990
|
installRetried = true;
|
|
3984
3991
|
try {
|
|
3985
|
-
await this.installDependencies(lastDev.cwd,
|
|
3992
|
+
await this.installDependencies(lastDev.cwd, {
|
|
3993
|
+
workspaceRoot: args.record.worktreePath,
|
|
3994
|
+
force: true,
|
|
3995
|
+
signal: this.resources.get(args.sessionId)
|
|
3996
|
+
?.dependencyAbortController.signal,
|
|
3997
|
+
});
|
|
3986
3998
|
}
|
|
3987
3999
|
catch (installErr) {
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4000
|
+
log.warn(`Dependency preparation failed for variant ${args.workItemId}`, installErr);
|
|
4001
|
+
this.resources
|
|
4002
|
+
.get(args.sessionId)
|
|
4003
|
+
?.previewFailures.set(args.workItemId, {
|
|
4004
|
+
reason: 'dependency_install_failed',
|
|
4005
|
+
portInUse: false,
|
|
4006
|
+
});
|
|
4007
|
+
trackPreviewFailed(this.telemetry, {
|
|
4008
|
+
sessionId: args.sessionId,
|
|
4009
|
+
variantId: args.workItemId,
|
|
4010
|
+
projectContextKind,
|
|
4011
|
+
previewKind: 'dev_server',
|
|
4012
|
+
errorCode: 'DEPENDENCY_INSTALL_FAILED',
|
|
4013
|
+
reason: 'dependency_install_failed',
|
|
4014
|
+
portInUse: false,
|
|
4015
|
+
durationMs: Date.now() - previewStartedAt,
|
|
4016
|
+
});
|
|
4017
|
+
this.emitChange();
|
|
4018
|
+
return undefined;
|
|
3992
4019
|
}
|
|
3993
4020
|
}
|
|
3994
4021
|
continue;
|
|
@@ -4002,9 +4029,11 @@ class AgentVariantsOrchestrator {
|
|
|
4002
4029
|
reason,
|
|
4003
4030
|
portInUse,
|
|
4004
4031
|
});
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4032
|
+
let errorCode = 'DEV_SERVER_START_FAILED';
|
|
4033
|
+
if (reason === 'dependency_install_failed')
|
|
4034
|
+
errorCode = 'DEPENDENCY_INSTALL_FAILED';
|
|
4035
|
+
if (reason === 'missing_dev_script')
|
|
4036
|
+
errorCode = 'DEV_SCRIPT_MISSING';
|
|
4008
4037
|
// Telemetry gets structured classification only — never the raw
|
|
4009
4038
|
// output tail, which routinely carries absolute project paths and
|
|
4010
4039
|
// source frames (see AGENTS.md "What NEVER to capture"). The full
|
|
@@ -4875,16 +4904,13 @@ class AgentVariantsOrchestrator {
|
|
|
4875
4904
|
* always use the Vite template's npm command at the worktree root; existing
|
|
4876
4905
|
* projects defer to the user's framework/packageManager config.
|
|
4877
4906
|
*/
|
|
4878
|
-
async resolveDevServer(sessionId,
|
|
4907
|
+
async resolveDevServer(sessionId, worktreePath, port, isFresh) {
|
|
4879
4908
|
const resolved = await this.resolveDevServerCommand(sessionId, worktreePath, port, isFresh);
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
packageManager: resolved.packageManager,
|
|
4886
|
-
});
|
|
4887
|
-
}
|
|
4909
|
+
await this.ensureProjectDependencies({
|
|
4910
|
+
signal: this.resources.get(sessionId)?.dependencyAbortController.signal,
|
|
4911
|
+
cwd: resolved.cwd,
|
|
4912
|
+
workspaceRoot: worktreePath,
|
|
4913
|
+
});
|
|
4888
4914
|
return resolved;
|
|
4889
4915
|
}
|
|
4890
4916
|
/**
|
|
@@ -4917,18 +4943,31 @@ class AgentVariantsOrchestrator {
|
|
|
4917
4943
|
throw new Error('Static projects do not have a dev server command');
|
|
4918
4944
|
}
|
|
4919
4945
|
const cwd = await this.resolveExistingProjectCwd(sessionId, worktreePath, env);
|
|
4920
|
-
const packageManager = detectPackageManagerForCwd(cwd, env.packageManager);
|
|
4921
4946
|
// Every branch below — including the production buildDevCommand recipes
|
|
4922
4947
|
// from utils/devServerCommand.ts — invokes the package.json script named
|
|
4923
4948
|
// by env.devCommand, so the preflight applies to all of them.
|
|
4924
4949
|
if (env.devCommand) {
|
|
4925
4950
|
assertDevScriptExists(cwd, env.devCommand);
|
|
4926
4951
|
}
|
|
4952
|
+
const detected = fs_1.default.existsSync(path_1.default.join(cwd, 'package.json'))
|
|
4953
|
+
? await (0, ProjectPackageManager_1.resolveProjectPackageManager)({
|
|
4954
|
+
cwd,
|
|
4955
|
+
workspaceRoot: worktreePath,
|
|
4956
|
+
}).catch((error) => {
|
|
4957
|
+
throw new devServerError_1.DevServerStartError({
|
|
4958
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4959
|
+
detail: '',
|
|
4960
|
+
reason: 'dependency_install_failed',
|
|
4961
|
+
portInUse: false,
|
|
4962
|
+
});
|
|
4963
|
+
})
|
|
4964
|
+
: null;
|
|
4965
|
+
const packageManager = detected?.name ?? env.packageManager;
|
|
4927
4966
|
if (packageManager !== env.packageManager) {
|
|
4928
4967
|
return {
|
|
4929
4968
|
cwd,
|
|
4930
4969
|
packageManager,
|
|
4931
|
-
...
|
|
4970
|
+
...(0, devServerCommand_1.buildDevServerCommand)(env.framework, packageManager, env.devCommand, port),
|
|
4932
4971
|
};
|
|
4933
4972
|
}
|
|
4934
4973
|
if (env.buildDevCommand) {
|
|
@@ -4942,19 +4981,14 @@ class AgentVariantsOrchestrator {
|
|
|
4942
4981
|
packageManager,
|
|
4943
4982
|
};
|
|
4944
4983
|
}
|
|
4945
|
-
/**
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
* install in the cloned worktree; install only when the package has no
|
|
4949
|
-
* node_modules so normal user projects are not penalized.
|
|
4950
|
-
*/
|
|
4951
|
-
async ensureExistingProjectDependencies(args) {
|
|
4952
|
-
const packageJsonPath = path_1.default.join(args.cwd, 'package.json');
|
|
4953
|
-
const nodeModulesPath = path_1.default.join(args.cwd, 'node_modules');
|
|
4954
|
-
if (!fs_1.default.existsSync(packageJsonPath) || fs_1.default.existsSync(nodeModulesPath)) {
|
|
4984
|
+
/** Prepares the direction's declared dependencies before each server start. */
|
|
4985
|
+
async ensureProjectDependencies(args) {
|
|
4986
|
+
if (!fs_1.default.existsSync(path_1.default.join(args.cwd, 'package.json')))
|
|
4955
4987
|
return;
|
|
4956
|
-
|
|
4957
|
-
|
|
4988
|
+
await this.installDependencies(args.cwd, {
|
|
4989
|
+
workspaceRoot: args.workspaceRoot,
|
|
4990
|
+
signal: args.signal,
|
|
4991
|
+
});
|
|
4958
4992
|
}
|
|
4959
4993
|
/**
|
|
4960
4994
|
* Resolve the dev-server working directory for an existing-project variant.
|
|
@@ -4983,8 +5017,7 @@ class AgentVariantsOrchestrator {
|
|
|
4983
5017
|
/**
|
|
4984
5018
|
* After the chosen variant has been renamed into `destinationPath`, move
|
|
4985
5019
|
* each unchosen sibling worktree into a `<destinationParent>/<slug>-variants/NN-<label>/`
|
|
4986
|
-
* folder
|
|
4987
|
-
* real install. Writes a manifest documenting all 5 variants (the chosen
|
|
5020
|
+
* folder with its own prepared dependency installation. Writes a manifest documenting all 5 variants (the chosen
|
|
4988
5021
|
* one with `folder: null` and `chosenPath: '../<slug>'`).
|
|
4989
5022
|
*
|
|
4990
5023
|
* Sets `vitePreservedSiblings = true` on session resources so the
|
|
@@ -4999,7 +5032,6 @@ class AgentVariantsOrchestrator {
|
|
|
4999
5032
|
const destinationParent = path_1.default.dirname(args.destinationPath);
|
|
5000
5033
|
const projectSlug = path_1.default.basename(args.destinationPath);
|
|
5001
5034
|
const historyDir = (0, createProjectArtifacts_1.createVariantsHistoryPath)(destinationParent, projectSlug);
|
|
5002
|
-
const sharedNodeModulesAbs = path_1.default.join(args.destinationPath, 'node_modules');
|
|
5003
5035
|
const briefs = this.store.getBriefs(args.sessionId);
|
|
5004
5036
|
const variants = this.store.getVariants(args.sessionId);
|
|
5005
5037
|
const briefIndexById = new Map(briefs.map((b, i) => [b.briefId, i]));
|
|
@@ -5057,26 +5089,6 @@ class AgentVariantsOrchestrator {
|
|
|
5057
5089
|
fs_1.default.renameSync(record.worktreePath, dest);
|
|
5058
5090
|
return dest;
|
|
5059
5091
|
})();
|
|
5060
|
-
// Repoint node_modules at the chosen project's real install.
|
|
5061
|
-
try {
|
|
5062
|
-
if (this.worktrees.repointNodeModulesSymlink) {
|
|
5063
|
-
this.worktrees.repointNodeModulesSymlink(newPath, sharedNodeModulesAbs);
|
|
5064
|
-
}
|
|
5065
|
-
else {
|
|
5066
|
-
const link = path_1.default.join(newPath, 'node_modules');
|
|
5067
|
-
try {
|
|
5068
|
-
fs_1.default.rmSync(link, { force: true, recursive: true });
|
|
5069
|
-
}
|
|
5070
|
-
catch {
|
|
5071
|
-
/* may not exist */
|
|
5072
|
-
}
|
|
5073
|
-
const target = path_1.default.relative(path_1.default.dirname(link), sharedNodeModulesAbs);
|
|
5074
|
-
fs_1.default.symlinkSync(target, link, 'dir');
|
|
5075
|
-
}
|
|
5076
|
-
}
|
|
5077
|
-
catch (err) {
|
|
5078
|
-
log.warn(`repointNodeModulesSymlink failed for ${newPath}; variant still runnable after npm install`, err);
|
|
5079
|
-
}
|
|
5080
5092
|
// Update the in-memory record so teardown doesn't try to operate on
|
|
5081
5093
|
// the stale path.
|
|
5082
5094
|
record.worktreePath = newPath;
|
|
@@ -5408,6 +5420,8 @@ class AgentVariantsOrchestrator {
|
|
|
5408
5420
|
resources.cleanupStarted = true;
|
|
5409
5421
|
this.clearActivePreviewIdleTimer(resources);
|
|
5410
5422
|
log.info(`Tearing down session ${sessionId} (reason: ${reason})`);
|
|
5423
|
+
resources.dependencyAbortController.abort();
|
|
5424
|
+
await Promise.all([...resources.worktrees.values()].map((record) => DependencyPreparationService_1.previewDependencies.settle(record.worktreePath)));
|
|
5411
5425
|
// Drain in-flight history persists BEFORE deleting worktrees: a tree
|
|
5412
5426
|
// snapshot's `git add -A --work-tree <worktree>` racing the deletion
|
|
5413
5427
|
// below doesn't fail — it stages whatever files remain, publishing a
|
|
@@ -5504,6 +5518,7 @@ class AgentVariantsOrchestrator {
|
|
|
5504
5518
|
workspaceManager: new VariantWorkspaceManager_1.VariantWorkspaceManager(),
|
|
5505
5519
|
contextAssets: new Map(),
|
|
5506
5520
|
cleanupStarted: false,
|
|
5521
|
+
dependencyAbortController: new AbortController(),
|
|
5507
5522
|
committedVariantIds: new Set(),
|
|
5508
5523
|
startedAt: Date.now(),
|
|
5509
5524
|
nextRoundIndex: 0,
|
|
@@ -6447,54 +6462,15 @@ function copyStaticPreviewDirectory(src, dest) {
|
|
|
6447
6462
|
}
|
|
6448
6463
|
}
|
|
6449
6464
|
}
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
return {
|
|
6458
|
-
cmd: packageManager,
|
|
6459
|
-
args: ['install', '--ignore-scripts'],
|
|
6460
|
-
};
|
|
6461
|
-
};
|
|
6462
|
-
const defaultInstallDependencies = (worktreePath, packageManager = 'npm') => {
|
|
6463
|
-
const install = buildInstallCommand(packageManager);
|
|
6464
|
-
return new Promise((resolve, reject) => {
|
|
6465
|
-
const proc = (0, child_process_1.spawn)(install.cmd, install.args, {
|
|
6466
|
-
cwd: worktreePath,
|
|
6467
|
-
stdio: ['ignore', 'ignore', 'pipe'],
|
|
6468
|
-
});
|
|
6469
|
-
let stderr = '';
|
|
6470
|
-
proc.stderr.on('data', (chunk) => {
|
|
6471
|
-
stderr += chunk.toString();
|
|
6472
|
-
});
|
|
6473
|
-
proc.on('error', reject);
|
|
6474
|
-
proc.on('exit', (code) => {
|
|
6475
|
-
if (code === 0) {
|
|
6476
|
-
resolve();
|
|
6477
|
-
return;
|
|
6478
|
-
}
|
|
6479
|
-
reject(new Error(`${install.cmd} install in ${worktreePath} failed (code ${code}): ${stderr.slice(-512)}`));
|
|
6480
|
-
});
|
|
6465
|
+
/** Prepares an isolated project through its selected package manager. */
|
|
6466
|
+
const defaultInstallDependencies = async (worktreePath, options) => {
|
|
6467
|
+
await DependencyPreparationService_1.previewDependencies.prepare({
|
|
6468
|
+
cwd: worktreePath,
|
|
6469
|
+
workspaceRoot: options?.workspaceRoot ?? worktreePath,
|
|
6470
|
+
force: options?.force,
|
|
6471
|
+
signal: options?.signal,
|
|
6481
6472
|
});
|
|
6482
6473
|
};
|
|
6483
|
-
const detectPackageManagerForCwd = (cwd, fallback) => {
|
|
6484
|
-
if (fs_1.default.existsSync(path_1.default.join(cwd, 'yarn.lock')))
|
|
6485
|
-
return 'yarn';
|
|
6486
|
-
if (fs_1.default.existsSync(path_1.default.join(cwd, 'pnpm-lock.yaml')))
|
|
6487
|
-
return 'pnpm';
|
|
6488
|
-
if (fs_1.default.existsSync(path_1.default.join(cwd, 'package-lock.json')))
|
|
6489
|
-
return 'npm';
|
|
6490
|
-
if (fallback === 'pnpm' || fallback === 'yarn' || fallback === 'npm') {
|
|
6491
|
-
// A nested package without a lockfile should not inherit a root Yarn Berry
|
|
6492
|
-
// workspace when it is not declared as a workspace member; npm treats it as
|
|
6493
|
-
// an independent package and is the safest default for old examples.
|
|
6494
|
-
return fs_1.default.existsSync(path_1.default.join(cwd, 'package.json')) ? 'npm' : fallback;
|
|
6495
|
-
}
|
|
6496
|
-
return 'npm';
|
|
6497
|
-
};
|
|
6498
6474
|
/**
|
|
6499
6475
|
* Turn the invocation a live dev server actually booted with into a
|
|
6500
6476
|
* port-parameterized replay recipe. The resolution bakes the allocated port
|
|
@@ -6516,12 +6492,16 @@ const buildServeRecipeFromResolved = (snapshotRoot, dev, port) => {
|
|
|
6516
6492
|
};
|
|
6517
6493
|
/**
|
|
6518
6494
|
* Whether an install at `cwd` stays inside the Rivet-owned worktree.
|
|
6519
|
-
*
|
|
6520
|
-
*
|
|
6521
|
-
*
|
|
6522
|
-
* dependencies, which the variants flow must never do. Install is safe only
|
|
6495
|
+
* A linked `node_modules` can point at another project's install; running a
|
|
6496
|
+
* package manager through it would mutate dependencies outside this worktree.
|
|
6497
|
+
* Install is safe only
|
|
6523
6498
|
* when `node_modules` is absent (a nested standalone package — the install
|
|
6524
6499
|
* creates a worktree-local dir) or a real directory owned by the worktree.
|
|
6500
|
+
*
|
|
6501
|
+
* Complements DependencyPreparationService.assertOwnedInstall rather than
|
|
6502
|
+
* duplicating it: this stricter check (any symlink, even one resolving
|
|
6503
|
+
* inside the worktree) decides whether an install RETRY is worth attempting,
|
|
6504
|
+
* while the service enforces containment on every install it runs.
|
|
6525
6505
|
*/
|
|
6526
6506
|
const canInstallWithoutTouchingHost = (cwd) => {
|
|
6527
6507
|
try {
|
|
@@ -6568,29 +6548,6 @@ const assertDevScriptExists = (cwd, scriptName) => {
|
|
|
6568
6548
|
: '',
|
|
6569
6549
|
});
|
|
6570
6550
|
};
|
|
6571
|
-
const buildExistingProjectDevCommand = (packageManager, devCommand, port) => {
|
|
6572
|
-
const env = { PORT: String(port) };
|
|
6573
|
-
if (packageManager === 'npm') {
|
|
6574
|
-
return {
|
|
6575
|
-
cmd: 'npm',
|
|
6576
|
-
args: [
|
|
6577
|
-
'run',
|
|
6578
|
-
devCommand,
|
|
6579
|
-
'--',
|
|
6580
|
-
'--port',
|
|
6581
|
-
String(port),
|
|
6582
|
-
'--host',
|
|
6583
|
-
FRESH_DEV_SERVER_HOST,
|
|
6584
|
-
],
|
|
6585
|
-
env,
|
|
6586
|
-
};
|
|
6587
|
-
}
|
|
6588
|
-
return {
|
|
6589
|
-
cmd: packageManager,
|
|
6590
|
-
args: [devCommand, '--port', String(port), '--host', FRESH_DEV_SERVER_HOST],
|
|
6591
|
-
env,
|
|
6592
|
-
};
|
|
6593
|
-
};
|
|
6594
6551
|
/**
|
|
6595
6552
|
* True when `a` and `b` (or the closest existing ancestor of each) live on
|
|
6596
6553
|
* the same filesystem volume. Used by commitVariant to decide between an
|
|
@@ -6612,65 +6569,20 @@ function areSameVolume(a, b) {
|
|
|
6612
6569
|
return false;
|
|
6613
6570
|
}
|
|
6614
6571
|
}
|
|
6615
|
-
/**
|
|
6616
|
-
* Materialize node_modules from symlink → real directory at the given path,
|
|
6617
|
-
* so a subsequent rename of that path leaves a self-contained tree.
|
|
6618
|
-
*
|
|
6619
|
-
* Fresh worktrees 1..N share variant 0's node_modules via a symlink (see
|
|
6620
|
-
* runBackgroundInstall). When the user picks variant K and we rename it
|
|
6621
|
-
* into the destination, the sibling worktrees get torn down — which would
|
|
6622
|
-
* otherwise leave a dangling symlink in the destination if variant K was
|
|
6623
|
-
* the one with the symlink (K > 0).
|
|
6624
|
-
*
|
|
6625
|
-
* Returns true when the destination must run a dependency install after the
|
|
6626
|
-
* rename because node_modules is missing or points nowhere.
|
|
6627
|
-
*/
|
|
6572
|
+
/** Clears a borrowed install so a moved generated project prepares its own dependencies. */
|
|
6628
6573
|
async function resolveNodeModulesSymlink(worktreePath) {
|
|
6629
6574
|
const nodeModulesPath = path_1.default.join(worktreePath, 'node_modules');
|
|
6630
|
-
let lstat;
|
|
6631
6575
|
try {
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
catch {
|
|
6635
|
-
return true;
|
|
6636
|
-
}
|
|
6637
|
-
if (!lstat.isSymbolicLink())
|
|
6638
|
-
return false;
|
|
6639
|
-
let target;
|
|
6640
|
-
try {
|
|
6641
|
-
target = await fs_1.default.promises.realpath(nodeModulesPath);
|
|
6642
|
-
}
|
|
6643
|
-
catch {
|
|
6576
|
+
if (!(await fs_1.default.promises.lstat(nodeModulesPath)).isSymbolicLink())
|
|
6577
|
+
return false;
|
|
6644
6578
|
await fs_1.default.promises.rm(nodeModulesPath, { force: true });
|
|
6645
6579
|
return true;
|
|
6646
6580
|
}
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
const stagingPath = `${nodeModulesPath}.materializing-${(0, crypto_1.randomBytes)(4).toString('hex')}`;
|
|
6652
|
-
try {
|
|
6653
|
-
await fs_1.default.promises.cp(target, stagingPath, {
|
|
6654
|
-
recursive: true,
|
|
6655
|
-
dereference: false,
|
|
6656
|
-
});
|
|
6581
|
+
catch (error) {
|
|
6582
|
+
if (error.code === 'ENOENT')
|
|
6583
|
+
return true;
|
|
6584
|
+
throw error;
|
|
6657
6585
|
}
|
|
6658
|
-
catch (err) {
|
|
6659
|
-
// Best-effort cleanup of the partial staging dir; leave the symlink
|
|
6660
|
-
// intact so the caller can re-run materialization.
|
|
6661
|
-
await fs_1.default.promises
|
|
6662
|
-
.rm(stagingPath, { recursive: true, force: true })
|
|
6663
|
-
.catch(() => { });
|
|
6664
|
-
throw err;
|
|
6665
|
-
}
|
|
6666
|
-
// Swap: remove the symlink, then rename the staging dir into place.
|
|
6667
|
-
// Both operations are local fs ops on the same parent — fast and
|
|
6668
|
-
// unlikely to fail independently, but if the rename fails the symlink
|
|
6669
|
-
// is already gone, so prefer to leave the staging dir on disk over a
|
|
6670
|
-
// half-broken worktree (the caller will surface the throw).
|
|
6671
|
-
await fs_1.default.promises.rm(nodeModulesPath, { force: true });
|
|
6672
|
-
await fs_1.default.promises.rename(stagingPath, nodeModulesPath);
|
|
6673
|
-
return false;
|
|
6674
6586
|
}
|
|
6675
6587
|
const defaultMaterializeProject = async (sourceWorktreePath, destinationPath) => {
|
|
6676
6588
|
await fs_1.default.promises.mkdir(destinationPath, { recursive: true });
|