rivet-design 0.14.11 → 0.14.13
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/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/agent-variants/SessionStore.js +3 -0
- package/dist/agent-variants/SessionStore.js.map +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.d.ts +19 -3
- package/dist/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.js +286 -61
- package/dist/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +1 -0
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.js +1 -0
- package/dist/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/agent-variants/contextAssets.d.ts +23 -0
- package/dist/agent-variants/contextAssets.d.ts.map +1 -0
- package/dist/agent-variants/contextAssets.js +199 -0
- package/dist/agent-variants/contextAssets.js.map +1 -0
- package/dist/agent-variants/contracts.d.ts +731 -181
- package/dist/agent-variants/contracts.d.ts.map +1 -1
- package/dist/agent-variants/contracts.js +33 -2
- package/dist/agent-variants/contracts.js.map +1 -1
- package/dist/agent-variants/variantContext.d.ts +2 -1
- package/dist/agent-variants/variantContext.d.ts.map +1 -1
- package/dist/agent-variants/variantContext.js +9 -0
- package/dist/agent-variants/variantContext.js.map +1 -1
- package/dist/agent-variants/workItemBriefing.d.ts.map +1 -1
- package/dist/agent-variants/workItemBriefing.js +12 -0
- package/dist/agent-variants/workItemBriefing.js.map +1 -1
- package/dist/cli/commands/mcpServe.d.ts +3 -1
- package/dist/cli/commands/mcpServe.d.ts.map +1 -1
- package/dist/cli/commands/mcpServe.js +168 -24
- package/dist/cli/commands/mcpServe.js.map +1 -1
- package/dist/cli/commands/reference.d.ts.map +1 -1
- package/dist/cli/commands/reference.js +10 -4
- package/dist/cli/commands/reference.js.map +1 -1
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/serve.js +36 -7
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/shared.d.ts +0 -1
- package/dist/cli/commands/shared.d.ts.map +1 -1
- package/dist/cli/commands/shared.js +9 -1
- package/dist/cli/commands/shared.js.map +1 -1
- package/dist/cli/commands/variants.d.ts.map +1 -1
- package/dist/cli/commands/variants.js +69 -1
- package/dist/cli/commands/variants.js.map +1 -1
- package/dist/cli/commands/wait.d.ts +38 -0
- package/dist/cli/commands/wait.d.ts.map +1 -1
- package/dist/cli/commands/wait.js +16 -2
- package/dist/cli/commands/wait.js.map +1 -1
- package/dist/cli/envelope.d.ts +8 -1
- package/dist/cli/envelope.d.ts.map +1 -1
- package/dist/cli/envelope.js +31 -9
- package/dist/cli/envelope.js.map +1 -1
- package/dist/cli/router.d.ts.map +1 -1
- package/dist/cli/router.js +32 -7
- package/dist/cli/router.js.map +1 -1
- package/dist/cli/telemetry.d.ts +14 -0
- package/dist/cli/telemetry.d.ts.map +1 -0
- package/dist/cli/telemetry.js +71 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/config/telemetryContract.d.ts +3 -0
- package/dist/config/telemetryContract.d.ts.map +1 -0
- package/dist/config/telemetryContract.js +6 -0
- package/dist/config/telemetryContract.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -6
- package/dist/index.js.map +1 -1
- package/dist/install/legacyMcp.d.ts +13 -0
- package/dist/install/legacyMcp.d.ts.map +1 -1
- package/dist/install/legacyMcp.js +40 -4
- package/dist/install/legacyMcp.js.map +1 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +5 -0
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +0 -5
- package/dist/routes/mcp.js.map +1 -1
- package/dist/services/ConfigManager.d.ts +8 -0
- package/dist/services/ConfigManager.d.ts.map +1 -1
- package/dist/services/ConfigManager.js +14 -0
- package/dist/services/ConfigManager.js.map +1 -1
- package/dist/services/DevServerRuntimeService.d.ts +5 -17
- package/dist/services/DevServerRuntimeService.d.ts.map +1 -1
- package/dist/services/DevServerRuntimeService.js +43 -81
- package/dist/services/DevServerRuntimeService.js.map +1 -1
- package/dist/services/ProjectDetectionService.d.ts.map +1 -1
- package/dist/services/ProjectDetectionService.js +6 -1
- package/dist/services/ProjectDetectionService.js.map +1 -1
- package/dist/services/TelemetryService.d.ts +55 -10
- package/dist/services/TelemetryService.d.ts.map +1 -1
- package/dist/services/TelemetryService.js +78 -13
- package/dist/services/TelemetryService.js.map +1 -1
- package/dist/services/VariantGenerationService.d.ts.map +1 -1
- package/dist/services/VariantGenerationService.js +10 -4
- package/dist/services/VariantGenerationService.js.map +1 -1
- package/dist/services/VariantRunService.d.ts +1 -0
- package/dist/services/VariantRunService.d.ts.map +1 -1
- package/dist/services/VariantRunService.js +12 -1
- package/dist/services/VariantRunService.js.map +1 -1
- package/dist/services/WorktreeManager.d.ts +3 -3
- package/dist/services/WorktreeManager.d.ts.map +1 -1
- package/dist/services/WorktreeManager.js +41 -25
- package/dist/services/WorktreeManager.js.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.js +4 -1
- package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
- package/dist/types/change-request-types.d.ts +3 -0
- package/dist/types/change-request-types.d.ts.map +1 -1
- package/dist/types/change-request-types.js +0 -5
- package/dist/types/change-request-types.js.map +1 -1
- package/dist/utils/devServerCommand.d.ts +10 -0
- package/dist/utils/devServerCommand.d.ts.map +1 -1
- package/dist/utils/devServerCommand.js +37 -1
- package/dist/utils/devServerCommand.js.map +1 -1
- package/dist/utils/devServerError.d.ts +2 -1
- package/dist/utils/devServerError.d.ts.map +1 -1
- package/dist/utils/devServerError.js +12 -0
- package/dist/utils/devServerError.js.map +1 -1
- package/dist/utils/loginShellPath.d.ts +2 -2
- package/dist/utils/loginShellPath.d.ts.map +1 -1
- package/dist/utils/loginShellPath.js +18 -12
- package/dist/utils/loginShellPath.js.map +1 -1
- package/dist/utils/skills/claude-skill.d.ts +1 -1
- package/dist/utils/skills/claude-skill.d.ts.map +1 -1
- package/dist/utils/skills/cli-guidance.d.ts +2 -2
- package/dist/utils/skills/cli-guidance.d.ts.map +1 -1
- package/dist/utils/skills/cli-guidance.js +4 -2
- package/dist/utils/skills/cli-guidance.js.map +1 -1
- package/dist/utils/skills/cursor-rules.d.ts +1 -1
- package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ui/dist/assets/main-BfVGaiD6.js +422 -0
- package/src/ui/dist/index.html +1 -1
- package/src/ui/dist/assets/main-EGX1lAi3.js +0 -422
|
@@ -29,6 +29,7 @@ const VariantWorkspaceManager_1 = require("./VariantWorkspaceManager");
|
|
|
29
29
|
const diffQa_1 = require("./diffQa");
|
|
30
30
|
const VariantHistoryService_1 = require("../services/VariantHistoryService");
|
|
31
31
|
const variantContext_1 = require("./variantContext");
|
|
32
|
+
const contextAssets_1 = require("./contextAssets");
|
|
32
33
|
const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
|
|
33
34
|
const FRESH_DEV_SERVER_HOST = '127.0.0.1';
|
|
34
35
|
const BRIEF_LABEL_MAX_LENGTH = 48;
|
|
@@ -86,6 +87,19 @@ const RECIPE_PORT_SENTINEL = 65535;
|
|
|
86
87
|
// Fresh worktrees run the Vite React template, whose dev server defaults to
|
|
87
88
|
// 5173. Used as the scan start when allocating a fresh variant's preview port.
|
|
88
89
|
const FRESH_DEV_SERVER_DEFAULT_PORT = 5173;
|
|
90
|
+
// Startup attempts for a variant's dev server. Attempt 2 covers a transient
|
|
91
|
+
// early exit (with a dependency install first when the output shows
|
|
92
|
+
// module-resolution errors); attempt 3 leaves room for one more port step
|
|
93
|
+
// when a collision persists. Early exits fail fast, so extra attempts cost
|
|
94
|
+
// little wall clock.
|
|
95
|
+
const DEV_SERVER_START_MAX_ATTEMPTS = 3;
|
|
96
|
+
// Auto-restarts allowed after a READY dev server crashes (see the exit
|
|
97
|
+
// handler in startVariantDevServer). Two rescues cover transient crashes;
|
|
98
|
+
// a third crash means the build is genuinely broken, so stop looping.
|
|
99
|
+
const DEV_SERVER_MAX_AUTO_RESTARTS = 2;
|
|
100
|
+
// Grace before an auto-restart so a crashing process's port and file locks
|
|
101
|
+
// are actually released first.
|
|
102
|
+
const DEV_SERVER_RESTART_DELAY_MS = 1_000;
|
|
89
103
|
const DESIGN_CONTEXT_ROUTE_SEGMENT = 'design-md';
|
|
90
104
|
const DESIGN_CONTEXT_VIEW_SEGMENT = 'view';
|
|
91
105
|
// Hard ceiling on worktree provisioning so a slow/large host project can never
|
|
@@ -142,28 +156,16 @@ function withProvisionTimeout(work, timeoutMs) {
|
|
|
142
156
|
return Promise.race([work, timeout]).finally(() => clearTimeout(timer));
|
|
143
157
|
}
|
|
144
158
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* configs. The check is defense-in-depth alongside the path / symlink
|
|
149
|
-
* filters in `copyAssetIntoWorktree`; without it, a prompt-injected
|
|
150
|
-
* source plan could copy readable host files such as SSH keys or
|
|
151
|
-
* project secrets into the generated app.
|
|
152
|
-
*
|
|
153
|
-
* Intentionally excludes `.json`, `.txt`, `.md`, `.csv`: these are
|
|
154
|
-
* common carriers for credentials (`credentials.json`, `.aws/config`,
|
|
155
|
-
* shell history `.txt`, README-shaped secrets) and have no legitimate
|
|
156
|
-
* use as binary preview assets. If a future flow needs structured data
|
|
157
|
-
* it should ship it through the manifest instead of file copy.
|
|
159
|
+
* File types an agent-authored asset plan may copy into a generated preview.
|
|
160
|
+
* This allowlist protects an agent-controlled path; caller-selected context
|
|
161
|
+
* assets use the separate, type-neutral delivery path.
|
|
158
162
|
*/
|
|
159
|
-
const
|
|
160
|
-
// 3D / models
|
|
163
|
+
const ALLOWED_INERT_ASSET_EXTENSIONS = new Set([
|
|
161
164
|
'.glb',
|
|
162
165
|
'.gltf',
|
|
163
166
|
'.obj',
|
|
164
167
|
'.fbx',
|
|
165
168
|
'.usdz',
|
|
166
|
-
// images
|
|
167
169
|
'.png',
|
|
168
170
|
'.jpg',
|
|
169
171
|
'.jpeg',
|
|
@@ -173,22 +175,18 @@ const ALLOWED_ASSET_EXTENSIONS = new Set([
|
|
|
173
175
|
'.avif',
|
|
174
176
|
'.bmp',
|
|
175
177
|
'.ico',
|
|
176
|
-
// video
|
|
177
178
|
'.mp4',
|
|
178
179
|
'.webm',
|
|
179
180
|
'.mov',
|
|
180
|
-
// audio
|
|
181
181
|
'.mp3',
|
|
182
182
|
'.wav',
|
|
183
183
|
'.ogg',
|
|
184
184
|
'.m4a',
|
|
185
|
-
// fonts
|
|
186
185
|
'.woff',
|
|
187
186
|
'.woff2',
|
|
188
187
|
'.ttf',
|
|
189
188
|
'.otf',
|
|
190
189
|
'.eot',
|
|
191
|
-
// PDFs
|
|
192
190
|
'.pdf',
|
|
193
191
|
]);
|
|
194
192
|
/**
|
|
@@ -267,7 +265,7 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
267
265
|
? entry.source
|
|
268
266
|
: path_1.default.resolve(assetSourceRoot, entry.source);
|
|
269
267
|
const ext = path_1.default.extname(absoluteSource).toLowerCase();
|
|
270
|
-
if (!
|
|
268
|
+
if (!ALLOWED_INERT_ASSET_EXTENSIONS.has(ext)) {
|
|
271
269
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source extension '${ext || '(none)'}' is not on the allowlist (got '${entry.source}'). Only inert media/font/document assets may be copied.`);
|
|
272
270
|
}
|
|
273
271
|
// lstat first: a symlink whose target lies outside the source path
|
|
@@ -331,7 +329,7 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
331
329
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source traverses a sensitive directory and is refused: ${entry.source}`);
|
|
332
330
|
}
|
|
333
331
|
const resolvedExt = path_1.default.extname(resolvedSource).toLowerCase();
|
|
334
|
-
if (!
|
|
332
|
+
if (!ALLOWED_INERT_ASSET_EXTENSIONS.has(resolvedExt)) {
|
|
335
333
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source resolved extension '${resolvedExt || '(none)'}' is not on the allowlist (resolved from '${entry.source}').`);
|
|
336
334
|
}
|
|
337
335
|
const normalizedDest = path_1.default.normalize(entry.destination);
|
|
@@ -476,6 +474,7 @@ class AgentVariantsOrchestrator {
|
|
|
476
474
|
startStaticPreviewServerImpl;
|
|
477
475
|
provisionTimeoutMs;
|
|
478
476
|
activeVariantServerIdleTtlMs;
|
|
477
|
+
devServerRestartDelayMs;
|
|
479
478
|
resources = new Map();
|
|
480
479
|
/**
|
|
481
480
|
* Committed dev servers from prior sessions that survived teardown. The
|
|
@@ -521,6 +520,8 @@ class AgentVariantsOrchestrator {
|
|
|
521
520
|
this.provisionTimeoutMs = deps.provisionTimeoutMs ?? PROVISION_TIMEOUT_MS;
|
|
522
521
|
this.activeVariantServerIdleTtlMs =
|
|
523
522
|
deps.activeVariantServerIdleTtlMs ?? ACTIVE_VARIANT_SERVER_IDLE_TTL_MS;
|
|
523
|
+
this.devServerRestartDelayMs =
|
|
524
|
+
deps.devServerRestartDelayMs ?? DEV_SERVER_RESTART_DELAY_MS;
|
|
524
525
|
}
|
|
525
526
|
// --- Pure delegations (no side effects) ---------------------------------
|
|
526
527
|
propose(args) {
|
|
@@ -559,6 +560,7 @@ class AgentVariantsOrchestrator {
|
|
|
559
560
|
requestedCount: args.count ?? contracts_1.DEFAULT_VARIANT_COUNT,
|
|
560
561
|
});
|
|
561
562
|
}
|
|
563
|
+
const designContextCount = projectContext.designContext?.length ?? 0;
|
|
562
564
|
this.telemetry.track('agent_variants.session_started', {
|
|
563
565
|
source: 'mcp',
|
|
564
566
|
sessionId: result.sessionId,
|
|
@@ -566,6 +568,8 @@ class AgentVariantsOrchestrator {
|
|
|
566
568
|
hasTarget: Boolean(args.target),
|
|
567
569
|
targetType: args.target?.type ?? null,
|
|
568
570
|
projectContextKind: args.projectContext?.kind ?? 'existing',
|
|
571
|
+
design_context_count: designContextCount,
|
|
572
|
+
design_context_kinds: designContextCount > 0 ? ['design_system'] : [],
|
|
569
573
|
});
|
|
570
574
|
this.emitChange();
|
|
571
575
|
return result;
|
|
@@ -697,7 +701,7 @@ class AgentVariantsOrchestrator {
|
|
|
697
701
|
.filter((source) => source.transport === 'file')
|
|
698
702
|
.map((source) => ({
|
|
699
703
|
path: source.path,
|
|
700
|
-
...(source.label ?? source.originalFilename
|
|
704
|
+
...((source.label ?? source.originalFilename)
|
|
701
705
|
? { label: source.label ?? source.originalFilename }
|
|
702
706
|
: {}),
|
|
703
707
|
}));
|
|
@@ -719,6 +723,9 @@ class AgentVariantsOrchestrator {
|
|
|
719
723
|
workItemId: item.workId,
|
|
720
724
|
kind,
|
|
721
725
|
});
|
|
726
|
+
const contextAssets = this.resources
|
|
727
|
+
.get(sessionId)
|
|
728
|
+
?.contextAssets.get(item.workId);
|
|
722
729
|
const mode = input.sourceVariantId
|
|
723
730
|
? 'vary'
|
|
724
731
|
: DEFAULT_EXECUTION_MODE_BY_KIND[kind];
|
|
@@ -756,6 +763,7 @@ class AgentVariantsOrchestrator {
|
|
|
756
763
|
? { contextBundlePath: input.context.contextBundlePath }
|
|
757
764
|
: {}),
|
|
758
765
|
...(AgentVariantsOrchestrator.contextFilesOf(input.context) ?? {}),
|
|
766
|
+
...(contextAssets?.length ? { contextAssets } : {}),
|
|
759
767
|
completion: kind === 'code_gen'
|
|
760
768
|
? { kind: 'worktree_diff' }
|
|
761
769
|
: { kind: 'static_artifact' },
|
|
@@ -1074,12 +1082,12 @@ class AgentVariantsOrchestrator {
|
|
|
1074
1082
|
const result = this.store.requestWork(args);
|
|
1075
1083
|
try {
|
|
1076
1084
|
this.ensureStaticWorkspaces(args.sessionId, result.leasedWorkItems);
|
|
1085
|
+
this.materializeContextAssets(args.sessionId, result.leasedWorkItems);
|
|
1077
1086
|
}
|
|
1078
1087
|
catch (error) {
|
|
1079
1088
|
for (const item of result.leasedWorkItems) {
|
|
1080
|
-
if (item.kind
|
|
1089
|
+
if (!isImplementationWorkItemKind(item.kind))
|
|
1081
1090
|
continue;
|
|
1082
|
-
}
|
|
1083
1091
|
try {
|
|
1084
1092
|
this.store.reportComplete({
|
|
1085
1093
|
sessionId: args.sessionId,
|
|
@@ -1091,7 +1099,7 @@ class AgentVariantsOrchestrator {
|
|
|
1091
1099
|
code: 'RUNTIME_VALIDATION_FAILED',
|
|
1092
1100
|
message: error instanceof Error
|
|
1093
1101
|
? error.message
|
|
1094
|
-
: 'Failed to
|
|
1102
|
+
: 'Failed to prepare variant workspace',
|
|
1095
1103
|
},
|
|
1096
1104
|
});
|
|
1097
1105
|
}
|
|
@@ -1133,6 +1141,24 @@ class AgentVariantsOrchestrator {
|
|
|
1133
1141
|
this.emitChange();
|
|
1134
1142
|
return decorated;
|
|
1135
1143
|
}
|
|
1144
|
+
/** @effect Copies caller-supplied context assets into each leased workspace. */
|
|
1145
|
+
materializeContextAssets(sessionId, items) {
|
|
1146
|
+
const resources = this.ensureResources(sessionId);
|
|
1147
|
+
for (const item of items) {
|
|
1148
|
+
if (!isImplementationWorkItemKind(item.kind))
|
|
1149
|
+
continue;
|
|
1150
|
+
const input = (item.input ?? {});
|
|
1151
|
+
const assets = input.context?.contextAssets ?? [];
|
|
1152
|
+
if (assets.length === 0)
|
|
1153
|
+
continue;
|
|
1154
|
+
const workspace = this.getAssignedWorkspace({
|
|
1155
|
+
sessionId,
|
|
1156
|
+
workItemId: item.id,
|
|
1157
|
+
kind: item.kind,
|
|
1158
|
+
});
|
|
1159
|
+
resources.contextAssets.set(item.id, (0, contextAssets_1.materializeContextAssets)({ assets, workspace }));
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1136
1162
|
/**
|
|
1137
1163
|
* @effect Writes each leased implementation item's server-authored briefing
|
|
1138
1164
|
* BESIDE its workspace so the coordinating host dispatches workers with a
|
|
@@ -1729,8 +1755,8 @@ class AgentVariantsOrchestrator {
|
|
|
1729
1755
|
* reclaimed. Other missing-preview causes remain terminal so this path never
|
|
1730
1756
|
* attempts to restart user-owned or repeatedly failing servers.
|
|
1731
1757
|
*
|
|
1732
|
-
* @effect Clears the idle marker, starts the worktree
|
|
1733
|
-
* preview-state snapshots.
|
|
1758
|
+
* @effect Clears the idle marker and prior crash budget, starts the worktree
|
|
1759
|
+
* dev server, and emits preview-state snapshots.
|
|
1734
1760
|
* @deps Uses the live session resources, project context, and dev-server
|
|
1735
1761
|
* manager owned by this orchestrator.
|
|
1736
1762
|
*/
|
|
@@ -1747,6 +1773,7 @@ class AgentVariantsOrchestrator {
|
|
|
1747
1773
|
return undefined;
|
|
1748
1774
|
}
|
|
1749
1775
|
resources.previewFailures.delete(workItemId);
|
|
1776
|
+
record.devServerAutoRestarts = 0;
|
|
1750
1777
|
this.emitChange();
|
|
1751
1778
|
const projectContext = this.store.getProjectContext(sessionId);
|
|
1752
1779
|
return this.startVariantDevServer({
|
|
@@ -1934,6 +1961,7 @@ class AgentVariantsOrchestrator {
|
|
|
1934
1961
|
const projectContext = args.projectContext ?? {
|
|
1935
1962
|
kind: 'existing',
|
|
1936
1963
|
};
|
|
1964
|
+
const designContextCount = projectContext.designContext?.length ?? 0;
|
|
1937
1965
|
// Fresh source-grounded sessions (a captured context bundle on a fresh
|
|
1938
1966
|
// project) must run through `propose` so the source-research/source_plan
|
|
1939
1967
|
// flow gates generation; start_variants routes them there before reaching
|
|
@@ -2056,6 +2084,8 @@ class AgentVariantsOrchestrator {
|
|
|
2056
2084
|
hasTarget: Boolean(args.target),
|
|
2057
2085
|
targetType: args.target?.type ?? null,
|
|
2058
2086
|
projectContextKind: projectContext.kind,
|
|
2087
|
+
design_context_count: designContextCount,
|
|
2088
|
+
design_context_kinds: designContextCount > 0 ? ['design_system'] : [],
|
|
2059
2089
|
});
|
|
2060
2090
|
sessionId = proposeResult.sessionId;
|
|
2061
2091
|
scaffoldBaseWorkItemId = approveResult.scaffoldBaseWorkItemId ?? null;
|
|
@@ -2245,7 +2275,11 @@ class AgentVariantsOrchestrator {
|
|
|
2245
2275
|
async provisionAppendedExistingWorktrees(sessionId, workItemIds) {
|
|
2246
2276
|
if (workItemIds.length === 0)
|
|
2247
2277
|
return;
|
|
2248
|
-
await this.withProvisioningTelemetry({
|
|
2278
|
+
await this.withProvisioningTelemetry({
|
|
2279
|
+
sessionId,
|
|
2280
|
+
kind: 'appended_existing',
|
|
2281
|
+
variantCount: workItemIds.length,
|
|
2282
|
+
}, async () => {
|
|
2249
2283
|
const resources = this.ensureResources(sessionId);
|
|
2250
2284
|
const instanceKeyPrefix = `a${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
|
|
2251
2285
|
const paths = await withProvisionTimeout(this.worktrees.createWorktrees(sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
|
|
@@ -3275,14 +3309,10 @@ class AgentVariantsOrchestrator {
|
|
|
3275
3309
|
? err.code
|
|
3276
3310
|
: 'PROVISION_FAILED',
|
|
3277
3311
|
});
|
|
3278
|
-
//
|
|
3279
|
-
//
|
|
3280
|
-
// the provisioning error
|
|
3281
|
-
|
|
3282
|
-
// could otherwise re-hang the tool on cleanup). The cleanup git calls are
|
|
3283
|
-
// bounded by GIT_OPTS in WorktreeManager, and the next-startup orphan
|
|
3284
|
-
// sweep catches anything created after this point.
|
|
3285
|
-
void this.teardownSession(sessionId, 'cancel').catch((teardownErr) => {
|
|
3312
|
+
// Cancel synchronously before cleanup so a retry does not see this
|
|
3313
|
+
// failed session as active. cancel() schedules teardown without blocking
|
|
3314
|
+
// the provisioning error on git cleanup.
|
|
3315
|
+
void this.cancel({ sessionId, reason: 'provisioning_failed' }).catch((teardownErr) => {
|
|
3286
3316
|
log.warn(`cleanup after provisioning failure for ${sessionId} failed`, teardownErr);
|
|
3287
3317
|
});
|
|
3288
3318
|
if (timedOut) {
|
|
@@ -3744,6 +3774,16 @@ class AgentVariantsOrchestrator {
|
|
|
3744
3774
|
if (args.record.port !== undefined && args.record.devServerProcess) {
|
|
3745
3775
|
return args.record.port;
|
|
3746
3776
|
}
|
|
3777
|
+
if (!args.autoRestartTimer && args.record.pendingAutoRestart) {
|
|
3778
|
+
clearTimeout(args.record.pendingAutoRestart);
|
|
3779
|
+
args.record.pendingAutoRestart = undefined;
|
|
3780
|
+
}
|
|
3781
|
+
const canContinueStart = () => {
|
|
3782
|
+
const resources = this.resources.get(args.sessionId);
|
|
3783
|
+
const isAutoRestartCancelled = args.autoRestartTimer !== undefined &&
|
|
3784
|
+
args.record.pendingAutoRestart !== args.autoRestartTimer;
|
|
3785
|
+
return Boolean(resources && !resources.cleanupStarted && !isAutoRestartCancelled);
|
|
3786
|
+
};
|
|
3747
3787
|
// Bring up a dev server in the variant's worktree so the user can cycle
|
|
3748
3788
|
// through live variants in the iframe via the chip. Failures here are
|
|
3749
3789
|
// logged but non-fatal — the user can still pick by reading the diff.
|
|
@@ -3764,35 +3804,99 @@ class AgentVariantsOrchestrator {
|
|
|
3764
3804
|
});
|
|
3765
3805
|
const startPort = await this.resolveDevServerStartPort(args.sessionId, args.isFresh);
|
|
3766
3806
|
let scanFrom = startPort;
|
|
3767
|
-
const maxAttempts =
|
|
3807
|
+
const maxAttempts = DEV_SERVER_START_MAX_ATTEMPTS;
|
|
3808
|
+
// At most one install-then-retry per start: a second missing-module exit
|
|
3809
|
+
// after a fresh install means the install can't fix it.
|
|
3810
|
+
let installRetried = false;
|
|
3811
|
+
let lastDev;
|
|
3768
3812
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3813
|
+
// Teardown can begin while a prior attempt waits for dependency
|
|
3814
|
+
// installation. Do not start more work against a removed worktree.
|
|
3815
|
+
if (!canContinueStart())
|
|
3816
|
+
return undefined;
|
|
3769
3817
|
let port;
|
|
3770
3818
|
try {
|
|
3771
3819
|
port = await this.worktrees.findFreePortFrom(scanFrom);
|
|
3772
3820
|
const dev = await this.resolveDevServer(args.sessionId, args.workItemId, args.record.worktreePath, port, args.isFresh);
|
|
3821
|
+
lastDev = dev;
|
|
3773
3822
|
const proc = await this.worktrees.startDevServer(dev.cwd, port, dev.cmd, dev.args, dev.env);
|
|
3823
|
+
// The boot can outlive the session (a cancel during the readiness
|
|
3824
|
+
// wait, or during an auto-restart's delay). At this point the child
|
|
3825
|
+
// isn't on the record yet, so teardown's stop loop can't see it —
|
|
3826
|
+
// kill it here instead of leaking an orphaned dev server.
|
|
3827
|
+
if (!canContinueStart()) {
|
|
3828
|
+
log.info(`Variant ${args.workItemId} dev server booted after preview cleanup; stopping it`);
|
|
3829
|
+
void this.worktrees.stopDevServer(proc).catch(() => undefined);
|
|
3830
|
+
return undefined;
|
|
3831
|
+
}
|
|
3832
|
+
if (args.autoRestartTimer) {
|
|
3833
|
+
args.record.pendingAutoRestart = undefined;
|
|
3834
|
+
}
|
|
3774
3835
|
args.record.port = port;
|
|
3775
3836
|
args.record.devServerProcess = proc;
|
|
3776
3837
|
args.record.devCommand = dev;
|
|
3777
3838
|
// If the dev server dies on its own (crash, OOM, Vite hard-fail),
|
|
3778
3839
|
// clear the port so the iframe stops routing the chip onto a dead
|
|
3779
|
-
// socket
|
|
3780
|
-
//
|
|
3781
|
-
//
|
|
3840
|
+
// socket, then bring a replacement up automatically. A ready server
|
|
3841
|
+
// that later crashes is almost always recoverable by a clean restart
|
|
3842
|
+
// (transient OOM, watcher crash, HMR wedge) — without one, every such
|
|
3843
|
+
// crash surfaced as a permanent "preview unavailable" plus a burst of
|
|
3844
|
+
// upstream_lost noise until the user regenerated the variant. The
|
|
3845
|
+
// restart budget keeps a genuinely broken build (crash loop) from
|
|
3846
|
+
// restarting forever. Intentional stops clear `devServerProcess`
|
|
3847
|
+
// before killing, so the guard below filters them out.
|
|
3782
3848
|
proc.once('exit', (code, signal) => {
|
|
3783
3849
|
if (args.record.devServerProcess !== proc)
|
|
3784
3850
|
return;
|
|
3785
3851
|
args.record.port = undefined;
|
|
3786
3852
|
args.record.devServerProcess = undefined;
|
|
3787
|
-
this.resources
|
|
3788
|
-
|
|
3789
|
-
|
|
3853
|
+
const resources = this.resources.get(args.sessionId);
|
|
3854
|
+
const restartsUsed = args.record.devServerAutoRestarts ?? 0;
|
|
3855
|
+
const canRestart = restartsUsed < DEV_SERVER_MAX_AUTO_RESTARTS &&
|
|
3856
|
+
resources !== undefined &&
|
|
3857
|
+
!resources.cleanupStarted;
|
|
3858
|
+
log.warn(`Variant ${args.workItemId} dev server exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'}); ` +
|
|
3859
|
+
(canRestart
|
|
3860
|
+
? `auto-restarting (${restartsUsed + 1}/${DEV_SERVER_MAX_AUTO_RESTARTS})`
|
|
3861
|
+
: 'preview disabled'));
|
|
3862
|
+
if (canRestart) {
|
|
3863
|
+
args.record.devServerAutoRestarts = restartsUsed + 1;
|
|
3864
|
+
this.emitChange();
|
|
3865
|
+
const timer = setTimeout(() => {
|
|
3866
|
+
// Re-check: the session may have been torn down while the
|
|
3867
|
+
// restart was pending — don't spawn into a cleaned-up worktree.
|
|
3868
|
+
const current = this.resources.get(args.sessionId);
|
|
3869
|
+
if (!current ||
|
|
3870
|
+
current.cleanupStarted ||
|
|
3871
|
+
args.record.pendingAutoRestart !== timer) {
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
this.startVariantDevServer({
|
|
3875
|
+
...args,
|
|
3876
|
+
autoRestartTimer: timer,
|
|
3877
|
+
})
|
|
3878
|
+
.catch((err) => log.warn(`Auto-restart of variant ${args.workItemId} dev server failed`, err))
|
|
3879
|
+
.finally(() => {
|
|
3880
|
+
if (args.record.pendingAutoRestart === timer) {
|
|
3881
|
+
args.record.pendingAutoRestart = undefined;
|
|
3882
|
+
}
|
|
3883
|
+
});
|
|
3884
|
+
}, this.devServerRestartDelayMs);
|
|
3885
|
+
args.record.pendingAutoRestart = timer;
|
|
3886
|
+
timer.unref?.();
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
resources?.previewFailures.set(args.workItemId, {
|
|
3790
3890
|
reason: 'exited',
|
|
3791
3891
|
portInUse: false,
|
|
3792
3892
|
});
|
|
3793
|
-
log.warn(`Variant ${args.workItemId} dev server exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'}); preview disabled`);
|
|
3794
3893
|
this.emitChange();
|
|
3795
3894
|
});
|
|
3895
|
+
// A prior failure record (startup failure before a successful retry,
|
|
3896
|
+
// idle reclaim, crash before this restart) is now stale.
|
|
3897
|
+
this.resources
|
|
3898
|
+
.get(args.sessionId)
|
|
3899
|
+
?.previewFailures.delete(args.workItemId);
|
|
3796
3900
|
this.emitChange();
|
|
3797
3901
|
trackFreshDevServerStarted(this.telemetry, {
|
|
3798
3902
|
sessionId: args.sessionId,
|
|
@@ -3813,19 +3917,54 @@ class AgentVariantsOrchestrator {
|
|
|
3813
3917
|
return port;
|
|
3814
3918
|
}
|
|
3815
3919
|
catch (err) {
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
const
|
|
3920
|
+
if (!canContinueStart())
|
|
3921
|
+
return undefined;
|
|
3922
|
+
const devErr = (0, devServerError_1.isDevServerStartError)(err) ? err : undefined;
|
|
3923
|
+
const portInUse = devErr?.portInUse ?? false;
|
|
3924
|
+
const reason = devErr?.reason ?? 'unknown';
|
|
3925
|
+
// Classify what a retry can actually fix:
|
|
3926
|
+
// - port_in_use: a fresh port fixes it (also step past unmatched
|
|
3927
|
+
// collision messages via the blanket exited retry below).
|
|
3928
|
+
// - missing/stale dependencies (module-resolution errors in the
|
|
3929
|
+
// output): an install fixes it — install, then retry once.
|
|
3930
|
+
// - any other early exit: retry once; exits fail fast, so one clean
|
|
3931
|
+
// attempt is cheap insurance against transient races (fs churn from
|
|
3932
|
+
// sibling provisioning, npm cache contention).
|
|
3933
|
+
// - timeout / spawn_error / missing_dev_script: deterministic; a
|
|
3934
|
+
// retry just doubles the wait, so fail with the actionable cause.
|
|
3935
|
+
const missingDepsOutput = (0, devServerError_1.isMissingDependencyOutput)(devErr?.detail ?? '');
|
|
3936
|
+
const missingDeps = missingDepsOutput &&
|
|
3937
|
+
reason === 'exited' &&
|
|
3938
|
+
!installRetried &&
|
|
3939
|
+
lastDev !== undefined &&
|
|
3940
|
+
canInstallWithoutTouchingHost(lastDev.cwd);
|
|
3941
|
+
const willRetry = attempt < maxAttempts &&
|
|
3942
|
+
(portInUse || missingDeps || (reason === 'exited' && attempt === 1));
|
|
3819
3943
|
// Next attempt scans past the port we just tried so we don't re-pick a
|
|
3820
3944
|
// port that's persistently held (vs. a transient race that freed it).
|
|
3821
3945
|
scanFrom = (port ?? scanFrom) + 1;
|
|
3822
3946
|
log.warn(`Failed to start dev server for variant ${args.workItemId} on port ${port ?? 'unallocated'} ` +
|
|
3823
|
-
`(attempt ${attempt}/${maxAttempts}, reason=${reason}, portInUse=${portInUse})` +
|
|
3947
|
+
`(attempt ${attempt}/${maxAttempts}, reason=${reason}, portInUse=${portInUse}, missingDeps=${missingDeps})` +
|
|
3824
3948
|
(willRetry
|
|
3825
|
-
?
|
|
3949
|
+
? missingDeps
|
|
3950
|
+
? '; installing dependencies and retrying'
|
|
3951
|
+
: '; retrying on a fresh port'
|
|
3826
3952
|
: '; live preview disabled for this variant'), err);
|
|
3827
|
-
if (willRetry)
|
|
3953
|
+
if (willRetry) {
|
|
3954
|
+
if (missingDeps && lastDev) {
|
|
3955
|
+
installRetried = true;
|
|
3956
|
+
try {
|
|
3957
|
+
await this.installDependencies(lastDev.cwd, lastDev.packageManager);
|
|
3958
|
+
}
|
|
3959
|
+
catch (installErr) {
|
|
3960
|
+
// Still retry: the boot error may have been transient, and the
|
|
3961
|
+
// retry's own failure telemetry is more specific than aborting
|
|
3962
|
+
// here on the install's stderr.
|
|
3963
|
+
log.warn(`Dependency install before dev-server retry failed for variant ${args.workItemId}`, installErr);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3828
3966
|
continue;
|
|
3967
|
+
}
|
|
3829
3968
|
// Record the failure so getVariants can tell the UI this direction's
|
|
3830
3969
|
// preview couldn't start (and why), then push a snapshot so the chip
|
|
3831
3970
|
// updates from "loading" to the accurate per-direction message.
|
|
@@ -3835,22 +3974,35 @@ class AgentVariantsOrchestrator {
|
|
|
3835
3974
|
reason,
|
|
3836
3975
|
portInUse,
|
|
3837
3976
|
});
|
|
3977
|
+
const errorCode = reason === 'missing_dev_script'
|
|
3978
|
+
? 'DEV_SCRIPT_MISSING'
|
|
3979
|
+
: 'DEV_SERVER_START_FAILED';
|
|
3980
|
+
// Telemetry gets structured classification only — never the raw
|
|
3981
|
+
// output tail, which routinely carries absolute project paths and
|
|
3982
|
+
// source frames (see AGENTS.md "What NEVER to capture"). The full
|
|
3983
|
+
// tail still reaches the local logs via `err` above.
|
|
3838
3984
|
trackFreshDevServerFailed(this.telemetry, {
|
|
3839
3985
|
sessionId: args.sessionId,
|
|
3840
3986
|
variantId: args.workItemId,
|
|
3841
|
-
errorCode
|
|
3987
|
+
errorCode,
|
|
3842
3988
|
reason,
|
|
3843
3989
|
portInUse,
|
|
3990
|
+
exitCode: devErr?.exitCode,
|
|
3991
|
+
signal: devErr?.signal,
|
|
3992
|
+
missingDeps: missingDepsOutput,
|
|
3844
3993
|
});
|
|
3845
3994
|
trackPreviewFailed(this.telemetry, {
|
|
3846
3995
|
sessionId: args.sessionId,
|
|
3847
3996
|
variantId: args.workItemId,
|
|
3848
3997
|
projectContextKind,
|
|
3849
3998
|
previewKind: 'dev_server',
|
|
3850
|
-
errorCode
|
|
3999
|
+
errorCode,
|
|
3851
4000
|
reason,
|
|
3852
4001
|
portInUse,
|
|
3853
4002
|
durationMs: Date.now() - previewStartedAt,
|
|
4003
|
+
exitCode: devErr?.exitCode,
|
|
4004
|
+
signal: devErr?.signal,
|
|
4005
|
+
missingDeps: missingDepsOutput,
|
|
3854
4006
|
});
|
|
3855
4007
|
this.emitChange();
|
|
3856
4008
|
return undefined;
|
|
@@ -4238,7 +4390,7 @@ class AgentVariantsOrchestrator {
|
|
|
4238
4390
|
label: input.briefLabel,
|
|
4239
4391
|
brief: input.briefBody,
|
|
4240
4392
|
sessionPrompt,
|
|
4241
|
-
runLabel: this.store.getRunLabel(args.sessionId),
|
|
4393
|
+
runLabel: input.runLabel ?? this.store.getRunLabel(args.sessionId),
|
|
4242
4394
|
kind: 'diff',
|
|
4243
4395
|
diff: args.diff,
|
|
4244
4396
|
sourceDir,
|
|
@@ -4483,7 +4635,7 @@ class AgentVariantsOrchestrator {
|
|
|
4483
4635
|
label: input.briefLabel,
|
|
4484
4636
|
brief: input.briefBody,
|
|
4485
4637
|
sessionPrompt,
|
|
4486
|
-
runLabel: this.store.getRunLabel(args.sessionId),
|
|
4638
|
+
runLabel: input.runLabel ?? this.store.getRunLabel(args.sessionId),
|
|
4487
4639
|
kind: 'project-created',
|
|
4488
4640
|
sourceDir,
|
|
4489
4641
|
preview,
|
|
@@ -4725,6 +4877,12 @@ class AgentVariantsOrchestrator {
|
|
|
4725
4877
|
const env = await this.resolveEnv(sessionId);
|
|
4726
4878
|
const cwd = await this.resolveExistingProjectCwd(sessionId, worktreePath, env);
|
|
4727
4879
|
const packageManager = detectPackageManagerForCwd(cwd, env.packageManager);
|
|
4880
|
+
// Every branch below — including the production buildDevCommand recipes
|
|
4881
|
+
// from utils/devServerCommand.ts — invokes the package.json script named
|
|
4882
|
+
// by env.devCommand, so the preflight applies to all of them.
|
|
4883
|
+
if (env.devCommand) {
|
|
4884
|
+
assertDevScriptExists(cwd, env.devCommand);
|
|
4885
|
+
}
|
|
4728
4886
|
if (packageManager !== env.packageManager) {
|
|
4729
4887
|
return {
|
|
4730
4888
|
cwd,
|
|
@@ -5026,6 +5184,10 @@ class AgentVariantsOrchestrator {
|
|
|
5026
5184
|
this.clearActivePreviewIdleTimer(resources);
|
|
5027
5185
|
const stops = [];
|
|
5028
5186
|
for (const record of resources.worktrees.values()) {
|
|
5187
|
+
if (record.pendingAutoRestart) {
|
|
5188
|
+
clearTimeout(record.pendingAutoRestart);
|
|
5189
|
+
record.pendingAutoRestart = undefined;
|
|
5190
|
+
}
|
|
5029
5191
|
if (!record.devServerProcess)
|
|
5030
5192
|
continue;
|
|
5031
5193
|
const proc = record.devServerProcess;
|
|
@@ -5070,11 +5232,12 @@ class AgentVariantsOrchestrator {
|
|
|
5070
5232
|
return;
|
|
5071
5233
|
}
|
|
5072
5234
|
const stop = (async () => {
|
|
5073
|
-
const
|
|
5074
|
-
|
|
5075
|
-
|
|
5235
|
+
const expiringRecords = Array.from(resources.worktrees.values()).filter((record) => Boolean(record.devServerProcess) ||
|
|
5236
|
+
Boolean(record.pendingAutoRestart));
|
|
5237
|
+
const expiringWorkItemIds = expiringRecords.map((record) => record.workItemId);
|
|
5238
|
+
const pendingRestartCount = expiringRecords.filter((record) => Boolean(record.pendingAutoRestart)).length;
|
|
5076
5239
|
const stoppedCount = await this.stopAllDevServersForSession(resources);
|
|
5077
|
-
if (
|
|
5240
|
+
if (expiringWorkItemIds.length === 0)
|
|
5078
5241
|
return;
|
|
5079
5242
|
for (const workItemId of expiringWorkItemIds) {
|
|
5080
5243
|
resources.previewFailures.set(workItemId, {
|
|
@@ -5082,7 +5245,8 @@ class AgentVariantsOrchestrator {
|
|
|
5082
5245
|
portInUse: false,
|
|
5083
5246
|
});
|
|
5084
5247
|
}
|
|
5085
|
-
log.info(`
|
|
5248
|
+
log.info(`Reclaimed ${expiringWorkItemIds.length} idle variant preview(s) for session ${sessionId} ` +
|
|
5249
|
+
`(${stoppedCount} running, ${pendingRestartCount} restarting)`);
|
|
5086
5250
|
// The next selection restores only the requested Rivet-owned preview;
|
|
5087
5251
|
// siblings remain stopped until the user asks to view them.
|
|
5088
5252
|
try {
|
|
@@ -5297,6 +5461,7 @@ class AgentVariantsOrchestrator {
|
|
|
5297
5461
|
worktrees: new Map(),
|
|
5298
5462
|
staticPreviews: new Map(),
|
|
5299
5463
|
workspaceManager: new VariantWorkspaceManager_1.VariantWorkspaceManager(),
|
|
5464
|
+
contextAssets: new Map(),
|
|
5300
5465
|
cleanupStarted: false,
|
|
5301
5466
|
committedVariantIds: new Set(),
|
|
5302
5467
|
startedAt: Date.now(),
|
|
@@ -6117,6 +6282,9 @@ const trackFreshDevServerFailed = (telemetry, data) => {
|
|
|
6117
6282
|
error_code: data.errorCode,
|
|
6118
6283
|
failure_reason: data.reason,
|
|
6119
6284
|
port_in_use: data.portInUse,
|
|
6285
|
+
exit_code: data.exitCode,
|
|
6286
|
+
term_signal: data.signal,
|
|
6287
|
+
missing_deps: data.missingDeps,
|
|
6120
6288
|
});
|
|
6121
6289
|
};
|
|
6122
6290
|
const trackPreviewStarted = (telemetry, data) => {
|
|
@@ -6163,6 +6331,9 @@ const trackPreviewFailed = (telemetry, data) => {
|
|
|
6163
6331
|
failure_reason: data.reason,
|
|
6164
6332
|
port_in_use: data.portInUse,
|
|
6165
6333
|
duration_ms: data.durationMs,
|
|
6334
|
+
exit_code: data.exitCode,
|
|
6335
|
+
term_signal: data.signal,
|
|
6336
|
+
missing_deps: data.missingDeps,
|
|
6166
6337
|
});
|
|
6167
6338
|
};
|
|
6168
6339
|
const trackStaticPreviewCompleted = (telemetry, data) => {
|
|
@@ -6191,7 +6362,7 @@ const STATIC_PREVIEW_HISTORY_EXCLUDE = new Set([
|
|
|
6191
6362
|
'.rivet',
|
|
6192
6363
|
]);
|
|
6193
6364
|
const STATIC_PREVIEW_HISTORY_FILE_EXTENSIONS = new Set([
|
|
6194
|
-
...
|
|
6365
|
+
...ALLOWED_INERT_ASSET_EXTENSIONS,
|
|
6195
6366
|
'.css',
|
|
6196
6367
|
'.htm',
|
|
6197
6368
|
'.html',
|
|
@@ -6301,6 +6472,60 @@ const buildServeRecipeFromResolved = (snapshotRoot, dev, port) => {
|
|
|
6301
6472
|
envTemplate: Object.fromEntries(Object.entries(dev.env).map(([key, value]) => [key, sub(value)])),
|
|
6302
6473
|
};
|
|
6303
6474
|
};
|
|
6475
|
+
/**
|
|
6476
|
+
* Whether an install at `cwd` stays inside the Rivet-owned worktree.
|
|
6477
|
+
* Existing-project worktrees SYMLINK `node_modules` to the user's real
|
|
6478
|
+
* install (WorktreeManager.symlinkNodeModulesIfAvailable) — running a
|
|
6479
|
+
* package manager through that link would mutate the user's project
|
|
6480
|
+
* dependencies, which the variants flow must never do. Install is safe only
|
|
6481
|
+
* when `node_modules` is absent (a nested standalone package — the install
|
|
6482
|
+
* creates a worktree-local dir) or a real directory owned by the worktree.
|
|
6483
|
+
*/
|
|
6484
|
+
const canInstallWithoutTouchingHost = (cwd) => {
|
|
6485
|
+
try {
|
|
6486
|
+
return !fs_1.default.lstatSync(path_1.default.join(cwd, 'node_modules')).isSymbolicLink();
|
|
6487
|
+
}
|
|
6488
|
+
catch {
|
|
6489
|
+
return true;
|
|
6490
|
+
}
|
|
6491
|
+
};
|
|
6492
|
+
/**
|
|
6493
|
+
* Fail fast — before spawning anything — when the package.json at `cwd`
|
|
6494
|
+
* positively lacks the dev script we're about to invoke. Without this the
|
|
6495
|
+
* spawn exits with the package manager's "missing script" error after a full
|
|
6496
|
+
* boot round-trip and surfaces as an opaque `exited` failure; with it the
|
|
6497
|
+
* user gets an actionable message naming the scripts that DO exist. Missing
|
|
6498
|
+
* or unparseable package.json is NOT treated as a failure here — other paths
|
|
6499
|
+
* own those cases, and the spawn's own error is more specific.
|
|
6500
|
+
*/
|
|
6501
|
+
const assertDevScriptExists = (cwd, scriptName) => {
|
|
6502
|
+
let scripts;
|
|
6503
|
+
try {
|
|
6504
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(cwd, 'package.json'), 'utf8');
|
|
6505
|
+
const parsed = JSON.parse(raw);
|
|
6506
|
+
if (parsed && typeof parsed === 'object' && 'scripts' in parsed) {
|
|
6507
|
+
const candidate = parsed.scripts;
|
|
6508
|
+
if (candidate && typeof candidate === 'object') {
|
|
6509
|
+
scripts = candidate;
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
catch {
|
|
6514
|
+
return;
|
|
6515
|
+
}
|
|
6516
|
+
if (scripts && typeof scripts[scriptName] === 'string')
|
|
6517
|
+
return;
|
|
6518
|
+
const available = Object.keys(scripts ?? {});
|
|
6519
|
+
throw new devServerError_1.DevServerStartError({
|
|
6520
|
+
message: `package.json at ${cwd} has no "${scriptName}" script` +
|
|
6521
|
+
(available.length ? ` (available: ${available.join(', ')})` : ''),
|
|
6522
|
+
reason: 'missing_dev_script',
|
|
6523
|
+
portInUse: false,
|
|
6524
|
+
detail: available.length
|
|
6525
|
+
? `available scripts: ${available.join(', ')}`
|
|
6526
|
+
: '',
|
|
6527
|
+
});
|
|
6528
|
+
};
|
|
6304
6529
|
const buildExistingProjectDevCommand = (packageManager, devCommand, port) => {
|
|
6305
6530
|
const env = { PORT: String(port) };
|
|
6306
6531
|
if (packageManager === 'npm') {
|